├── LICENSE ├── README.md ├── bin └── ecs-docker-run ├── ecs-cloudformation ├── README.md └── ecs.json └── test ├── .gitignore ├── expect ├── test-all-options-with-equal-sign.json ├── test-all-options.json ├── test-all-short-options.json └── test-expose-all-ports.json ├── run-tests ├── test-all-options ├── test-all-options-with-equal-sign ├── test-all-short-options └── test-expose-all-ports /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecs-docker-run 2 | 3 | A simple command line utility to run docker images on Amazon ECS. This utility will generate a task definition based upon the 4 | docker run command line options you are familiar with. 5 | 6 | The utility can be used to generate a task definition or to run a task or service in ECS based on this definition. 7 | 8 | ## options 9 | 10 | ``` 11 | --generate-only - - will only generate the task definition on standard output, without starting anything 12 | --run-as-service - runs the task as service, ECS will ensure that 'desired-count' tasks will keep running 13 | --desired-count - specifies the number tasks to run (default = 1) 14 | --cluster - the ECS cluster to run the task or service (default = cluster) 15 | ``` 16 | 17 | All the following Docker run command line options of Docker 1.7 are supported. 18 | 19 | ``` 20 | -P - publishes all ports by pulling and inspecting the image. 21 | --name - the family name of the task. If unspecified the name will be derived from the image name. 22 | -p - add a port publication to the task definition. 23 | --env - set the environment variable. 24 | --memory - sets the amount of memory to allocate, defaults to 256 25 | --cpu-shares - set the share cpu to allocate, defaults to 100 26 | --entrypoint - changes the entrypoint for the container 27 | --link - set the container link. 28 | -v - set the mount points for the container. 29 | --volumes-from - sets the volumes to mount. 30 | ``` 31 | all other options are ignored with a warning. 32 | 33 | 34 | ## pre-requisites 35 | the ecs-docker-run utility has the following pre-requisites. 36 | 37 | - jq installed 38 | - aws CLI installed version 1.7.44 or higher 39 | - aws connectivity configured (aws iam get-user) 40 | - docker connectivity configured (docker ps) 41 | - a running ECS cluster checkout 42 | 43 | If you do not have an ECS cluster running, the fastest way is to follow the 44 | instructions in [ecs-cloudformation/README](ecs-cloudformation/README.md) 45 | 46 | ## usage 47 | 48 | ### Generating a task definition 49 | 50 | ``` 51 | $ ecs-docker-run --generate-only --name paas-monitor -P mvanholsteijn/paas-monitor:latest 52 | 53 | { 54 | "family": "paas-monitor", 55 | "containerDefinitions": [ 56 | { 57 | "volumesFrom": [], 58 | "portMappings": [ 59 | { 60 | "hostPort": 0, 61 | "containerPort": 1337 62 | } 63 | ], 64 | "command": [], 65 | "environment": [], 66 | "links": [], 67 | "mountPoints": [], 68 | "essential": true, 69 | "memory": 256, 70 | "name": "paas-monitor", 71 | "cpu": 100, 72 | "image": "mvanholsteijn/paas-monitor:latest" 73 | } 74 | ], 75 | "volumes": [] 76 | } 77 | ``` 78 | 79 | 80 | ### Starting docker container as a task 81 | 82 | ``` 83 | $ ecs-docker-run \ 84 | --cluster default \ 85 | --name paas-monitor \ 86 | --env SERVICE_NAME=paas-monitor \ 87 | --env SERVICE_TAGS=http \ 88 | --env "MESSAGE=Hello from ECS task" \ 89 | --env RELEASE=v10 \ 90 | -P \ 91 | mvanholsteijn/paas-monitor:latest 92 | ``` 93 | 94 | ### Starting docker container as a service in ECS 95 | 96 | ``` 97 | $ ecs-docker-run \ 98 | --run-as-service \ 99 | --number-of-instances 3 \ 100 | --cluster default \ 101 | --name paas-monitor \ 102 | --env SERVICE_NAME=paas-monitor \ 103 | --env SERVICE_TAGS=http \ 104 | --env "MESSAGE=Hello from ECS paas-monitor service" \ 105 | --env RELEASE=v11 \ 106 | -P \ 107 | mvanholsteijn/paas-monitor:latest 108 | ``` 109 | 110 | ## Caveats 111 | - Multiple container definitions are not supported. 112 | -------------------------------------------------------------------------------- /bin/ecs-docker-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # A simple command line utility to run docker images on Amazon ECS. This utility will generate a task definition 3 | # based upon the docker run command line options you are familiar with. 4 | # 5 | # Copyright 2015 - Xebia Nederland B.V. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | # Author Mark van Holsteijn 20 | 21 | declare -a PUBLISH_PORTS 22 | declare -a VOLUMES 23 | declare -a VOLUMES_FROM 24 | declare -a LINKS 25 | declare -a ENVS 26 | CLUSTER=default 27 | WORKDIR= 28 | DOCKER_USER= 29 | ENTRYPOINT= 30 | EXPOSE_ALL_PORTS= 31 | ARGS= 32 | NAME= 33 | IMAGE= 34 | NUMBER_OF_INSTANCES=1 35 | MEMORY=256 36 | CPU_SHARES=100 37 | TASK_DEF= 38 | TASK_ARN= 39 | RUN_AS_SERVICE=0 40 | 41 | function ignore() { 42 | echo "WARN: command line option $1 recoqnized but ignored " >&2 43 | } 44 | 45 | function parseDockerRunCmd() { 46 | 47 | PUBLISH_PORTS=() 48 | VOLUMES=() 49 | VOLUMES_FROM=() 50 | LINKS=() 51 | ENVS=() 52 | 53 | while [ $# -gt 0 ] ; do 54 | case "$1" in 55 | --generate-only) GENERATE_ONLY=1; shift;; 56 | --number-of-instances) shift ; NUMBER_OF_INSTANCES="$1"; shift;; 57 | --cluster) shift; CLUSTER="$1"; shift;; 58 | --run-as-service) RUN_AS_SERVICE=1; shift;; 59 | -t) ignore "$1"; shift;; 60 | -it) ignore "$1"; shift;; 61 | -ti) ignore "$1"; shift;; 62 | -a) ignore "$1"; shift 2;; 63 | -d|-d=*) ignore "$1"; shift;; 64 | -i|-i=*) ignore "$1"; shift;; 65 | --sig-proxy=*) ignore "$1"; shift;; 66 | --sig-proxy) ignore "$1"; shift 2;; 67 | --cidfile=*) ignore "$1"; shift;; 68 | --cidfile) ignore "$1"; shift 2;; 69 | --pid=*) ignore "$1"; shift;; 70 | --uts=*) ignore "$1"; shift;; 71 | --ipc=*) ignore "$1"; shift;; 72 | --net=*) ignore "$1"; shift;; 73 | --net) ignore "$1"; shift 2;; 74 | --add-host=*) ignore "$1"; shift;; 75 | --add-host) ignore "$1"; shift 2;; 76 | --mac-address=*) ignore "$1"; shift;; 77 | --mac-address) ignore "$1"; shift 2;; 78 | --name) shift; NAME="$1" ; shift;; 79 | --name=*) NAME="$(echo "$1" | sed -e 's/^[^=]*=//')" ; shift;; 80 | --dns=*) ignore "$1"; shift;; 81 | --dns) ignore "$1"; shift 2;; 82 | --dns-search=*) ignore "$1"; shift;; 83 | --dns-search) ignore "$1"; shift 2;; 84 | --restart) ignore "$1"; shift 2;; 85 | --restart=*) ignore "$1"; shift;; 86 | --rm|--rm=*) ignore "$1"; shift;; 87 | --security-opt) ignore "$1"; shift 2;; 88 | --security-opt=*) ignore "$1"; shift;; 89 | -m|--memory) shift ; MEMORY="$1" ; shift;; 90 | -m=*|--memory=*) MEMORY="$(echo "$1" | sed -e 's/^[^=]*=//')" ; shift;; 91 | --memory-swap=*) ignore "$1"; shift;; 92 | --memory-swap) ignore "$1"; shift 2;; 93 | -c|--cpu-shares) shift ; CPU_SHARES="$1"; shift;; 94 | -c=*|--cpu-shares=*) CPU_SHARES="$(echo "$1" | sed -e 's/^[^=]*=//')" ; shift ;; 95 | --cpuset-cpus=*) ignore "$1"; shift;; 96 | --cpuset-cpus) ignore "$1"; shift 2;; 97 | --cpuset-mems=*) ignore "$1"; shift;; 98 | --cpuset-mems) ignore "$1"; shift 2;; 99 | --cpu-quota=*) ignore "$1"; shift;; 100 | --cpu-quota) ignore "$1"; shift 2;; 101 | --blkio-weight) ignore "$1"; shift 2;; 102 | --blkio-weight=*) ignore "$1"; shift ;; 103 | --oom-kill-disable*) ignore "$1"; shift ;; 104 | --cap-add=*) ignore "$1"; shift;; 105 | --cap-add) ignore "$1"; shift 2;; 106 | --cap-drop=*) ignore "$1"; shift 2;; 107 | --cap-drop) ignore "$1"; shift 2;; 108 | --privileged*) ignore "$1"; shift;; 109 | --device=) ignore "$1"; shift ;; 110 | --device) ignore "$1"; shift 2;; 111 | --lxc-conf=*) ignore "$1"; shift ;; 112 | --lxc-conf) ignore "$1"; shift 2;; 113 | --log-driver) ignore "$1"; shift 2;; 114 | --log-driver=) ignore "$1"; shift ;; 115 | --log-opt=) ignore "$1"; shift ;; 116 | --log-opt) ignore "$1"; shift 2;; 117 | --entrypoint) shift ; ENTRYPOINT="$1"; shift ;; 118 | --entrypoint=*) ENTRYPOINT="$(echo "$1" | sed -e 's/^[^=]*=//')" ; shift ;; 119 | --expose*) ignore "$1"; shift ; shift;; 120 | -P) EXPOSE_ALL_PORTS=1; shift ;; 121 | -p) shift ; PUBLISH_PORTS+=("$1") ; shift;; 122 | -p=*) PUBLISH_PORTS+=("$(echo "$1" | sed -e 's/^[^=]*=//')") ; shift;; 123 | --link) shift ; LINKS+=("$1") ; shift;; 124 | --link=*) LINKS+=("$(echo "$1" | sed -e 's/^[^=]*=//')") ; shift;; 125 | -e|--env) shift ; ENVS+=("$1") ; shift;; 126 | -u) ignore "$1"; shift ; DOCKER_USER="$1" ; shift;; 127 | -u=*) ignore "$1"; shift ; DOCKER_USR="$(echo "$1" | sed -e 's/^[^=]*=//')" ; shift;; 128 | -w) ignore "$1"; shift ; WORKDIR=("$1") ; shift;; 129 | -w=*) ignore "$1"; shift ; WORKDIR="$("echo "$1" | sed -e 's/^[^=]*=//'")" ; shift;; 130 | -v) shift ; VOLUMES+=("$1") ; shift;; 131 | -v=*) shift ; VOLUMES+=("$(echo "$1" | sed -e 's/^[^=]*=//')") ; shift;; 132 | --volumes-from) shift ; VOLUMES_FROM+=("$1") ; shift;; 133 | --volumes-from=*) VOLUMES_FROM+=("$(echo "$1" | sed -e 's/^[^=]*=//')") ; shift;; 134 | -*) 135 | if expr "$1" : '^--env=.*' > /dev/null ; then 136 | ENVS+=("$(echo "$1" | sed -e 's/^[^=]*=//')") ; 137 | else 138 | echo ERROR: ignoring Unknown option $1. >&2 139 | fi 140 | shift ;; 141 | *) 142 | IMAGE="$1"; shift; 143 | break;; 144 | esac 145 | done 146 | ARGS="$@" 147 | } 148 | 149 | function getDockerRun() { 150 | set -- $(getExecStart $1) 151 | 152 | DOCKER=0 153 | while [ $# -gt 0 ] ; do 154 | if [ "$1" == "/usr/bin/docker" -o "$1" == "docker" ] ; then 155 | shift 156 | DOCKER=1 157 | elif [ $DOCKER -eq 1 ] ; then 158 | if [ "$1" == "run" ] ; then 159 | shift 160 | parseDockerRunCmd $(echo $@ | sed -e 's/\( --?[a-z][a-z\-]*\)=/\1 /') 161 | break 162 | else 163 | echo ERROR expect docker run. Got docker "$1". 164 | exit 1 165 | fi 166 | else 167 | shift 168 | fi 169 | done 170 | } 171 | 172 | function _generateTaskDef() { 173 | echo { \"family\" : \"$NAME\" , 174 | echo \"containerDefinitions\" : [ 175 | echo { 176 | generateContainerDef 177 | echo } 178 | echo ] , 179 | generateVolumes 180 | echo } 181 | } 182 | 183 | function generateTaskDef() { 184 | TASK_DEF="$(_generateTaskDef | jq .)" 185 | } 186 | 187 | function generateContainerDef() { 188 | generateVolumesFromDef 189 | generatePortMappingsDef 190 | generateCommandDef 191 | generateEnvironmentDef 192 | generateLinkDef 193 | generateMountPointsDef 194 | generateImageDef 195 | } 196 | 197 | function generateVolumesFromDef() { 198 | echo \"volumesFrom\" : [ 199 | COMMA= 200 | for VOLUME in "${VOLUMES_FROM[@]}"; do 201 | [ -n "$COMMA" ] && echo ',' 202 | echo -n \"$VOLUME\" 203 | COMMA=1 204 | done 205 | echo ], 206 | } 207 | 208 | function generatePortMappingsDef() { 209 | echo \"portMappings\" : [ 210 | COMMA= 211 | if [ -n "$EXPOSE_ALL_PORTS" ] ; then 212 | PORTS=$(docker inspect $IMAGE | jq -r '.[0].Config.ExposedPorts | keys | .[] | .') 213 | for P in $PORTS; do 214 | PORT=$(echo $P | awk -F/ '{print $1}') 215 | PROTOCOL=$(echo $P | awk -F/ '{print $2}') 216 | [ -n "$COMMA" ] && echo ',' 217 | echo { 218 | echo \"hostPort\": 0, 219 | echo \"containerPort\" : $PORT 220 | if [ $PROTOCOL != "tcp" ] ; then 221 | echo , \"protocol\" : \"$PROTOCOL\" 222 | fi 223 | echo -n } 224 | COMMA=1 225 | done 226 | fi 227 | for P in ${PUBLISH_PORTS[@]}; do 228 | LOCALHOST=$(echo $P | awk -F: '{ if (NF > 2) print $1}') 229 | LOCALPORT=$(echo $P | awk -F: '{ if (NF > 2) print $2; else print $1;}') 230 | CONTAINERPORT=$(echo $P | awk -F: '{ if (NF > 2) print $3; else print $2;}') 231 | PORT=$(echo $CONTAINERPORT | awk -F/ '{print $1}') 232 | PROTOCOL=$(echo $CONTAINERPORT | awk -F/ '{print $2}') 233 | if [ -n "$LOCALHOST" ] ; then 234 | echo WARN: ECS cannot bind to IP address $LOCALHOST for port $CONTAINERPORT >&2 235 | fi 236 | 237 | [ -n "$COMMA" ] && echo ',' 238 | echo { 239 | echo \"hostPort\": $LOCALPORT, 240 | echo \"containerPort\" : $PORT 241 | if [ -n "$PROTOCOL" -a "$PROTOCOL" != "tcp" ] ; then 242 | echo , \"protocol\" : \"$PROTOCOL\" 243 | fi 244 | echo -n } 245 | COMMA=1 246 | done 247 | echo ], 248 | } 249 | 250 | function generateCommandDef() { 251 | echo \"command\": [ 252 | COMMA= 253 | for ARG in $ARGS; do 254 | [ -n "$COMMA" ] && echo ',' 255 | echo -n \"$ARG\" 256 | COMMA=1 257 | done 258 | echo ], 259 | } 260 | 261 | function generateEnvironmentDef() { 262 | echo \"environment\": [ 263 | COMMA= 264 | for ENV in "${ENVS[@]}"; do 265 | [ -n "$COMMA" ] && echo ',' 266 | echo { 267 | echo -n \"name\": \";echo -n $ENV | sed -e 's/[ \t]*=.*/",/' 268 | echo -n \"value\": \";echo -n $ENV | sed -e 's/^[^=]*=//' -e 's/$/"/' 269 | echo -n } 270 | COMMA=1 271 | done 272 | echo ], 273 | } 274 | 275 | 276 | function generateLinkDef() { 277 | echo -n "\"links\": [ " 278 | COMMA= 279 | for LINK in ${LINKS[@]}; do 280 | [ -n "$COMMA" ] && echo -n ', ' 281 | echo -n \"$LINK\" 282 | COMMA=1 283 | done 284 | echo ' ],' 285 | } 286 | 287 | function generateMountPointsDef() { 288 | echo \"mountPoints\": [ 289 | COMMA= 290 | for VOLUME in "${VOLUMES[@]}"; do 291 | [ -n "$COMMA" ] && echo ',' 292 | HOSTPATH=$(echo $VOLUME | awk -F: '{print $1}' | sed -e 's/^\///' -e 's/\//-/g') 293 | CONTAINERPATH=$(echo $VOLUME | awk -F: '{print $2}') 294 | RW=$(echo $VOLUME | awk -F: '{if ($3 == "ro") print "true"; else print "false" ; }') 295 | COMMA=1 296 | echo { 297 | echo \"sourceVolume\": \"$HOSTPATH\", 298 | echo \"containerPath\": \"$CONTAINERPATH\", 299 | echo \"readOnly\": $RW 300 | echo } 301 | done 302 | echo ], 303 | } 304 | 305 | function generateImageDef() { 306 | if [ -n "$ENTRYPOINT" ] ; then 307 | echo \"entrypoint\": [ \"$ENTRYPOINT\" ], 308 | fi 309 | echo \"essential\": true, 310 | echo \"memory\": $MEMORY, 311 | echo \"name\": \"$NAME\", 312 | echo \"cpu\": $CPU_SHARES, 313 | echo \"image\": \"$IMAGE\" 314 | } 315 | 316 | function generateVolumes() { 317 | echo \"volumes\": [ 318 | COMMA= 319 | for VOLUME in ${VOLUMES[@]}; do 320 | [ -n "$COMMA" ] && echo ',' 321 | HOSTPATH=$(echo $VOLUME | awk -F: '{print $1}') 322 | VOLHOSTNAME=$(echo $HOSTPATH | sed -e 's/^\///' -e 's/\//-/g') 323 | COMMA=1 324 | echo { 325 | echo \"name\": \"$VOLHOSTNAME\", 326 | echo \"host\": { 327 | echo \"sourcePath\": \"$HOSTPATH\" 328 | echo } 329 | echo } 330 | done 331 | echo ] 332 | } 333 | 334 | function registerTask() { 335 | TASK_DEF="$(aws ecs register-task-definition --cli-input-json "$TASK_DEF")" 336 | } 337 | 338 | function serviceExists() { 339 | local EXIST=$(aws ecs list-services --cluster $CLUSTER | jq -r .serviceArns[] | grep "/$NAME\$") 340 | test -n "$EXIST" 341 | } 342 | 343 | function runService() { 344 | 345 | if serviceExists ; then 346 | aws ecs update-service --cluster $CLUSTER --service $NAME --task-definition "$(getTaskArn)" --desired-count $NUMBER_OF_INSTANCES 347 | else 348 | aws ecs create-service --cluster $CLUSTER --service-name $NAME --task-definition "$(getTaskArn)" --desired-count $NUMBER_OF_INSTANCES 349 | fi 350 | } 351 | 352 | function getTaskArn() { 353 | echo "$TASK_DEF" | jq -r .taskDefinition.taskDefinitionArn 354 | } 355 | 356 | function runTask() { 357 | aws ecs run-task --cluster $CLUSTER --task-definition "$(getTaskArn)" --count $NUMBER_OF_INSTANCES 358 | } 359 | 360 | 361 | function checkPreconditions() { 362 | if [ -z "$(which jq 2>/dev/null)" ] ; then 363 | echo "ERROR: the executable jq was not found on the path." >&2 364 | exit 1 365 | fi 366 | 367 | if [ -z "$(which aws 2>/dev/null)" ] ; then 368 | echo "ERROR: the aws CLI was not found on the path." >&2 369 | exit 1 370 | fi 371 | 372 | if ! aws iam get-user > /dev/null ; then 373 | echo "ERROR: failed to connect to AWS." >&2 374 | exit 1 375 | fi 376 | 377 | if ! docker ps >/dev/null ; then 378 | echo "ERROR: failed to connect to docker deamon." >&2 379 | exit 1 380 | fi 381 | 382 | if [ -z "$IMAGE" ] ; then 383 | echo "ERROR: missing image to run." 2>&1 384 | exit 1 385 | fi 386 | 387 | if [ -z "$NAME" ] ; then 388 | NAME=$(echo "$IMAGE" | sed -e 's/[^\/]*\///g' -e 's/:.*$//') 389 | if [ -n "$NAME" ] ; then 390 | echo "WARN: using $NAME as name for this task definition." >&2 391 | else 392 | echo "ERROR: no name could be derived for this Docker task definition" >&2 393 | exit 1 394 | fi 395 | fi 396 | } 397 | 398 | parseDockerRunCmd "$@" 399 | checkPreconditions 400 | 401 | generateTaskDef 402 | if [ -n "$GENERATE_ONLY" ] ; then 403 | echo "$TASK_DEF" 404 | else 405 | registerTask 406 | if [ $RUN_AS_SERVICE -eq 1 ] ; then 407 | runService 408 | else 409 | runTask 410 | fi 411 | fi 412 | -------------------------------------------------------------------------------- /ecs-cloudformation/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Elastic Container Services CloudFormation quick start. 2 | 3 | This directory contains an Amazon ECS cloudformation template, which is a slightly modified version of the 4 | default ECS cluster that is created by the ECS first-run wizard. 5 | 6 | It creates a cluster with three machines and has a load balancer attached. 7 | 8 | to create the Cluster from the run time type the following commands: 9 | 10 | ``` bash 11 | aws ec2 import-key-pair \ 12 | --key-name ecs-$USER-key \ 13 | --public-key-material "$(ssh-keygen -y -f ~/.ssh/id_rsa)" 14 | 15 | aws cloudformation create-stack \ 16 | --stack-name ecs-$USER-cluster \ 17 | --template-body "$( /dev/null ; do 26 | sleep 10 27 | STATUS=$(aws cloudformation describe-stacks --stack-name ecs-$USER-cluster | jq -r '.Stacks[0].StackStatus') 28 | echo $STATUS 29 | done 30 | } 31 | 32 | waitOnCompletion 33 | 34 | aws ecs create-cluster --cluster-name ecs-$USER-cluster 35 | ``` 36 | 37 | Now the cluster is ready, you can start the infamous [paas-monitor](https://github.com/mvanholsteijn/paas-monitor) application! 38 | 39 | ``` bash 40 | ../bin/ecs-docker-run --run-as-service \ 41 | --number-of-instances 3 \ 42 | --cluster ecs-$USER-cluster \ 43 | -p :80:1337 \ 44 | mvanholsteijn/paas-monitor 45 | 46 | 47 | ELBNAME=$(aws cloudformation describe-stacks --stack-name ecs-$USER-cluster | \ 48 | jq -r '.Stacks[0].Outputs[] | select(.OutputKey =="EcsElbName") | .OutputValue') 49 | 50 | DNSNAME=$(aws elb describe-load-balancers --load-balancer-names $ELBNAME | \ 51 | jq -r .LoadBalancerDescriptions[].DNSName) 52 | 53 | # 54 | # Once the paas-monitor is running, you can actually access the application 55 | # 56 | open http://$DNSNAME/status 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /ecs-cloudformation/ecs.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS CloudFormation template to create a new VPC or use an existing VPC for ECS deployment", 4 | "Mappings": { 5 | "VpcCidrs": { 6 | "eu-central-1": { 7 | "vpc": "10.0.0.0/16", 8 | "pubsubnet1": "10.0.0.0/24", 9 | "pubsubnet2": "10.0.1.0/24" 10 | }, 11 | "sa-east-1": { 12 | "vpc": "10.0.0.0/16", 13 | "pubsubnet1": "10.0.0.0/24", 14 | "pubsubnet2": "10.0.1.0/24" 15 | }, 16 | "ap-northeast-1": { 17 | "vpc": "10.0.0.0/16", 18 | "pubsubnet1": "10.0.0.0/24", 19 | "pubsubnet2": "10.0.1.0/24" 20 | }, 21 | "eu-west-1": { 22 | "vpc": "10.0.0.0/16", 23 | "pubsubnet1": "10.0.0.0/24", 24 | "pubsubnet2": "10.0.1.0/24" 25 | }, 26 | "us-east-1": { 27 | "vpc": "10.0.0.0/16", 28 | "pubsubnet1": "10.0.0.0/24", 29 | "pubsubnet2": "10.0.1.0/24" 30 | }, 31 | "us-west-1": { 32 | "vpc": "10.0.0.0/16", 33 | "pubsubnet1": "10.0.0.0/24", 34 | "pubsubnet2": "10.0.1.0/24" 35 | }, 36 | "us-west-2": { 37 | "vpc": "10.0.0.0/16", 38 | "pubsubnet1": "10.0.0.0/24", 39 | "pubsubnet2": "10.0.1.0/24" 40 | }, 41 | "ap-southeast-2": { 42 | "vpc": "10.0.0.0/16", 43 | "pubsubnet1": "10.0.0.0/24", 44 | "pubsubnet2": "10.0.1.0/24" 45 | }, 46 | "ap-southeast-1": { 47 | "vpc": "10.0.0.0/16", 48 | "pubsubnet1": "10.0.0.0/24", 49 | "pubsubnet2": "10.0.1.0/24" 50 | } 51 | }, 52 | "EcsAmiIds": { 53 | "ap-northeast-1": { 54 | "id": "ami-3077525e" 55 | }, 56 | "ap-southeast-2": { 57 | "id": "ami-23b4eb40" 58 | }, 59 | "eu-west-1": { 60 | "id": "ami-f1b46b82" 61 | }, 62 | "us-east-1": { 63 | "id": "ami-ddc7b6b7" 64 | }, 65 | "us-west-1": { 66 | "id": "ami-a39df1c3" 67 | }, 68 | "us-west-2": { 69 | "id": "ami-d74357b6" 70 | } 71 | } 72 | }, 73 | "Parameters": { 74 | "EcsInstanceType": { 75 | "Type": "String", 76 | "Description": "ECS EC2 instance type", 77 | "Default": "t2.micro", 78 | "AllowedValues": [ 79 | "t2.micro", 80 | "t2.small", 81 | "t2.medium", 82 | "t2.large", 83 | "m4.large", 84 | "m4.xlarge", 85 | "m4.2xlarge", 86 | "m4.4xlarge", 87 | "m4.10xlarge", 88 | "m3.medium", 89 | "m3.large", 90 | "m3.xlarge", 91 | "m3.2xlarge", 92 | "c4.large", 93 | "c4.xlarge", 94 | "c4.2xlarge", 95 | "c4.4xlarge", 96 | "c4.8xlarge", 97 | "c3.large", 98 | "c3.xlarge", 99 | "c3.2xlarge", 100 | "c3.4xlarge", 101 | "c3.8xlarge", 102 | "r3.large", 103 | "r3.xlarge", 104 | "r3.2xlarge", 105 | "r3.4xlarge", 106 | "r3.8xlarge", 107 | "i2.xlarge", 108 | "i2.2xlarge", 109 | "i2.4xlarge", 110 | "i2.8xlarge" 111 | ], 112 | "ConstraintDescription": "must be a valid EC2 instance type." 113 | }, 114 | "KeyName": { 115 | "Type": "String", 116 | "Description": "Optional - Name of an existing EC2 KeyPair to enable SSH access to the ECS instances", 117 | "Default": "" 118 | }, 119 | "VpcId": { 120 | "Type": "String", 121 | "Description": "Optional - VPC Id of existing VPC. Leave blank to have a new VPC created", 122 | "Default": "", 123 | "AllowedPattern": "^(?:vpc-[0-9a-f]{8}|)$", 124 | "ConstraintDescription": "VPC Id must begin with 'vpc-' or leave blank to have a new VPC created" 125 | }, 126 | "SubnetIds": { 127 | "Type": "CommaDelimitedList", 128 | "Description": "Optional - Comma separated list of existing VPC Subnet Ids where ECS instances will run", 129 | "Default": "" 130 | }, 131 | "AsgDesiredSize": { 132 | "Type": "Number", 133 | "Description": "Initial Desired Capacity of ECS Auto Scaling Group", 134 | "Default": "6" 135 | }, 136 | "EcsClusterName": { 137 | "Type": "String", 138 | "Description": "ECS Cluster Name", 139 | "Default": "default" 140 | }, 141 | "EcsPort": { 142 | "Type": "String", 143 | "Description": "Optional - Security Group port to open on ECS instances - defaults to port 80", 144 | "Default": "80" 145 | }, 146 | "ElbPort": { 147 | "Type": "String", 148 | "Description": "Optional - Security Group port to open on ELB - port 80 will be open by default", 149 | "Default": "80" 150 | }, 151 | "ElbProtocol": { 152 | "Type": "String", 153 | "Description": "Optional - ELB Protocol - defaults to HTTP", 154 | "Default": "HTTP" 155 | }, 156 | "ElbHealthCheckTarget": { 157 | "Type": "String", 158 | "Description": "Optional - Health Check Target for ELB - defaults to HTTP:80/", 159 | "Default": "HTTP:80/" 160 | }, 161 | "SourceCidr": { 162 | "Type": "String", 163 | "Description": "Optional - CIDR/IP range for EcsPort and ElbPort - defaults to 0.0.0.0/0", 164 | "Default": "0.0.0.0/0" 165 | }, 166 | "EcsEndpoint": { 167 | "Type": "String", 168 | "Description": "Optional : ECS Endpoint for the ECS Agent to connect to", 169 | "Default": "" 170 | }, 171 | "CreateElasticLoadBalancer": { 172 | "Type": "String", 173 | "Description": "Optional : When set to true, creates a ELB for ECS Service", 174 | "Default": "true" 175 | }, 176 | "VpcAvailabilityZones": { 177 | "Type": "CommaDelimitedList", 178 | "Description": "Optional : Comma-delimited list of two VPC availability zones in which to create subnets", 179 | "Default": "" 180 | } 181 | }, 182 | "Conditions": { 183 | "CreateVpcResources": { 184 | "Fn::Equals": [ 185 | { 186 | "Ref": "VpcId" 187 | }, 188 | "" 189 | ] 190 | }, 191 | "ExistingVpcResources": { 192 | "Fn::Not": [ 193 | { 194 | "Fn::Equals": [ 195 | { 196 | "Ref": "VpcId" 197 | }, 198 | "" 199 | ] 200 | } 201 | ] 202 | }, 203 | "SetEndpointToECSAgent": { 204 | "Fn::Not": [ 205 | { 206 | "Fn::Equals": [ 207 | { 208 | "Ref": "EcsEndpoint" 209 | }, 210 | "" 211 | ] 212 | } 213 | ] 214 | }, 215 | "CreateELBForExistingVpc": { 216 | "Fn::And": [ 217 | { 218 | "Fn::Equals": [ 219 | { 220 | "Ref": "CreateElasticLoadBalancer" 221 | }, 222 | "true" 223 | ] 224 | }, 225 | { 226 | "Condition": "ExistingVpcResources" 227 | } 228 | ] 229 | }, 230 | "CreateELBForNewVpc": { 231 | "Fn::And": [ 232 | { 233 | "Fn::Equals": [ 234 | { 235 | "Ref": "CreateElasticLoadBalancer" 236 | }, 237 | "true" 238 | ] 239 | }, 240 | { 241 | "Condition": "CreateVpcResources" 242 | } 243 | ] 244 | }, 245 | "CreateELB": { 246 | "Fn::Or": [ 247 | { 248 | "Condition": "CreateELBForExistingVpc" 249 | }, 250 | { 251 | "Condition": "CreateELBForNewVpc" 252 | } 253 | ] 254 | }, 255 | "CreateEC2LCWithKeyPair": { 256 | "Fn::Not": [ 257 | { 258 | "Fn::Equals": [ 259 | { 260 | "Ref": "KeyName" 261 | }, 262 | "" 263 | ] 264 | } 265 | ] 266 | }, 267 | "CreateEC2LCWithoutKeyPair": { 268 | "Fn::Equals": [ 269 | { 270 | "Ref": "KeyName" 271 | }, 272 | "" 273 | ] 274 | }, 275 | "UseSpecifiedVpcAvailabilityZones": { 276 | "Fn::Not": [ 277 | { 278 | "Fn::Equals": [ 279 | { 280 | "Fn::Join": [ 281 | "", 282 | { 283 | "Ref": "VpcAvailabilityZones" 284 | } 285 | ] 286 | }, 287 | "" 288 | ] 289 | } 290 | ] 291 | } 292 | }, 293 | "Resources": { 294 | "Vpc": { 295 | "Condition": "CreateVpcResources", 296 | "Type": "AWS::EC2::VPC", 297 | "Properties": { 298 | "CidrBlock": { 299 | "Fn::FindInMap": [ 300 | "VpcCidrs", 301 | { 302 | "Ref": "AWS::Region" 303 | }, 304 | "vpc" 305 | ] 306 | }, 307 | "EnableDnsSupport": "true", 308 | "EnableDnsHostnames": "true" 309 | } 310 | }, 311 | "PubSubnetAz1": { 312 | "Condition": "CreateVpcResources", 313 | "Type": "AWS::EC2::Subnet", 314 | "Properties": { 315 | "VpcId": { 316 | "Ref": "Vpc" 317 | }, 318 | "CidrBlock": { 319 | "Fn::FindInMap": [ 320 | "VpcCidrs", 321 | { 322 | "Ref": "AWS::Region" 323 | }, 324 | "pubsubnet1" 325 | ] 326 | }, 327 | "AvailabilityZone": { 328 | "Fn::If": [ 329 | "UseSpecifiedVpcAvailabilityZones", 330 | { 331 | "Fn::Select": [ 332 | "0", 333 | { 334 | "Ref": "VpcAvailabilityZones" 335 | } 336 | ] 337 | }, 338 | { 339 | "Fn::Select": [ 340 | "0", 341 | { 342 | "Fn::GetAZs": { 343 | "Ref": "AWS::Region" 344 | } 345 | } 346 | ] 347 | } 348 | ] 349 | } 350 | } 351 | }, 352 | "PubSubnetAz2": { 353 | "Condition": "CreateVpcResources", 354 | "Type": "AWS::EC2::Subnet", 355 | "Properties": { 356 | "VpcId": { 357 | "Ref": "Vpc" 358 | }, 359 | "CidrBlock": { 360 | "Fn::FindInMap": [ 361 | "VpcCidrs", 362 | { 363 | "Ref": "AWS::Region" 364 | }, 365 | "pubsubnet2" 366 | ] 367 | }, 368 | "AvailabilityZone": { 369 | "Fn::If": [ 370 | "UseSpecifiedVpcAvailabilityZones", 371 | { 372 | "Fn::Select": [ 373 | "1", 374 | { 375 | "Ref": "VpcAvailabilityZones" 376 | } 377 | ] 378 | }, 379 | { 380 | "Fn::Select": [ 381 | "1", 382 | { 383 | "Fn::GetAZs": { 384 | "Ref": "AWS::Region" 385 | } 386 | } 387 | ] 388 | } 389 | ] 390 | } 391 | } 392 | }, 393 | "InternetGateway": { 394 | "Condition": "CreateVpcResources", 395 | "Type": "AWS::EC2::InternetGateway" 396 | }, 397 | "AttachGateway": { 398 | "Condition": "CreateVpcResources", 399 | "Type": "AWS::EC2::VPCGatewayAttachment", 400 | "Properties": { 401 | "VpcId": { 402 | "Ref": "Vpc" 403 | }, 404 | "InternetGatewayId": { 405 | "Ref": "InternetGateway" 406 | } 407 | } 408 | }, 409 | "RouteViaIgw": { 410 | "Condition": "CreateVpcResources", 411 | "Type": "AWS::EC2::RouteTable", 412 | "Properties": { 413 | "VpcId": { 414 | "Ref": "Vpc" 415 | } 416 | } 417 | }, 418 | "PublicRouteViaIgw": { 419 | "Condition": "CreateVpcResources", 420 | "Type": "AWS::EC2::Route", 421 | "Properties": { 422 | "RouteTableId": { 423 | "Ref": "RouteViaIgw" 424 | }, 425 | "DestinationCidrBlock": "0.0.0.0/0", 426 | "GatewayId": { 427 | "Ref": "InternetGateway" 428 | } 429 | } 430 | }, 431 | "PubSubnet1RouteTableAssociation": { 432 | "Condition": "CreateVpcResources", 433 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 434 | "Properties": { 435 | "SubnetId": { 436 | "Ref": "PubSubnetAz1" 437 | }, 438 | "RouteTableId": { 439 | "Ref": "RouteViaIgw" 440 | } 441 | } 442 | }, 443 | "PubSubnet2RouteTableAssociation": { 444 | "Condition": "CreateVpcResources", 445 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 446 | "Properties": { 447 | "SubnetId": { 448 | "Ref": "PubSubnetAz2" 449 | }, 450 | "RouteTableId": { 451 | "Ref": "RouteViaIgw" 452 | } 453 | } 454 | }, 455 | "ElbSecurityGroup": { 456 | "Type": "AWS::EC2::SecurityGroup", 457 | "Properties": { 458 | "GroupDescription": "ELB Allowed Ports", 459 | "VpcId": { 460 | "Fn::If": [ 461 | "CreateVpcResources", 462 | { 463 | "Ref": "Vpc" 464 | }, 465 | { 466 | "Ref": "VpcId" 467 | } 468 | ] 469 | }, 470 | "SecurityGroupIngress": [ 471 | { 472 | "IpProtocol": "tcp", 473 | "FromPort": { 474 | "Ref": "ElbPort" 475 | }, 476 | "ToPort": { 477 | "Ref": "ElbPort" 478 | }, 479 | "CidrIp": { 480 | "Ref": "SourceCidr" 481 | } 482 | } 483 | ] 484 | } 485 | }, 486 | "EcsSecurityGroup": { 487 | "Type": "AWS::EC2::SecurityGroup", 488 | "Properties": { 489 | "GroupDescription": "ECS Allowed Ports", 490 | "VpcId": { 491 | "Fn::If": [ 492 | "CreateVpcResources", 493 | { 494 | "Ref": "Vpc" 495 | }, 496 | { 497 | "Ref": "VpcId" 498 | } 499 | ] 500 | }, 501 | "SecurityGroupIngress": { 502 | "Fn::If": [ 503 | "CreateELB", 504 | [ 505 | { 506 | "IpProtocol": "tcp", 507 | "FromPort": { 508 | "Ref": "EcsPort" 509 | }, 510 | "ToPort": { 511 | "Ref": "EcsPort" 512 | }, 513 | "CidrIp": { 514 | "Ref": "SourceCidr" 515 | } 516 | }, 517 | { 518 | "IpProtocol": "tcp", 519 | "FromPort": "1", 520 | "ToPort": "65535", 521 | "SourceSecurityGroupId": { 522 | "Ref": "ElbSecurityGroup" 523 | } 524 | } 525 | ], 526 | [ 527 | { 528 | "IpProtocol": "tcp", 529 | "FromPort": { 530 | "Ref": "EcsPort" 531 | }, 532 | "ToPort": { 533 | "Ref": "EcsPort" 534 | }, 535 | "CidrIp": { 536 | "Ref": "SourceCidr" 537 | } 538 | } 539 | ] 540 | ] 541 | } 542 | } 543 | }, 544 | "EcsElasticLoadBalancer": { 545 | "Condition": "CreateELBForNewVpc", 546 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 547 | "Properties": { 548 | "SecurityGroups": [ 549 | { 550 | "Ref": "ElbSecurityGroup" 551 | } 552 | ], 553 | "Subnets": [ 554 | { 555 | "Ref": "PubSubnetAz1" 556 | }, 557 | { 558 | "Ref": "PubSubnetAz2" 559 | } 560 | ], 561 | "CrossZone": "true", 562 | "Listeners": [ 563 | { 564 | "LoadBalancerPort": { 565 | "Ref": "ElbPort" 566 | }, 567 | "InstancePort": { 568 | "Ref": "EcsPort" 569 | }, 570 | "Protocol": { 571 | "Ref": "ElbProtocol" 572 | } 573 | } 574 | ], 575 | "HealthCheck": { 576 | "Target": { 577 | "Ref": "ElbHealthCheckTarget" 578 | }, 579 | "HealthyThreshold": "2", 580 | "UnhealthyThreshold": "10", 581 | "Interval": "30", 582 | "Timeout": "5" 583 | } 584 | } 585 | }, 586 | "EcsElasticLoadBalancerExistingVpc": { 587 | "Condition": "CreateELBForExistingVpc", 588 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 589 | "Properties": { 590 | "SecurityGroups": [ 591 | { 592 | "Ref": "ElbSecurityGroup" 593 | } 594 | ], 595 | "Subnets": { 596 | "Ref": "SubnetIds" 597 | }, 598 | "CrossZone": "true", 599 | "Listeners": [ 600 | { 601 | "LoadBalancerPort": { 602 | "Ref": "ElbPort" 603 | }, 604 | "InstancePort": { 605 | "Ref": "EcsPort" 606 | }, 607 | "Protocol": { 608 | "Ref": "ElbProtocol" 609 | } 610 | } 611 | ], 612 | "HealthCheck": { 613 | "Target": { 614 | "Ref": "ElbHealthCheckTarget" 615 | }, 616 | "HealthyThreshold": "2", 617 | "UnhealthyThreshold": "10", 618 | "Interval": "30", 619 | "Timeout": "5" 620 | } 621 | } 622 | }, 623 | "EcsInstanceLc": { 624 | "Condition": "CreateEC2LCWithKeyPair", 625 | "Type": "AWS::AutoScaling::LaunchConfiguration", 626 | "Properties": { 627 | "ImageId": { 628 | "Fn::FindInMap": [ 629 | "EcsAmiIds", 630 | { 631 | "Ref": "AWS::Region" 632 | }, 633 | "id" 634 | ] 635 | }, 636 | "InstanceType": { 637 | "Ref": "EcsInstanceType" 638 | }, 639 | "AssociatePublicIpAddress": true, 640 | "IamInstanceProfile": { 641 | "Ref": "EcsInstanceProfile" 642 | }, 643 | "KeyName": { 644 | "Ref": "KeyName" 645 | }, 646 | "SecurityGroups": [ 647 | { 648 | "Ref": "EcsSecurityGroup" 649 | } 650 | ], 651 | "UserData": { 652 | "Fn::If": [ 653 | "SetEndpointToECSAgent", 654 | { 655 | "Fn::Base64": { 656 | "Fn::Join": [ 657 | "", 658 | [ 659 | "#!/bin/bash\n", 660 | "echo ECS_CLUSTER=", 661 | { 662 | "Ref": "EcsClusterName" 663 | }, 664 | " >> /etc/ecs/ecs.config", 665 | "\necho ECS_BACKEND_HOST=", 666 | { 667 | "Ref": "EcsEndpoint" 668 | }, 669 | " >> /etc/ecs/ecs.config" 670 | ] 671 | ] 672 | } 673 | }, 674 | { 675 | "Fn::Base64": { 676 | "Fn::Join": [ 677 | "", 678 | [ 679 | "#!/bin/bash\n", 680 | "echo ECS_CLUSTER=", 681 | { 682 | "Ref": "EcsClusterName" 683 | }, 684 | " >> /etc/ecs/ecs.config" 685 | ] 686 | ] 687 | } 688 | } 689 | ] 690 | } 691 | } 692 | }, 693 | "EcsInstanceLcWithoutKeyPair": { 694 | "Condition": "CreateEC2LCWithoutKeyPair", 695 | "Type": "AWS::AutoScaling::LaunchConfiguration", 696 | "Properties": { 697 | "ImageId": { 698 | "Fn::FindInMap": [ 699 | "EcsAmiIds", 700 | { 701 | "Ref": "AWS::Region" 702 | }, 703 | "id" 704 | ] 705 | }, 706 | "InstanceType": { 707 | "Ref": "EcsInstanceType" 708 | }, 709 | "AssociatePublicIpAddress": true, 710 | "IamInstancProfile": { 711 | "Ref": "EcsInstanceProfile" 712 | }, 713 | "SecurityGroups": [ 714 | { 715 | "Ref": "EcsSecurityGroup" 716 | } 717 | ], 718 | "UserData": { 719 | "Fn::If": [ 720 | "SetEndpointToECSAgent", 721 | { 722 | "Fn::Base64": { 723 | "Fn::Join": [ 724 | "", 725 | [ 726 | "#!/bin/bash\n", 727 | "echo ECS_CLUSTER=", 728 | { 729 | "Ref": "EcsClusterName" 730 | }, 731 | " >> /etc/ecs/ecs.config", 732 | "\necho ECS_BACKEND_HOST=", 733 | { 734 | "Ref": "EcsEndpoint" 735 | }, 736 | " >> /etc/ecs/ecs.config" 737 | ] 738 | ] 739 | } 740 | }, 741 | { 742 | "Fn::Base64": { 743 | "Fn::Join": [ 744 | "", 745 | [ 746 | "#!/bin/bash\n", 747 | "echo ECS_CLUSTER=", 748 | { 749 | "Ref": "EcsClusterName" 750 | }, 751 | " >> /etc/ecs/ecs.config" 752 | ] 753 | ] 754 | } 755 | } 756 | ] 757 | } 758 | } 759 | }, 760 | "EcsInstanceAsg": { 761 | "Type": "AWS::AutoScaling::AutoScalingGroup", 762 | "Properties": { 763 | "LoadBalancerNames" : 764 | { 765 | "Fn::If": [ 766 | "CreateELBForNewVpc", 767 | [ { 768 | "Ref": "EcsElasticLoadBalancer" 769 | } ], 770 | [ { 771 | "Ref": "EcsElasticLoadBalancerExistingVpc" 772 | } ] 773 | ] 774 | }, 775 | "AvailabilityZones": { 776 | "Fn::If": [ 777 | "UseSpecifiedVpcAvailabilityZones", 778 | [ 779 | { 780 | "Fn::Select": [ 781 | "0", 782 | { 783 | "Ref": "VpcAvailabilityZones" 784 | } 785 | ] 786 | }, 787 | { 788 | "Fn::Select": [ 789 | "1", 790 | { 791 | "Ref": "VpcAvailabilityZones" 792 | } 793 | ] 794 | } 795 | ], 796 | [ 797 | { 798 | "Fn::Select": [ 799 | "0", 800 | { 801 | "Fn::GetAZs": { 802 | "Ref": "AWS::Region" 803 | } 804 | } 805 | ] 806 | }, 807 | { 808 | "Fn::Select": [ 809 | "1", 810 | { 811 | "Fn::GetAZs": { 812 | "Ref": "AWS::Region" 813 | } 814 | } 815 | ] 816 | } 817 | ] 818 | ] 819 | }, 820 | "VPCZoneIdentifier": { 821 | "Fn::If": [ 822 | "CreateVpcResources", 823 | [ 824 | { 825 | "Fn::Join": [ 826 | ",", 827 | [ 828 | { 829 | "Ref": "PubSubnetAz1" 830 | }, 831 | { 832 | "Ref": "PubSubnetAz2" 833 | } 834 | ] 835 | ] 836 | } 837 | ], 838 | { 839 | "Ref": "SubnetIds" 840 | } 841 | ] 842 | }, 843 | "LaunchConfigurationName": { 844 | "Fn::If": [ 845 | "CreateEC2LCWithKeyPair", 846 | { 847 | "Ref": "EcsInstanceLc" 848 | }, 849 | { 850 | "Ref": "EcsInstanceLcWithoutKeyPair" 851 | } 852 | ] 853 | }, 854 | "MinSize": "1", 855 | "MaxSize": "10", 856 | "DesiredCapacity": { 857 | "Ref": "AsgDesiredSize" 858 | }, 859 | "Tags": [ 860 | { 861 | "Key": "Name", 862 | "Value": { 863 | "Fn::Join": [ 864 | "", 865 | [ 866 | "ECS Instance - ", 867 | { 868 | "Ref": "AWS::StackName" 869 | } 870 | ] 871 | ] 872 | }, 873 | "PropagateAtLaunch": "true" 874 | } 875 | ] 876 | } 877 | }, 878 | "EcsInstanceProfile": { 879 | "Type": "AWS::IAM::InstanceProfile", 880 | "Properties": { 881 | "Path": "/", 882 | "Roles": [ 883 | { 884 | "Ref": "EcsInstanceRole" 885 | } 886 | ] 887 | } 888 | }, 889 | "EcsInstanceRole": { 890 | "Type": "AWS::IAM::Role", 891 | "Properties": { 892 | "AssumeRolePolicyDocument": { 893 | "Version": "2008-10-17", 894 | "Statement": [ 895 | { 896 | "Action": "sts:AssumeRole", 897 | "Principal": { 898 | "Service": "ec2.amazonaws.com" 899 | }, 900 | "Effect": "Allow", 901 | "Sid": "" 902 | } 903 | ] 904 | }, 905 | "Path": "/", 906 | "Policies": [ 907 | { 908 | "PolicyName": "EcsInstance", 909 | "PolicyDocument": { 910 | "Version": "2012-10-17", 911 | "Statement": [ 912 | { 913 | "Effect": "Allow", 914 | "Action": [ 915 | "ecs:CreateCluster", 916 | "ecs:DeregisterContainerInstance", 917 | "ecs:DiscoverPollEndpoint", 918 | "ecs:Poll", 919 | "ecs:RegisterContainerInstance", 920 | "ecs:StartTelemetrySession", 921 | "ecs:Submit*" 922 | ], 923 | "Resource": [ 924 | "*" 925 | ] 926 | }, 927 | { 928 | "Effect": "Allow", 929 | "Action": [ 930 | "logs:CreateLogGroup", 931 | "logs:CreateLogStream", 932 | "logs:PutLogEvents", 933 | "logs:DescribeLogStreams" 934 | ], 935 | "Resource": [ 936 | "arn:aws:logs:*:*:*" 937 | ] 938 | } 939 | ] 940 | } 941 | } 942 | ] 943 | } 944 | } 945 | }, 946 | "Outputs": { 947 | "EcsInstanceAsgName": { 948 | "Description": "Auto Scaling Group Name for ECS Instances", 949 | "Value": { 950 | "Ref": "EcsInstanceAsg" 951 | } 952 | }, 953 | "EcsElbName": { 954 | "Description": "Load Balancer for ECS Service", 955 | "Value": { 956 | "Fn::If": [ 957 | "CreateELB", 958 | { 959 | "Fn::If": [ 960 | "CreateELBForNewVpc", 961 | { 962 | "Ref": "EcsElasticLoadBalancer" 963 | }, 964 | { 965 | "Ref": "EcsElasticLoadBalancerExistingVpc" 966 | } 967 | ] 968 | }, 969 | "" 970 | ] 971 | } 972 | } 973 | } 974 | } 975 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | output/** 2 | -------------------------------------------------------------------------------- /test/expect/test-all-options-with-equal-sign.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "app-hellodb", 3 | "containerDefinitions": [ 4 | { 5 | "volumesFrom": [ 6 | "container-a", 7 | "container-b" 8 | ], 9 | "portMappings": [ 10 | { 11 | "hostPort": 1000, 12 | "containerPort": 100 13 | }, 14 | { 15 | "hostPort": 1001, 16 | "containerPort": 101, 17 | "protocol": "udp" 18 | }, 19 | { 20 | "hostPort": 1002, 21 | "containerPort": 102 22 | } 23 | ], 24 | "command": [ 25 | "/srv/helloworld-db.py" 26 | ], 27 | "environment": [ 28 | { 29 | "name": "SERVICE_NAME", 30 | "value": "hellodb" 31 | }, 32 | { 33 | "name": "SERVICE_TAGS", 34 | "value": "http" 35 | }, 36 | { 37 | "name": "MESSAGE", 38 | "value": "Hello World" 39 | } 40 | ], 41 | "links": [ 42 | "a:a", 43 | "b:b" 44 | ], 45 | "mountPoints": [ 46 | { 47 | "sourceVolume": "host-path", 48 | "containerPath": "/container/path", 49 | "readOnly": false 50 | } 51 | ], 52 | "entrypoint": [ 53 | "new-entry-point" 54 | ], 55 | "essential": true, 56 | "memory": 256, 57 | "name": "app-hellodb", 58 | "cpu": 512, 59 | "image": "cargonauts/helloworld-python" 60 | } 61 | ], 62 | "volumes": [ 63 | { 64 | "name": "host-path", 65 | "host": { 66 | "sourcePath": "/host/path" 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /test/expect/test-all-options.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "app-hellodb", 3 | "containerDefinitions": [ 4 | { 5 | "volumesFrom": [ 6 | "container-a", 7 | "container-b" 8 | ], 9 | "portMappings": [ 10 | { 11 | "hostPort": 1000, 12 | "containerPort": 100 13 | }, 14 | { 15 | "hostPort": 1001, 16 | "containerPort": 101, 17 | "protocol": "udp" 18 | }, 19 | { 20 | "hostPort": 1002, 21 | "containerPort": 102 22 | } 23 | ], 24 | "command": [ 25 | "/srv/helloworld-db.py" 26 | ], 27 | "environment": [ 28 | { 29 | "name": "SERVICE_NAME", 30 | "value": "hellodb" 31 | }, 32 | { 33 | "name": "SERVICE_TAGS", 34 | "value": "http" 35 | }, 36 | { 37 | "name": "MESSAGE", 38 | "value": "Hello World" 39 | } 40 | ], 41 | "links": [ 42 | "a:a", 43 | "b:b" 44 | ], 45 | "mountPoints": [ 46 | { 47 | "sourceVolume": "host-path", 48 | "containerPath": "/container/path", 49 | "readOnly": false 50 | } 51 | ], 52 | "entrypoint": [ 53 | "new-entry-point" 54 | ], 55 | "essential": true, 56 | "memory": 256, 57 | "name": "app-hellodb", 58 | "cpu": 512, 59 | "image": "cargonauts/helloworld-python" 60 | } 61 | ], 62 | "volumes": [ 63 | { 64 | "name": "host-path", 65 | "host": { 66 | "sourcePath": "/host/path" 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /test/expect/test-all-short-options.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "app-hellodb", 3 | "containerDefinitions": [ 4 | { 5 | "volumesFrom": [ 6 | "container-a" 7 | ], 8 | "portMappings": [ 9 | { 10 | "hostPort": 1000, 11 | "containerPort": 100 12 | } 13 | ], 14 | "command": [ 15 | "/srv/helloworld-db.py" 16 | ], 17 | "environment": [ 18 | { 19 | "name": "SERVICE_NAME", 20 | "value": "hellodb" 21 | } 22 | ], 23 | "links": [ 24 | "a:a" 25 | ], 26 | "mountPoints": [ 27 | { 28 | "sourceVolume": "host-path", 29 | "containerPath": "/container/path", 30 | "readOnly": false 31 | } 32 | ], 33 | "entrypoint": [ 34 | "new-entry-point" 35 | ], 36 | "essential": true, 37 | "memory": 256, 38 | "name": "app-hellodb", 39 | "cpu": 512, 40 | "image": "cargonauts/helloworld-python" 41 | } 42 | ], 43 | "volumes": [ 44 | { 45 | "name": "host-path", 46 | "host": { 47 | "sourcePath": "/host/path" 48 | } 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /test/expect/test-expose-all-ports.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "oraxe", 3 | "containerDefinitions": [ 4 | { 5 | "volumesFrom": [], 6 | "portMappings": [ 7 | { 8 | "hostPort": 0, 9 | "containerPort": 1521 10 | }, 11 | { 12 | "hostPort": 0, 13 | "containerPort": 22 14 | }, 15 | { 16 | "hostPort": 0, 17 | "containerPort": 8080 18 | } 19 | ], 20 | "command": [], 21 | "environment": [], 22 | "links": [], 23 | "mountPoints": [], 24 | "essential": true, 25 | "memory": 256, 26 | "name": "oraxe", 27 | "cpu": 100, 28 | "image": "oraxe" 29 | } 30 | ], 31 | "volumes": [] 32 | } 33 | -------------------------------------------------------------------------------- /test/run-tests: -------------------------------------------------------------------------------- 1 | for test in test*; do 2 | ./$test > /dev/null 2>&1 3 | diff {output,expect}/$test.json 4 | [ $? -ne 0 ] && echo ERROR: test $test failed || echo INFO: test $test success 5 | done 6 | -------------------------------------------------------------------------------- /test/test-all-options: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../bin/ecs-docker-run \ 3 | --generate-only \ 4 | --cluster my-own-cluster \ 5 | --number-of-instances 3 \ 6 | --memory 256 \ 7 | --cpu-shares 512 \ 8 | --entrypoint new-entry-point \ 9 | --rm \ 10 | --name app-hellodb \ 11 | --env SERVICE_NAME=hellodb \ 12 | --env SERVICE_TAGS=http \ 13 | --env "MESSAGE=Hello World" \ 14 | --link a:a \ 15 | --link b:b \ 16 | -v /host/path:/container/path \ 17 | --volumes-from container-a \ 18 | --volumes-from container-b \ 19 | -p :1000:100 \ 20 | -p :1001:101/udp \ 21 | -p :1002:102/tcp \ 22 | cargonauts/helloworld-python \ 23 | /srv/helloworld-db.py \ 24 | | jq . > output/$(basename $0).json 25 | 26 | 27 | diff {output,expect}/$(basename $0).json 28 | [ $? -ne 0 ] && echo ERROR: test $(basename $0) failed || echo INFO: test $(basename $0) succeeded 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/test-all-options-with-equal-sign: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bash ../bin/ecs-docker-run \ 3 | --generate-only \ 4 | --cluster my-own-cluster \ 5 | --number-of-instances 3 \ 6 | --memory=256 \ 7 | --cpu-shares=512 \ 8 | --entrypoint=new-entry-point \ 9 | --rm \ 10 | --name=app-hellodb \ 11 | --env=SERVICE_NAME=hellodb \ 12 | --env=SERVICE_TAGS=http \ 13 | --env="MESSAGE=Hello World" \ 14 | --link=a:a \ 15 | --link=b:b \ 16 | -v /host/path:/container/path \ 17 | --volumes-from=container-a \ 18 | --volumes-from=container-b \ 19 | -p=:1000:100 \ 20 | -p=:1001:101/udp \ 21 | -p=:1002:102/tcp \ 22 | cargonauts/helloworld-python \ 23 | /srv/helloworld-db.py \ 24 | | jq . > output/$(basename $0).json 25 | 26 | diff {output,expect}/$(basename $0).json 27 | [ $? -ne 0 ] && echo ERROR: test $(basename $0) failed || echo INFO: test $(basename $0) succeeded 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/test-all-short-options: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bash ../bin/ecs-docker-run \ 3 | --generate-only \ 4 | --cluster my-own-cluster \ 5 | --number-of-instances 3 \ 6 | -m=256 \ 7 | -c=512 \ 8 | --entrypoint=new-entry-point \ 9 | --rm \ 10 | --name=app-hellodb \ 11 | --env=SERVICE_NAME=hellodb \ 12 | --link=a:a \ 13 | -v /host/path:/container/path \ 14 | --volumes-from=container-a \ 15 | -p=:1000:100 \ 16 | cargonauts/helloworld-python \ 17 | /srv/helloworld-db.py \ 18 | | jq . > output/$(basename $0).json 19 | 20 | diff {output,expect}/$(basename $0).json 21 | [ $? -ne 0 ] && echo ERROR: test $(basename $0) failed || echo INFO: test $(basename $0) succeeded 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/test-expose-all-ports: -------------------------------------------------------------------------------- 1 | ../bin/ecs-docker-run \ 2 | --generate-only \ 3 | -P \ 4 | oraxe \ 5 | | jq . > output/$(basename $0).json 6 | 7 | diff {output,expect}/$(basename $0).json 8 | [ $? -ne 0 ] && echo ERROR: test $(basename $0) failed || echo INFO: test $(basename $0) succeeded 9 | 10 | 11 | --------------------------------------------------------------------------------