├── .gitignore ├── LICENSE ├── README.md ├── bin └── p42 ├── doc └── cf-template.json ├── package.json ├── share ├── aws │ ├── cf │ │ └── vpc.yaml │ ├── dns │ │ ├── a.yaml │ │ ├── alias.yaml │ │ └── srv.yaml │ └── ecr │ │ └── policy.yaml ├── commands.yaml ├── interviews │ └── init.yaml ├── messages.yaml ├── mixins │ └── coffee │ │ ├── interview.yaml │ │ └── template │ │ ├── Dockerfile.tmpl │ │ └── config.yaml ├── options.yaml └── words.yaml ├── src ├── application.coffee ├── cli.coffee ├── cluster.coffee ├── commands │ ├── build.coffee │ ├── cluster.coffee │ ├── help.coffee │ ├── index.coffee │ ├── init.coffee │ ├── mixin.coffee │ ├── run.coffee │ ├── start.coffee │ ├── stop.coffee │ └── target.coffee ├── decorators.coffee ├── helpers │ ├── aws.coffee │ ├── dns.coffee │ └── docker.coffee ├── interview.coffee ├── logger.coffee ├── name.coffee ├── options.coffee ├── raise.coffee ├── run.coffee ├── serialize.coffee ├── sh.coffee ├── shared.coffee ├── template.coffee └── tmp.coffee └── test ├── aws-helpers.coffee ├── cli-helpers.coffee ├── data ├── app │ ├── p42.yaml │ └── run │ │ └── api │ │ ├── Dockerfile │ │ └── config.yaml ├── clusters │ └── violent-aftermath.yaml └── expectations.yaml ├── dns-helpers.coffee ├── docker-helpers.coffee ├── foundation.coffee ├── helpers.coffee ├── index.coffee └── test-sh.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | log 3 | *.log 4 | .DS_Store 5 | lib 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Panda Strike 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # p42 2 | 3 | > **IMPORTANT** This project is no longer under active development. 4 | > Based on what we've learned building this, 5 | > we recommend looking at [Convox][] instead. 6 | 7 | [Convox]:https://github.com/convox/rack 8 | 9 | A CLI for simplifying the use of AWS with Docker Swarm. 10 | 11 | ## Getting Started 12 | 13 | ### Prerequites 14 | 15 | - Bash version 3 or later 16 | - Docker version 1.10 17 | - Docker Machine version 0.6 release candidate (see below) 18 | - AWS CLI version 1.10.8 19 | - Node version 4 or later 20 | - NPM version 2 or later 21 | - `yaml` (via `npm install yaml -g`) version 1 or later 22 | 23 | To install the Docker Machine release candidate, run the following from the shell: 24 | 25 | ```sh 26 | curl -L https://github.com/docker/machine/releases/download/v0.6.0-rc4/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine && \\ 27 | chmod +x /usr/local/bin/docker-machine 28 | ``` 29 | 30 | ### Installation 31 | 32 | ``` 33 | $ npm install -g p42 34 | ``` 35 | 36 | ### Creating A Cluster 37 | 38 | ``` 39 | $ p42 cluster create 40 | Creating VPC [red-ghost]... 41 | ``` 42 | 43 | #### Add Nodes To The Cluster 44 | 45 | To add 3 nodes to a cluster: 46 | 47 | ``` 48 | $ p42 cluster add red-ghost -n 3 49 | ``` 50 | 51 | To add just one: 52 | 53 | ``` 54 | $ p42 cluster add red-ghost 55 | ``` 56 | 57 | #### Using Docker Commands 58 | 59 | If you want to use Docker commands directly: 60 | 61 | ``` 62 | $ eval $(p42 cluster env red-ghost) 63 | ``` 64 | 65 | which will select the Swarm master, if possible, or the default machine otherwise. 66 | 67 | #### Examining Your Cluster 68 | 69 | ``` 70 | p42 cluster ls red-ghost 71 | ``` 72 | 73 | ### Running An App 74 | 75 | #### Initialize Your App 76 | 77 | ``` 78 | $ p42 init 79 | Application name [blurb9]: 80 | Organization repository [pandastrike]: 81 | ``` 82 | 83 | #### Add Mixins 84 | 85 | Provide the git cloneable URL for the mixin repo: 86 | 87 | ``` 88 | $ p42 mixin add git@github.com:pandastrike/p42-mixin-nginx.git 89 | Document root [www]: 90 | ``` 91 | 92 | #### Add Target 93 | 94 | Add the cluster as a target for your app. 95 | 96 | ```tty 97 | $ p42 target add master red-ghost 98 | ``` 99 | 100 | #### Run Your App 101 | 102 | The `run` command will build and run all the images described in your `launch` directory. 103 | 104 | ``` 105 | $ p42 run 106 | ``` 107 | 108 | ## Example 109 | 110 | Let's build a simple Web page and deploy it using `p42`. 111 | 112 | We'll assume we've already run a cluster (see [Creating A Cluster](#creating-a-cluster)). 113 | 114 | Let's create an application directory and initialize it. 115 | 116 | ``` 117 | $ mkdir hello-world 118 | $ cd hello-world 119 | $ p42 init 120 | Application name [hello-world]: 121 | Organization repository []: pandastrike 122 | ``` 123 | 124 | Add the Nginx mixin. 125 | 126 | ``` 127 | $ p42 mixin add git@github.com:pandastrike/p42-mixin-nginx.git 128 | Document root [www]: 129 | ``` 130 | 131 | This will create a `launch/www` directory that includes a `Dockerfile` for running Nginx. 132 | 133 | Create an index HTML file. 134 | 135 | ``` 136 | $ mkdir www 137 | $ cat >> www/index.html 138 |

Hello, World!

139 | ``` 140 | 141 | Run your application. 142 | 143 | ``` 144 | $ p42 run 145 | ``` 146 | 147 | This will take a minute to build and run the image described by `launch/www/Dockerfile`. 148 | 149 | Get the IP and port of your Nginx container. 150 | 151 | ``` 152 | $ p42 ps 153 | swarm-01/hello-world-www 159.203.247.225:32769->80/tcp, 159.203.247.225:32768->443/tcp 154 | $ curl 159.203.247.225:32769 155 |

Hello, World!

156 | ``` 157 | 158 | ## Autocomplete 159 | 160 | You can add autocomplete to your shell by running: 161 | 162 | ``` 163 | $ eval $(p42 env -) 164 | ``` 165 | 166 | ## Status 167 | 168 | `p42` is under heavy development. 169 | 170 | ## Reference 171 | 172 | Run `p42 help` to get a list of commands and what they do. 173 | -------------------------------------------------------------------------------- /bin/p42: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | CLI = require "../src/cli" 4 | 5 | CLI process.argv[2..] 6 | -------------------------------------------------------------------------------- /doc/cf-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS CloudFormation Template for use with p42.", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Name of an existing EC2 KeyPair to enable SSH access to the bastion host", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "ConstraintDescription": "must be the name of an existing EC2 KeyPair." 9 | }, 10 | "SSHLocation": { 11 | "Description": "Lockdown SSH access to the bastion host (default can be accessed from anywhere)", 12 | "Type": "String", 13 | "MinLength": "9", 14 | "MaxLength": "18", 15 | "Default": "0.0.0.0/0", 16 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 17 | "ConstraintDescription": "must be a valid CIDR range of the form x.x.x.x/x." 18 | }, 19 | "EC2InstanceType": { 20 | "Description": "EC2 instance type", 21 | "Type": "String", 22 | "Default": "t2.small", 23 | "AllowedValues": [ 24 | "t1.micro", 25 | "t2.nano", 26 | "t2.micro", 27 | "t2.small", 28 | "t2.medium", 29 | "t2.large", 30 | "m1.small", 31 | "m1.medium", 32 | "m1.large", 33 | "m1.xlarge", 34 | "m2.xlarge", 35 | "m2.2xlarge", 36 | "m2.4xlarge", 37 | "m3.medium", 38 | "m3.large", 39 | "m3.xlarge", 40 | "m3.2xlarge", 41 | "m4.large", 42 | "m4.xlarge", 43 | "m4.2xlarge", 44 | "m4.4xlarge", 45 | "m4.10xlarge", 46 | "c1.medium", 47 | "c1.xlarge", 48 | "c3.large", 49 | "c3.xlarge", 50 | "c3.2xlarge", 51 | "c3.4xlarge", 52 | "c3.8xlarge", 53 | "c4.large", 54 | "c4.xlarge", 55 | "c4.2xlarge", 56 | "c4.4xlarge", 57 | "c4.8xlarge", 58 | "g2.2xlarge", 59 | "g2.8xlarge", 60 | "r3.large", 61 | "r3.xlarge", 62 | "r3.2xlarge", 63 | "r3.4xlarge", 64 | "r3.8xlarge", 65 | "i2.xlarge", 66 | "i2.2xlarge", 67 | "i2.4xlarge", 68 | "i2.8xlarge", 69 | "d2.xlarge", 70 | "d2.2xlarge", 71 | "d2.4xlarge", 72 | "d2.8xlarge", 73 | "hi1.4xlarge", 74 | "hs1.8xlarge", 75 | "cr1.8xlarge", 76 | "cc2.8xlarge", 77 | "cg1.4xlarge" 78 | ], 79 | "ConstraintDescription": "must be a valid EC2 instance type." 80 | } 81 | }, 82 | "Mappings": { 83 | "SubnetConfig": { 84 | "VPC": { 85 | "CIDR": "10.0.0.0/16" 86 | }, 87 | "Public": { 88 | "CIDR": "10.0.0.0/24" 89 | } 90 | }, 91 | "AWSInstanceType2Arch": { 92 | "t1.micro": { 93 | "Arch": "PV64" 94 | }, 95 | "t2.nano": { 96 | "Arch": "HVM64" 97 | }, 98 | "t2.micro": { 99 | "Arch": "HVM64" 100 | }, 101 | "t2.small": { 102 | "Arch": "HVM64" 103 | }, 104 | "t2.medium": { 105 | "Arch": "HVM64" 106 | }, 107 | "t2.large": { 108 | "Arch": "HVM64" 109 | }, 110 | "m1.small": { 111 | "Arch": "PV64" 112 | }, 113 | "m1.medium": { 114 | "Arch": "PV64" 115 | }, 116 | "m1.large": { 117 | "Arch": "PV64" 118 | }, 119 | "m1.xlarge": { 120 | "Arch": "PV64" 121 | }, 122 | "m2.xlarge": { 123 | "Arch": "PV64" 124 | }, 125 | "m2.2xlarge": { 126 | "Arch": "PV64" 127 | }, 128 | "m2.4xlarge": { 129 | "Arch": "PV64" 130 | }, 131 | "m3.medium": { 132 | "Arch": "HVM64" 133 | }, 134 | "m3.large": { 135 | "Arch": "HVM64" 136 | }, 137 | "m3.xlarge": { 138 | "Arch": "HVM64" 139 | }, 140 | "m3.2xlarge": { 141 | "Arch": "HVM64" 142 | }, 143 | "m4.large": { 144 | "Arch": "HVM64" 145 | }, 146 | "m4.xlarge": { 147 | "Arch": "HVM64" 148 | }, 149 | "m4.2xlarge": { 150 | "Arch": "HVM64" 151 | }, 152 | "m4.4xlarge": { 153 | "Arch": "HVM64" 154 | }, 155 | "m4.10xlarge": { 156 | "Arch": "HVM64" 157 | }, 158 | "c1.medium": { 159 | "Arch": "PV64" 160 | }, 161 | "c1.xlarge": { 162 | "Arch": "PV64" 163 | }, 164 | "c3.large": { 165 | "Arch": "HVM64" 166 | }, 167 | "c3.xlarge": { 168 | "Arch": "HVM64" 169 | }, 170 | "c3.2xlarge": { 171 | "Arch": "HVM64" 172 | }, 173 | "c3.4xlarge": { 174 | "Arch": "HVM64" 175 | }, 176 | "c3.8xlarge": { 177 | "Arch": "HVM64" 178 | }, 179 | "c4.large": { 180 | "Arch": "HVM64" 181 | }, 182 | "c4.xlarge": { 183 | "Arch": "HVM64" 184 | }, 185 | "c4.2xlarge": { 186 | "Arch": "HVM64" 187 | }, 188 | "c4.4xlarge": { 189 | "Arch": "HVM64" 190 | }, 191 | "c4.8xlarge": { 192 | "Arch": "HVM64" 193 | }, 194 | "g2.2xlarge": { 195 | "Arch": "HVMG2" 196 | }, 197 | "g2.8xlarge": { 198 | "Arch": "HVMG2" 199 | }, 200 | "r3.large": { 201 | "Arch": "HVM64" 202 | }, 203 | "r3.xlarge": { 204 | "Arch": "HVM64" 205 | }, 206 | "r3.2xlarge": { 207 | "Arch": "HVM64" 208 | }, 209 | "r3.4xlarge": { 210 | "Arch": "HVM64" 211 | }, 212 | "r3.8xlarge": { 213 | "Arch": "HVM64" 214 | }, 215 | "i2.xlarge": { 216 | "Arch": "HVM64" 217 | }, 218 | "i2.2xlarge": { 219 | "Arch": "HVM64" 220 | }, 221 | "i2.4xlarge": { 222 | "Arch": "HVM64" 223 | }, 224 | "i2.8xlarge": { 225 | "Arch": "HVM64" 226 | }, 227 | "d2.xlarge": { 228 | "Arch": "HVM64" 229 | }, 230 | "d2.2xlarge": { 231 | "Arch": "HVM64" 232 | }, 233 | "d2.4xlarge": { 234 | "Arch": "HVM64" 235 | }, 236 | "d2.8xlarge": { 237 | "Arch": "HVM64" 238 | }, 239 | "hi1.4xlarge": { 240 | "Arch": "HVM64" 241 | }, 242 | "hs1.8xlarge": { 243 | "Arch": "HVM64" 244 | }, 245 | "cr1.8xlarge": { 246 | "Arch": "HVM64" 247 | }, 248 | "cc2.8xlarge": { 249 | "Arch": "HVM64" 250 | } 251 | }, 252 | "AWSInstanceType2NATArch": { 253 | "t1.micro": { 254 | "Arch": "NATPV64" 255 | }, 256 | "t2.nano": { 257 | "Arch": "NATHVM64" 258 | }, 259 | "t2.micro": { 260 | "Arch": "NATHVM64" 261 | }, 262 | "t2.small": { 263 | "Arch": "NATHVM64" 264 | }, 265 | "t2.medium": { 266 | "Arch": "NATHVM64" 267 | }, 268 | "t2.large": { 269 | "Arch": "NATHVM64" 270 | }, 271 | "m1.small": { 272 | "Arch": "NATPV64" 273 | }, 274 | "m1.medium": { 275 | "Arch": "NATPV64" 276 | }, 277 | "m1.large": { 278 | "Arch": "NATPV64" 279 | }, 280 | "m1.xlarge": { 281 | "Arch": "NATPV64" 282 | }, 283 | "m2.xlarge": { 284 | "Arch": "NATPV64" 285 | }, 286 | "m2.2xlarge": { 287 | "Arch": "NATPV64" 288 | }, 289 | "m2.4xlarge": { 290 | "Arch": "NATPV64" 291 | }, 292 | "m3.medium": { 293 | "Arch": "NATHVM64" 294 | }, 295 | "m3.large": { 296 | "Arch": "NATHVM64" 297 | }, 298 | "m3.xlarge": { 299 | "Arch": "NATHVM64" 300 | }, 301 | "m3.2xlarge": { 302 | "Arch": "NATHVM64" 303 | }, 304 | "m4.large": { 305 | "Arch": "NATHVM64" 306 | }, 307 | "m4.xlarge": { 308 | "Arch": "NATHVM64" 309 | }, 310 | "m4.2xlarge": { 311 | "Arch": "NATHVM64" 312 | }, 313 | "m4.4xlarge": { 314 | "Arch": "NATHVM64" 315 | }, 316 | "m4.10xlarge": { 317 | "Arch": "NATHVM64" 318 | }, 319 | "c1.medium": { 320 | "Arch": "NATPV64" 321 | }, 322 | "c1.xlarge": { 323 | "Arch": "NATPV64" 324 | }, 325 | "c3.large": { 326 | "Arch": "NATHVM64" 327 | }, 328 | "c3.xlarge": { 329 | "Arch": "NATHVM64" 330 | }, 331 | "c3.2xlarge": { 332 | "Arch": "NATHVM64" 333 | }, 334 | "c3.4xlarge": { 335 | "Arch": "NATHVM64" 336 | }, 337 | "c3.8xlarge": { 338 | "Arch": "NATHVM64" 339 | }, 340 | "c4.large": { 341 | "Arch": "NATHVM64" 342 | }, 343 | "c4.xlarge": { 344 | "Arch": "NATHVM64" 345 | }, 346 | "c4.2xlarge": { 347 | "Arch": "NATHVM64" 348 | }, 349 | "c4.4xlarge": { 350 | "Arch": "NATHVM64" 351 | }, 352 | "c4.8xlarge": { 353 | "Arch": "NATHVM64" 354 | }, 355 | "g2.2xlarge": { 356 | "Arch": "NATHVMG2" 357 | }, 358 | "g2.8xlarge": { 359 | "Arch": "NATHVMG2" 360 | }, 361 | "r3.large": { 362 | "Arch": "NATHVM64" 363 | }, 364 | "r3.xlarge": { 365 | "Arch": "NATHVM64" 366 | }, 367 | "r3.2xlarge": { 368 | "Arch": "NATHVM64" 369 | }, 370 | "r3.4xlarge": { 371 | "Arch": "NATHVM64" 372 | }, 373 | "r3.8xlarge": { 374 | "Arch": "NATHVM64" 375 | }, 376 | "i2.xlarge": { 377 | "Arch": "NATHVM64" 378 | }, 379 | "i2.2xlarge": { 380 | "Arch": "NATHVM64" 381 | }, 382 | "i2.4xlarge": { 383 | "Arch": "NATHVM64" 384 | }, 385 | "i2.8xlarge": { 386 | "Arch": "NATHVM64" 387 | }, 388 | "d2.xlarge": { 389 | "Arch": "NATHVM64" 390 | }, 391 | "d2.2xlarge": { 392 | "Arch": "NATHVM64" 393 | }, 394 | "d2.4xlarge": { 395 | "Arch": "NATHVM64" 396 | }, 397 | "d2.8xlarge": { 398 | "Arch": "NATHVM64" 399 | }, 400 | "hi1.4xlarge": { 401 | "Arch": "NATHVM64" 402 | }, 403 | "hs1.8xlarge": { 404 | "Arch": "NATHVM64" 405 | }, 406 | "cr1.8xlarge": { 407 | "Arch": "NATHVM64" 408 | }, 409 | "cc2.8xlarge": { 410 | "Arch": "NATHVM64" 411 | } 412 | }, 413 | "AWSRegionArch2AMI": { 414 | "us-east-1": { 415 | "PV64": "ami-5fb8c835", 416 | "HVM64": "ami-60b6c60a", 417 | "HVMG2": "ami-e998ea83" 418 | }, 419 | "us-west-2": { 420 | "PV64": "ami-d93622b8", 421 | "HVM64": "ami-f0091d91", 422 | "HVMG2": "ami-315f4850" 423 | }, 424 | "us-west-1": { 425 | "PV64": "ami-56ea8636", 426 | "HVM64": "ami-d5ea86b5", 427 | "HVMG2": "ami-943956f4" 428 | }, 429 | "eu-west-1": { 430 | "PV64": "ami-95e33ce6", 431 | "HVM64": "ami-bff32ccc", 432 | "HVMG2": "ami-83fd23f0" 433 | }, 434 | "eu-central-1": { 435 | "PV64": "ami-794a5915", 436 | "HVM64": "ami-bc5b48d0", 437 | "HVMG2": "ami-ba1a09d6" 438 | }, 439 | "ap-northeast-1": { 440 | "PV64": "ami-393c1957", 441 | "HVM64": "ami-383c1956", 442 | "HVMG2": "ami-08e5c166" 443 | }, 444 | "ap-northeast-2": { 445 | "PV64": "NOT_SUPPORTED", 446 | "HVM64": "ami-249b554a", 447 | "HVMG2": "NOT_SUPPORTED" 448 | }, 449 | "ap-southeast-1": { 450 | "PV64": "ami-34bd7a57", 451 | "HVM64": "ami-c9b572aa", 452 | "HVMG2": "ami-5a15d239" 453 | }, 454 | "ap-southeast-2": { 455 | "PV64": "ami-ced887ad", 456 | "HVM64": "ami-48d38c2b", 457 | "HVMG2": "ami-0c1a446f" 458 | }, 459 | "sa-east-1": { 460 | "PV64": "ami-7d15ad11", 461 | "HVM64": "ami-6817af04", 462 | "HVMG2": "NOT_SUPPORTED" 463 | }, 464 | "cn-north-1": { 465 | "PV64": "ami-18ac6575", 466 | "HVM64": "ami-43a36a2e", 467 | "HVMG2": "NOT_SUPPORTED" 468 | } 469 | } 470 | }, 471 | "Resources": { 472 | "VPC": { 473 | "Type": "AWS::EC2::VPC", 474 | "Properties": { 475 | "EnableDnsSupport": "true", 476 | "EnableDnsHostnames": "true", 477 | "CidrBlock": { 478 | "Fn::FindInMap": [ 479 | "SubnetConfig", 480 | "VPC", 481 | "CIDR" 482 | ] 483 | }, 484 | "Tags": [ 485 | { 486 | "Key": "Application", 487 | "Value": { 488 | "Ref": "AWS::StackName" 489 | } 490 | }, 491 | { 492 | "Key": "Network", 493 | "Value": "Public" 494 | } 495 | ] 496 | }, 497 | "Metadata": { 498 | "AWS::CloudFormation::Designer": { 499 | "id": "a01f146b-dd51-4935-94cb-7d46278bccba" 500 | } 501 | } 502 | }, 503 | "PublicSubnet": { 504 | "Type": "AWS::EC2::Subnet", 505 | "Properties": { 506 | "VpcId": { 507 | "Ref": "VPC" 508 | }, 509 | "CidrBlock": { 510 | "Fn::FindInMap": [ 511 | "SubnetConfig", 512 | "Public", 513 | "CIDR" 514 | ] 515 | }, 516 | "Tags": [ 517 | { 518 | "Key": "Application", 519 | "Value": { 520 | "Ref": "AWS::StackName" 521 | } 522 | }, 523 | { 524 | "Key": "Network", 525 | "Value": "Public" 526 | } 527 | ] 528 | }, 529 | "Metadata": { 530 | "AWS::CloudFormation::Designer": { 531 | "id": "237a98ca-bae9-44d0-8c0e-078801a04cef" 532 | } 533 | } 534 | }, 535 | "InternetGateway": { 536 | "Type": "AWS::EC2::InternetGateway", 537 | "Properties": { 538 | "Tags": [ 539 | { 540 | "Key": "Application", 541 | "Value": { 542 | "Ref": "AWS::StackName" 543 | } 544 | }, 545 | { 546 | "Key": "Network", 547 | "Value": "Public" 548 | } 549 | ] 550 | }, 551 | "Metadata": { 552 | "AWS::CloudFormation::Designer": { 553 | "id": "1e4580ef-6e51-4daf-9f9b-778096368de7" 554 | } 555 | } 556 | }, 557 | "GatewayToInternet": { 558 | "Type": "AWS::EC2::VPCGatewayAttachment", 559 | "Properties": { 560 | "VpcId": { 561 | "Ref": "VPC" 562 | }, 563 | "InternetGatewayId": { 564 | "Ref": "InternetGateway" 565 | } 566 | }, 567 | "Metadata": { 568 | "AWS::CloudFormation::Designer": { 569 | "id": "650759d1-5122-4fd9-8e1f-93bda58244bc" 570 | } 571 | } 572 | }, 573 | "PublicRouteTable": { 574 | "Type": "AWS::EC2::RouteTable", 575 | "Properties": { 576 | "VpcId": { 577 | "Ref": "VPC" 578 | }, 579 | "Tags": [ 580 | { 581 | "Key": "Application", 582 | "Value": { 583 | "Ref": "AWS::StackName" 584 | } 585 | }, 586 | { 587 | "Key": "Network", 588 | "Value": "Public" 589 | } 590 | ] 591 | }, 592 | "Metadata": { 593 | "AWS::CloudFormation::Designer": { 594 | "id": "e9acd514-bdcc-4d89-944c-6c9585c0b215" 595 | } 596 | } 597 | }, 598 | "PublicRoute": { 599 | "Type": "AWS::EC2::Route", 600 | "DependsOn": "GatewayToInternet", 601 | "Properties": { 602 | "RouteTableId": { 603 | "Ref": "PublicRouteTable" 604 | }, 605 | "DestinationCidrBlock": "0.0.0.0/0", 606 | "GatewayId": { 607 | "Ref": "InternetGateway" 608 | } 609 | }, 610 | "Metadata": { 611 | "AWS::CloudFormation::Designer": { 612 | "id": "e940bae2-493f-4016-9fb4-c1b62e1a348d" 613 | } 614 | } 615 | }, 616 | "PublicSubnetRouteTableAssociation": { 617 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 618 | "Properties": { 619 | "SubnetId": { 620 | "Ref": "PublicSubnet" 621 | }, 622 | "RouteTableId": { 623 | "Ref": "PublicRouteTable" 624 | } 625 | }, 626 | "Metadata": { 627 | "AWS::CloudFormation::Designer": { 628 | "id": "5135621c-2561-4efd-ba18-e9419832ef2c" 629 | } 630 | } 631 | }, 632 | "PublicNetworkAcl": { 633 | "Type": "AWS::EC2::NetworkAcl", 634 | "Properties": { 635 | "VpcId": { 636 | "Ref": "VPC" 637 | }, 638 | "Tags": [ 639 | { 640 | "Key": "Application", 641 | "Value": { 642 | "Ref": "AWS::StackName" 643 | } 644 | }, 645 | { 646 | "Key": "Network", 647 | "Value": "Public" 648 | } 649 | ] 650 | }, 651 | "Metadata": { 652 | "AWS::CloudFormation::Designer": { 653 | "id": "d41ab43e-f7ca-46e9-86e2-2520558295c8" 654 | } 655 | } 656 | }, 657 | "InboundHTTPPublicNetworkAclEntry": { 658 | "Type": "AWS::EC2::NetworkAclEntry", 659 | "Properties": { 660 | "NetworkAclId": { 661 | "Ref": "PublicNetworkAcl" 662 | }, 663 | "RuleNumber": "100", 664 | "Protocol": "6", 665 | "RuleAction": "allow", 666 | "Egress": "false", 667 | "CidrBlock": "0.0.0.0/0", 668 | "PortRange": { 669 | "From": "80", 670 | "To": "80" 671 | } 672 | }, 673 | "Metadata": { 674 | "AWS::CloudFormation::Designer": { 675 | "id": "17c905aa-4073-44c3-97d5-b1ccd010fce2" 676 | } 677 | } 678 | }, 679 | "InboundHTTPSPublicNetworkAclEntry": { 680 | "Type": "AWS::EC2::NetworkAclEntry", 681 | "Properties": { 682 | "NetworkAclId": { 683 | "Ref": "PublicNetworkAcl" 684 | }, 685 | "RuleNumber": "101", 686 | "Protocol": "6", 687 | "RuleAction": "allow", 688 | "Egress": "false", 689 | "CidrBlock": "0.0.0.0/0", 690 | "PortRange": { 691 | "From": "443", 692 | "To": "443" 693 | } 694 | }, 695 | "Metadata": { 696 | "AWS::CloudFormation::Designer": { 697 | "id": "35f2c538-7640-48f7-84aa-6927f323f9cf" 698 | } 699 | } 700 | }, 701 | "InboundSSHPublicNetworkAclEntry": { 702 | "Type": "AWS::EC2::NetworkAclEntry", 703 | "Properties": { 704 | "NetworkAclId": { 705 | "Ref": "PublicNetworkAcl" 706 | }, 707 | "RuleNumber": "102", 708 | "Protocol": "6", 709 | "RuleAction": "allow", 710 | "Egress": "false", 711 | "CidrBlock": { 712 | "Ref": "SSHLocation" 713 | }, 714 | "PortRange": { 715 | "From": "22", 716 | "To": "22" 717 | } 718 | }, 719 | "Metadata": { 720 | "AWS::CloudFormation::Designer": { 721 | "id": "6e66b590-be39-480f-988e-84ce2bcfa993" 722 | } 723 | } 724 | }, 725 | "InboundEphemeralPublicNetworkAclEntry": { 726 | "Type": "AWS::EC2::NetworkAclEntry", 727 | "Properties": { 728 | "NetworkAclId": { 729 | "Ref": "PublicNetworkAcl" 730 | }, 731 | "RuleNumber": "103", 732 | "Protocol": "6", 733 | "RuleAction": "allow", 734 | "Egress": "false", 735 | "CidrBlock": "0.0.0.0/0", 736 | "PortRange": { 737 | "From": "1024", 738 | "To": "65535" 739 | } 740 | }, 741 | "Metadata": { 742 | "AWS::CloudFormation::Designer": { 743 | "id": "bf45e14e-4cc1-46d0-bab9-8dc2fa5c678e" 744 | } 745 | } 746 | }, 747 | "OutboundPublicNetworkAclEntry": { 748 | "Type": "AWS::EC2::NetworkAclEntry", 749 | "Properties": { 750 | "NetworkAclId": { 751 | "Ref": "PublicNetworkAcl" 752 | }, 753 | "RuleNumber": "100", 754 | "Protocol": "6", 755 | "RuleAction": "allow", 756 | "Egress": "true", 757 | "CidrBlock": "0.0.0.0/0", 758 | "PortRange": { 759 | "From": "0", 760 | "To": "65535" 761 | } 762 | }, 763 | "Metadata": { 764 | "AWS::CloudFormation::Designer": { 765 | "id": "19573bf0-8f10-4fe6-a443-6ee35b60a98c" 766 | } 767 | } 768 | }, 769 | "PublicSubnetNetworkAclAssociation": { 770 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 771 | "Properties": { 772 | "SubnetId": { 773 | "Ref": "PublicSubnet" 774 | }, 775 | "NetworkAclId": { 776 | "Ref": "PublicNetworkAcl" 777 | } 778 | }, 779 | "Metadata": { 780 | "AWS::CloudFormation::Designer": { 781 | "id": "e9e0d41b-07e7-465c-95dc-de12ffb1f1aa" 782 | } 783 | } 784 | }, 785 | "EC2Host": { 786 | "Type": "AWS::EC2::Instance", 787 | "DependsOn": "GatewayToInternet", 788 | "Properties": { 789 | "InstanceType": { 790 | "Ref": "EC2InstanceType" 791 | }, 792 | "KeyName": { 793 | "Ref": "KeyName" 794 | }, 795 | "ImageId": { 796 | "Fn::FindInMap": [ 797 | "AWSRegionArch2AMI", 798 | { 799 | "Ref": "AWS::Region" 800 | }, 801 | { 802 | "Fn::FindInMap": [ 803 | "AWSInstanceType2Arch", 804 | { 805 | "Ref": "EC2InstanceType" 806 | }, 807 | "Arch" 808 | ] 809 | } 810 | ] 811 | }, 812 | "NetworkInterfaces": [ 813 | { 814 | "GroupSet": [ 815 | { 816 | "Ref": "EC2SecurityGroup" 817 | } 818 | ], 819 | "AssociatePublicIpAddress": "true", 820 | "DeviceIndex": "0", 821 | "DeleteOnTermination": "true", 822 | "SubnetId": { 823 | "Ref": "PublicSubnet" 824 | } 825 | } 826 | ] 827 | }, 828 | "Metadata": { 829 | "AWS::CloudFormation::Designer": { 830 | "id": "e4d17af6-fe25-4470-a26d-bf34f3dfd621" 831 | } 832 | } 833 | }, 834 | "EC2SecurityGroup": { 835 | "Type": "AWS::EC2::SecurityGroup", 836 | "Properties": { 837 | "GroupDescription": "Enable access to the EC2 host", 838 | "VpcId": { 839 | "Ref": "VPC" 840 | }, 841 | "SecurityGroupIngress": [ 842 | { 843 | "IpProtocol": "tcp", 844 | "FromPort": "22", 845 | "ToPort": "22", 846 | "CidrIp": { 847 | "Ref": "SSHLocation" 848 | } 849 | } 850 | ] 851 | }, 852 | "Metadata": { 853 | "AWS::CloudFormation::Designer": { 854 | "id": "d4611cc9-f137-4942-9e2a-d49a52f0195a" 855 | } 856 | } 857 | } 858 | }, 859 | "Outputs": { 860 | "VPCId": { 861 | "Description": "VPCId of the newly created VPC", 862 | "Value": { 863 | "Ref": "VPC" 864 | } 865 | }, 866 | "PublicSubnet": { 867 | "Description": "SubnetId of the public subnet", 868 | "Value": { 869 | "Ref": "PublicSubnet" 870 | } 871 | }, 872 | "DNSName": { 873 | "Description": "DNS Name of the EC2 host", 874 | "Value": { 875 | "Fn::GetAtt": [ 876 | "EC2Host", 877 | "PublicDnsName" 878 | ] 879 | } 880 | } 881 | }, 882 | "Metadata": { 883 | "AWS::CloudFormation::Designer": { 884 | "1e4580ef-6e51-4daf-9f9b-778096368de7": { 885 | "size": { 886 | "width": 60, 887 | "height": 60 888 | }, 889 | "position": { 890 | "x": 900, 891 | "y": 90 892 | }, 893 | "z": 1, 894 | "embeds": [] 895 | }, 896 | "a01f146b-dd51-4935-94cb-7d46278bccba": { 897 | "size": { 898 | "width": 780, 899 | "height": 780 900 | }, 901 | "position": { 902 | "x": 60, 903 | "y": 90 904 | }, 905 | "z": 1, 906 | "embeds": [ 907 | "d4611cc9-f137-4942-9e2a-d49a52f0195a", 908 | "d41ab43e-f7ca-46e9-86e2-2520558295c8", 909 | "e9acd514-bdcc-4d89-944c-6c9585c0b215", 910 | "237a98ca-bae9-44d0-8c0e-078801a04cef" 911 | ] 912 | }, 913 | "d4611cc9-f137-4942-9e2a-d49a52f0195a": { 914 | "size": { 915 | "width": 60, 916 | "height": 60 917 | }, 918 | "position": { 919 | "x": 570, 920 | "y": 150 921 | }, 922 | "z": 2, 923 | "parent": "a01f146b-dd51-4935-94cb-7d46278bccba", 924 | "embeds": [] 925 | }, 926 | "d41ab43e-f7ca-46e9-86e2-2520558295c8": { 927 | "size": { 928 | "width": 420, 929 | "height": 330 930 | }, 931 | "position": { 932 | "x": 90, 933 | "y": 150 934 | }, 935 | "z": 2, 936 | "parent": "a01f146b-dd51-4935-94cb-7d46278bccba", 937 | "embeds": [ 938 | "19573bf0-8f10-4fe6-a443-6ee35b60a98c", 939 | "bf45e14e-4cc1-46d0-bab9-8dc2fa5c678e", 940 | "6e66b590-be39-480f-988e-84ce2bcfa993", 941 | "35f2c538-7640-48f7-84aa-6927f323f9cf", 942 | "17c905aa-4073-44c3-97d5-b1ccd010fce2" 943 | ] 944 | }, 945 | "19573bf0-8f10-4fe6-a443-6ee35b60a98c": { 946 | "size": { 947 | "width": 60, 948 | "height": 60 949 | }, 950 | "position": { 951 | "x": 120, 952 | "y": 210 953 | }, 954 | "z": 3, 955 | "parent": "d41ab43e-f7ca-46e9-86e2-2520558295c8", 956 | "embeds": [] 957 | }, 958 | "bf45e14e-4cc1-46d0-bab9-8dc2fa5c678e": { 959 | "size": { 960 | "width": 60, 961 | "height": 60 962 | }, 963 | "position": { 964 | "x": 240, 965 | "y": 210 966 | }, 967 | "z": 3, 968 | "parent": "d41ab43e-f7ca-46e9-86e2-2520558295c8", 969 | "embeds": [] 970 | }, 971 | "6e66b590-be39-480f-988e-84ce2bcfa993": { 972 | "size": { 973 | "width": 60, 974 | "height": 60 975 | }, 976 | "position": { 977 | "x": 120, 978 | "y": 330 979 | }, 980 | "z": 3, 981 | "parent": "d41ab43e-f7ca-46e9-86e2-2520558295c8", 982 | "embeds": [] 983 | }, 984 | "35f2c538-7640-48f7-84aa-6927f323f9cf": { 985 | "size": { 986 | "width": 60, 987 | "height": 60 988 | }, 989 | "position": { 990 | "x": 240, 991 | "y": 330 992 | }, 993 | "z": 3, 994 | "parent": "d41ab43e-f7ca-46e9-86e2-2520558295c8", 995 | "embeds": [] 996 | }, 997 | "17c905aa-4073-44c3-97d5-b1ccd010fce2": { 998 | "size": { 999 | "width": 60, 1000 | "height": 60 1001 | }, 1002 | "position": { 1003 | "x": 360, 1004 | "y": 210 1005 | }, 1006 | "z": 3, 1007 | "parent": "d41ab43e-f7ca-46e9-86e2-2520558295c8", 1008 | "embeds": [] 1009 | }, 1010 | "e9acd514-bdcc-4d89-944c-6c9585c0b215": { 1011 | "size": { 1012 | "width": 240, 1013 | "height": 240 1014 | }, 1015 | "position": { 1016 | "x": 390, 1017 | "y": 540 1018 | }, 1019 | "z": 2, 1020 | "parent": "a01f146b-dd51-4935-94cb-7d46278bccba", 1021 | "embeds": [ 1022 | "e940bae2-493f-4016-9fb4-c1b62e1a348d" 1023 | ] 1024 | }, 1025 | "650759d1-5122-4fd9-8e1f-93bda58244bc": { 1026 | "source": { 1027 | "id": "1e4580ef-6e51-4daf-9f9b-778096368de7" 1028 | }, 1029 | "target": { 1030 | "id": "a01f146b-dd51-4935-94cb-7d46278bccba" 1031 | } 1032 | }, 1033 | "e940bae2-493f-4016-9fb4-c1b62e1a348d": { 1034 | "size": { 1035 | "width": 60, 1036 | "height": 60 1037 | }, 1038 | "position": { 1039 | "x": 420, 1040 | "y": 600 1041 | }, 1042 | "z": 3, 1043 | "parent": "e9acd514-bdcc-4d89-944c-6c9585c0b215", 1044 | "embeds": [], 1045 | "references": [ 1046 | "1e4580ef-6e51-4daf-9f9b-778096368de7" 1047 | ], 1048 | "dependson": [ 1049 | "650759d1-5122-4fd9-8e1f-93bda58244bc" 1050 | ] 1051 | }, 1052 | "237a98ca-bae9-44d0-8c0e-078801a04cef": { 1053 | "size": { 1054 | "width": 240, 1055 | "height": 240 1056 | }, 1057 | "position": { 1058 | "x": 90, 1059 | "y": 540 1060 | }, 1061 | "z": 2, 1062 | "parent": "a01f146b-dd51-4935-94cb-7d46278bccba", 1063 | "embeds": [ 1064 | "e4d17af6-fe25-4470-a26d-bf34f3dfd621" 1065 | ] 1066 | }, 1067 | "e4d17af6-fe25-4470-a26d-bf34f3dfd621": { 1068 | "size": { 1069 | "width": 60, 1070 | "height": 60 1071 | }, 1072 | "position": { 1073 | "x": 120, 1074 | "y": 600 1075 | }, 1076 | "z": 3, 1077 | "parent": "237a98ca-bae9-44d0-8c0e-078801a04cef", 1078 | "embeds": [], 1079 | "dependson": [ 1080 | "650759d1-5122-4fd9-8e1f-93bda58244bc" 1081 | ], 1082 | "isrelatedto": [ 1083 | "d4611cc9-f137-4942-9e2a-d49a52f0195a" 1084 | ] 1085 | }, 1086 | "e9e0d41b-07e7-465c-95dc-de12ffb1f1aa": { 1087 | "source": { 1088 | "id": "d41ab43e-f7ca-46e9-86e2-2520558295c8" 1089 | }, 1090 | "target": { 1091 | "id": "237a98ca-bae9-44d0-8c0e-078801a04cef" 1092 | } 1093 | }, 1094 | "5135621c-2561-4efd-ba18-e9419832ef2c": { 1095 | "source": { 1096 | "id": "e9acd514-bdcc-4d89-944c-6c9585c0b215" 1097 | }, 1098 | "target": { 1099 | "id": "237a98ca-bae9-44d0-8c0e-078801a04cef" 1100 | } 1101 | } 1102 | } 1103 | } 1104 | } 1105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p42", 3 | "version": "1.2.0", 4 | "description": "CLI wrapper for using Docker with Swarm.", 5 | "files": [ 6 | "lib", 7 | "share" 8 | ], 9 | "bin": { 10 | "p42": "bin/p42" 11 | }, 12 | "scripts": { 13 | "test": "coffee test/index.coffee", 14 | "prepublish": "coffee -o lib/ -c src/*.*coffee", 15 | "postpublish": "(node_modules/.bin/json -f package.json version | xargs -I version git tag -am version version) && git push --tags" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/pandastrike/p42.git" 20 | }, 21 | "keywords": [ 22 | "Docker", 23 | "Swarm", 24 | "CLI" 25 | ], 26 | "author": "Dan Yoder", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/pandastrike/p42/issues" 30 | }, 31 | "homepage": "https://github.com/pandastrike/p42#readme", 32 | "devDependencies": { 33 | "amen": "^1.0.0-alpha-06", 34 | "json": "^9.0.3" 35 | }, 36 | "dependencies": { 37 | "bartlett": "^1.0.0-alpha-02", 38 | "fairmont": "^1.0.x", 39 | "handlebars": "^4.0.5", 40 | "js-yaml": "^3.5.3", 41 | "panda-messages": "0.0.1", 42 | "panda-rw": "^1.0.0-beta-02", 43 | "prompt": "^1.0.0", 44 | "rimraf": "^2.5.2", 45 | "sprintf": "^0.1.5", 46 | "swag": "^0.7.0", 47 | "when": "^3.7.7" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /share/aws/cf/vpc.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: AWS CloudFormation Template for use with p42. 3 | Parameters: 4 | SSHLocation: 5 | Description: Lockdown SSH access to the bastion host (default can be accessed from anywhere) 6 | Type: String 7 | MinLength: '9' 8 | MaxLength: '18' 9 | Default: 0.0.0.0/0 10 | AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" 11 | ConstraintDescription: must be a valid CIDR range of the form x.x.x.x/x. 12 | Mappings: 13 | SubnetConfig: 14 | VPC: 15 | CIDR: 10.0.0.0/16 16 | Public: 17 | CIDR: 10.0.0.0/24 18 | Resources: 19 | VPC: 20 | Type: 'AWS::EC2::VPC' 21 | Properties: 22 | EnableDnsSupport: 'true' 23 | EnableDnsHostnames: 'true' 24 | CidrBlock: 25 | 'Fn::FindInMap': 26 | - SubnetConfig 27 | - VPC 28 | - CIDR 29 | Tags: 30 | - Key: Application 31 | Value: 32 | Ref: 'AWS::StackName' 33 | - Key: Network 34 | Value: Public 35 | PublicSubnet: 36 | Type: 'AWS::EC2::Subnet' 37 | Properties: 38 | VpcId: 39 | Ref: VPC 40 | CidrBlock: 41 | 'Fn::FindInMap': 42 | - SubnetConfig 43 | - Public 44 | - CIDR 45 | Tags: 46 | - Key: Application 47 | Value: 48 | Ref: 'AWS::StackName' 49 | - Key: Network 50 | Value: Public 51 | InternetGateway: 52 | Type: 'AWS::EC2::InternetGateway' 53 | Properties: 54 | Tags: 55 | - Key: Application 56 | Value: 57 | Ref: 'AWS::StackName' 58 | - Key: Network 59 | Value: Public 60 | GatewayToInternet: 61 | Type: 'AWS::EC2::VPCGatewayAttachment' 62 | Properties: 63 | VpcId: 64 | Ref: VPC 65 | InternetGatewayId: 66 | Ref: InternetGateway 67 | PublicRouteTable: 68 | Type: 'AWS::EC2::RouteTable' 69 | Properties: 70 | VpcId: 71 | Ref: VPC 72 | Tags: 73 | - Key: Application 74 | Value: 75 | Ref: 'AWS::StackName' 76 | - Key: Network 77 | Value: Public 78 | PublicRoute: 79 | Type: 'AWS::EC2::Route' 80 | DependsOn: GatewayToInternet 81 | Properties: 82 | RouteTableId: 83 | Ref: PublicRouteTable 84 | DestinationCidrBlock: 0.0.0.0/0 85 | GatewayId: 86 | Ref: InternetGateway 87 | PublicSubnetRouteTableAssociation: 88 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 89 | Properties: 90 | SubnetId: 91 | Ref: PublicSubnet 92 | RouteTableId: 93 | Ref: PublicRouteTable 94 | PublicNetworkAcl: 95 | Type: 'AWS::EC2::NetworkAcl' 96 | Properties: 97 | VpcId: 98 | Ref: VPC 99 | Tags: 100 | - Key: Application 101 | Value: 102 | Ref: 'AWS::StackName' 103 | - Key: Network 104 | Value: Public 105 | InboundHTTPPublicNetworkAclEntry: 106 | Type: 'AWS::EC2::NetworkAclEntry' 107 | Properties: 108 | NetworkAclId: 109 | Ref: PublicNetworkAcl 110 | RuleNumber: '100' 111 | Protocol: '6' 112 | RuleAction: allow 113 | Egress: 'false' 114 | CidrBlock: 0.0.0.0/0 115 | PortRange: 116 | From: '80' 117 | To: '80' 118 | InboundHTTPSPublicNetworkAclEntry: 119 | Type: 'AWS::EC2::NetworkAclEntry' 120 | Properties: 121 | NetworkAclId: 122 | Ref: PublicNetworkAcl 123 | RuleNumber: '101' 124 | Protocol: '6' 125 | RuleAction: allow 126 | Egress: 'false' 127 | CidrBlock: 0.0.0.0/0 128 | PortRange: 129 | From: '443' 130 | To: '443' 131 | InboundSSHPublicNetworkAclEntry: 132 | Type: 'AWS::EC2::NetworkAclEntry' 133 | Properties: 134 | NetworkAclId: 135 | Ref: PublicNetworkAcl 136 | RuleNumber: '102' 137 | Protocol: '6' 138 | RuleAction: allow 139 | Egress: 'false' 140 | CidrBlock: 141 | Ref: SSHLocation 142 | PortRange: 143 | From: '22' 144 | To: '22' 145 | InboundEphemeralPublicNetworkAclEntry: 146 | Type: 'AWS::EC2::NetworkAclEntry' 147 | Properties: 148 | NetworkAclId: 149 | Ref: PublicNetworkAcl 150 | RuleNumber: '103' 151 | Protocol: '6' 152 | RuleAction: allow 153 | Egress: 'false' 154 | CidrBlock: 0.0.0.0/0 155 | PortRange: 156 | From: '1024' 157 | To: '65535' 158 | OutboundPublicNetworkAclEntry: 159 | Type: 'AWS::EC2::NetworkAclEntry' 160 | Properties: 161 | NetworkAclId: 162 | Ref: PublicNetworkAcl 163 | RuleNumber: '100' 164 | Protocol: '6' 165 | RuleAction: allow 166 | Egress: 'true' 167 | CidrBlock: 0.0.0.0/0 168 | PortRange: 169 | From: '0' 170 | To: '65535' 171 | PublicSubnetNetworkAclAssociation: 172 | Type: 'AWS::EC2::SubnetNetworkAclAssociation' 173 | Properties: 174 | SubnetId: 175 | Ref: PublicSubnet 176 | NetworkAclId: 177 | Ref: PublicNetworkAcl 178 | PrivateDNS: 179 | Type: 'AWS::Route53::HostedZone' 180 | DependsOn: VPC 181 | Properties: 182 | Name: 'name.internal.' 183 | VPCs: 184 | - VPCId: 185 | Ref: VPC 186 | VPCRegion: 187 | Ref: 'AWS::Region' 188 | DHCPOptions : 189 | Type: 'AWS::EC2::DHCPOptions' 190 | Properties: 191 | DomainName: 'name.internal' 192 | DomainNameServers: 193 | - AmazonProvidedDNS 194 | ELBSecurityGroup: 195 | Type: 'AWS::EC2::SecurityGroup' 196 | Properties: 197 | GroupDescription: 'Allow ELB to accept traffic from the public Web' 198 | VpcId: 199 | Ref: 'VPC' 200 | SecurityGroupIngress: 201 | - IpProtocol: 'tcp' 202 | FromPort: '80' 203 | ToPort: '80' 204 | CidrIp: '0.0.0.0/0' 205 | ELB: 206 | Type: 'AWS::ElasticLoadBalancing::LoadBalancer' 207 | DependsOn: GatewayToInternet 208 | Properties: 209 | LoadBalancerName: 210 | Ref: 'AWS::StackName' 211 | SecurityGroups: 212 | - Ref: 'ELBSecurityGroup' 213 | - 'Fn::GetAtt': 214 | - VPC 215 | - DefaultSecurityGroup 216 | Listeners: 217 | - LoadBalancerPort: 80 218 | Protocol: HTTP 219 | InstancePort: 80 220 | # Ignore HTTPS for now 221 | # - LoadBalancerPort: 443 222 | # Protocol: HTTPS 223 | # InstancePort: 80 224 | # InstanceProtocol: HTTP 225 | Subnets: 226 | - Ref: 'PublicSubnet' 227 | HealthCheck: 228 | HealthyThreshold: 2 # checks 229 | Interval: 30 # seconds 230 | Target: "TCP:80" 231 | Timeout: 5 232 | UnhealthyThreshold: 2 233 | Outputs: 234 | VPCId: 235 | Description: VPCId of the newly created VPC 236 | Value: 237 | Ref: VPC 238 | PublicSubnet: 239 | Description: SubnetId of the public subnet 240 | Value: 241 | Ref: PublicSubnet 242 | AvailabilityZone: 243 | Description: Availability Zone of the public subnet 244 | Value: 245 | 'Fn::GetAtt': 246 | - PublicSubnet 247 | - AvailabilityZone 248 | HostedZoneId: 249 | Description: HostedZoneId of the private HostedZone 250 | Value: 251 | Ref: PrivateDNS 252 | DefaultSecurityGroupId: 253 | Description: "Default security group ID for the VPC" 254 | Value: 255 | 'Fn::GetAtt': 256 | - VPC 257 | - DefaultSecurityGroup 258 | -------------------------------------------------------------------------------- /share/aws/dns/a.yaml: -------------------------------------------------------------------------------- 1 | Comment: "{{comment}}" 2 | Changes: 3 | - Action: UPSERT 4 | ResourceRecordSet: 5 | Name: {{name}}.name.internal. 6 | Type: A 7 | TTL: 60 8 | ResourceRecords: 9 | - Value: '{{ip}}' 10 | -------------------------------------------------------------------------------- /share/aws/dns/alias.yaml: -------------------------------------------------------------------------------- 1 | Comment: "{{comment}}" 2 | Changes: 3 | - Action: UPSERT 4 | ResourceRecordSet: 5 | Name: '{{domain}}.' 6 | Type: A 7 | AliasTarget: 8 | HostedZoneId: '{{zoneId}}' 9 | DNSName: '{{name}}' 10 | EvaluateTargetHealth: false 11 | -------------------------------------------------------------------------------- /share/aws/dns/srv.yaml: -------------------------------------------------------------------------------- 1 | Comment: "{{comment}}" 2 | Changes: 3 | - Action: UPSERT 4 | ResourceRecordSet: 5 | Name: _{{subdomain}}._{{protocol}}.name.internal. 6 | Type: SRV 7 | TTL: 60 8 | ResourceRecords: 9 | {{#targets}} 10 | - Value: '0 0 {{port}} {{host}}.name.internal' 11 | {{/targets}} 12 | -------------------------------------------------------------------------------- /share/aws/ecr/policy.yaml: -------------------------------------------------------------------------------- 1 | Version: '2008-10-17' 2 | Statement: 3 | - Sid: 'Allow Any/All' 4 | Effect: Allow 5 | Principal: '*' 6 | Action: 7 | - 'ecr:*' 8 | -------------------------------------------------------------------------------- /share/commands.yaml: -------------------------------------------------------------------------------- 1 | aws: 2 | 3 | cloudformation: 4 | 5 | create-stack: 6 | template: | 7 | aws cloudformation create-stack 8 | --stack-name {{stack}} 9 | --template-body file://{{file}} 10 | 11 | describe-stacks: 12 | 13 | template: | 14 | aws cloudformation describe-stacks --stack-name {{stack}} 15 | 16 | processor: json 17 | 18 | attributes: 19 | - name: status 20 | accessor: Stacks.0.StackStatus 21 | - name: vpcId 22 | accessor: Stacks.0.Outputs.0.OutputValue 23 | - name: subnetId 24 | accessor: Stacks.0.Outputs.1.OutputValue 25 | - name: az 26 | accessor: Stacks.0.Outputs.2.OutputValue 27 | - name: zoneId 28 | accessor: Stacks.0.Outputs.3.OutputValue 29 | 30 | test: 31 | status: CREATE_COMPLETE 32 | vpcId: test-vpc-00 33 | subnetId: test-subnet-00 34 | az: us-west-1a 35 | zoneId: test-zone-00 36 | 37 | delete-stack: 38 | 39 | template: | 40 | aws cloudformation delete-stack --stack-name {{stack}} 41 | 42 | elb: 43 | 44 | describe-load-balancers: 45 | 46 | template: | 47 | aws elb describe-load-balancers 48 | --load-balancer-name {{cluster}} 49 | 50 | processor: json 51 | 52 | attributes: 53 | - name: zoneId 54 | accessor: LoadBalancerDescriptions.0.CanonicalHostedZoneNameID 55 | - name: domain 56 | accessor: LoadBalancerDescriptions.0.CanonicalHostedZoneName 57 | 58 | test: 59 | zoneId: test-zone-00 60 | domain: '123.elb.test.com' 61 | 62 | register-instances-with-load-balancer: 63 | 64 | template: | 65 | aws elb register-instances-with-load-balancer 66 | --load-balancer-name {{cluster}} 67 | --instances {{instanceId}} 68 | 69 | ecr: 70 | 71 | create-repository: 72 | 73 | template: | 74 | aws ecr create-repository 75 | --repository-name {{repository}} 76 | --region us-east-1 77 | 78 | set-repository-policy: 79 | 80 | template: | 81 | aws ecr set-repository-policy 82 | --repository-name {{repository}} 83 | --region us-east-1 84 | --policy-text {{policy}} 85 | 86 | 87 | describe-repositories: 88 | 89 | template: | 90 | aws ecr describe-repositories 91 | --repository-name {{repository}} 92 | --region us-east-1 93 | 94 | processor: json 95 | 96 | attributes: 97 | - name: registryId 98 | accessor: repositories.0.registryId 99 | 100 | test: 101 | repositoryId: test-repo-00 102 | 103 | # actually processord to get the registry URL 104 | get-authorization-token: 105 | template: | 106 | aws ecr get-authorization-token 107 | --region us-east-1 108 | 109 | processor: json 110 | 111 | attributes: 112 | - name: url 113 | accessor: authorizationData.0.proxyEndpoint 114 | 115 | test: 116 | url: 'https://123.registry.test.com' 117 | 118 | ec2: 119 | 120 | describe-instances: 121 | 122 | template: | 123 | aws ec2 describe-instances 124 | --filters Name=tag-value,Values={{instance}} 125 | 126 | processor: json 127 | 128 | attributes: 129 | - name: instanceId 130 | accessor: Reservations.0.Instances.0.InstanceId 131 | - name: ip 132 | accessor: Reservations.0.Instances.0.PrivateIpAddress 133 | 134 | test: 135 | instanceId: test-instance-00 136 | ip: '192.168.0.42' 137 | 138 | describe-security-groups: 139 | 140 | template: | 141 | aws ec2 describe-security-groups 142 | --filters 143 | Name=vpc-id,Values={{vpcId}} 144 | Name=group-name,Values={{group}} 145 | 146 | processor: json 147 | 148 | attributes: 149 | - name: groupId 150 | accessor: SecurityGroups.0.GroupId 151 | 152 | test: 153 | groupId: test-group-00 154 | 155 | modify-instance-attribute: 156 | 157 | template: | 158 | aws ec2 modify-instance-attribute 159 | --instance-id {{instanceId}} 160 | --groups {{join " " groupIds}} 161 | 162 | route53: 163 | 164 | list-hosted-zones-by-name: 165 | 166 | template: | 167 | aws route53 list-hosted-zones-by-name 168 | --dns-name {{domain}} 169 | --max-items 1 170 | 171 | processor: json 172 | 173 | attributes: 174 | - name: zoneId 175 | accessor: HostedZones.0.Id 176 | 177 | test: 178 | zoneId: test-zone-00 179 | 180 | change-resource-record-sets: 181 | 182 | template: | 183 | aws route53 change-resource-record-sets 184 | --hosted-zone-id {{zoneId}} 185 | --change-batch file://{{file}} 186 | 187 | docker: 188 | 189 | login: 190 | 191 | template: | 192 | eval $(aws ecr get-login --region us-east-1) 193 | 194 | machine: 195 | 196 | env: 197 | 198 | template: | 199 | eval $(docker-machine env {{name}}) 200 | 201 | create: 202 | 203 | template: | 204 | docker-machine create {{name}} 205 | --driver amazonec2 206 | --amazonec2-region {{region}} 207 | --amazonec2-vpc-id {{vpcId}} 208 | --amazonec2-subnet-id {{subnetId}} 209 | --amazonec2-zone {{zone}} 210 | 211 | ls: 212 | 213 | template: >- 214 | docker-machine ls 215 | --format '\{{ .Name }}' 216 | --filter name={{ name }} 217 | 218 | processor: line 219 | 220 | test: 221 | - violent-aftermath-00 222 | - violent-aftermath-01 223 | - violent-aftermath-02 224 | 225 | 226 | stop: 227 | template: | 228 | docker-machine stop {{join " " nodes}} 229 | 230 | rm: 231 | template: | 232 | docker-machine rm {{join " " nodes}} 233 | 234 | swarm: 235 | 236 | env: 237 | 238 | template: | 239 | eval $(docker-machine env --swarm {{name}}) 240 | 241 | create: 242 | 243 | template: | 244 | docker-machine create {{name}} 245 | --driver amazonec2 246 | --amazonec2-region {{region}} 247 | --amazonec2-vpc-id {{vpcId}} 248 | --amazonec2-subnet-id {{subnetId}} 249 | --amazonec2-zone {{zone}} 250 | --swarm 251 | --swarm-discovery nodes://10.0.[0:255].[0:255]:2375 252 | {{#master}}--swarm-master{{/master}} 253 | 254 | build: 255 | 256 | template: | 257 | docker build 258 | -t {{tag}} 259 | -f {{file}} 260 | . 261 | 262 | push: 263 | 264 | template: | 265 | docker push {{tag}} 266 | 267 | pull: 268 | 269 | template: | 270 | docker pull {{tag}} 271 | 272 | run: 273 | 274 | # TODO: may parameterize keys/region too? 275 | # Doesn't seem like high-value coverage. 276 | template: | 277 | docker run 278 | {{options}} 279 | --name {{name}} 280 | --restart always 281 | -e AWS_ACCESS_KEYId="$(aws configure get aws_access_keyId)" 282 | -e AWS_SECRET_ACCESS_KEY="$(aws configure get aws_secret_access_key)" 283 | -e AWS_DEFAULT_REGION="$(aws configure get region)" 284 | -d {{tag}} 285 | 286 | inspect: 287 | 288 | template: | 289 | docker inspect {{name}} 290 | 291 | processor: json 292 | 293 | attributes: 294 | 295 | - name: name 296 | accessor: '.0.Node.Name' 297 | - name: ip 298 | accessor: '.0..Node.IP' 299 | - name: port 300 | accessor: '.0.NetworkSettings.Ports["80/tcp"].0.HostPort' 301 | 302 | test: 303 | name: violent-aftermath-01 304 | ip: 192.168.0.42 305 | port: 32768 306 | 307 | ps: 308 | 309 | template: | 310 | docker ps --filter name={{cluster}} --format \{{ .ID }} 311 | 312 | 313 | 314 | # 315 | # find_available_name() { 316 | # local cluster="${1}" 317 | # local candidates=$(printf '%s\n' $(echo ${cluster}-{0..9}{0..9})) 318 | # local taken=$(list-nodes ${cluster}) 319 | # # return the first element from candidates list that 320 | # # isn't in the taken list... 321 | # comm -23 <(echo "${candidates}") <(echo "${taken}") | head -n 1 322 | # } 323 | # 324 | # list_swarm_nodes() { 325 | # docker-machine ls \ 326 | # --format '{{ .Name }}' \ 327 | # --filter "name=${1}" 328 | # } 329 | # 330 | # remove_swarm_nodes() { 331 | # local cluster="${1}" 332 | # echo "Removing Swarm nodes for cluster <${cluster}>" 333 | # local machines=$(list_swarm_nodes ${cluster}) 334 | # docker-machine stop $machines 335 | # docker-machine rm $machines 336 | # } 337 | -------------------------------------------------------------------------------- /share/interviews/init.yaml: -------------------------------------------------------------------------------- 1 | - name: name 2 | description: Application name 3 | type: string 4 | required: true 5 | 6 | - name: domain 7 | description: Domain name 8 | type: string 9 | require: true 10 | 11 | - name: registry 12 | description: Private registry domain 13 | type: string 14 | require: true 15 | -------------------------------------------------------------------------------- /share/messages.yaml: -------------------------------------------------------------------------------- 1 | unexpected-error: >- 2 | p42: unexpected error: {{message}} 3 | 4 | bad-command: | 5 | p42: '{{name}}' is not a valid command. Run `p42 help` for help. 6 | 7 | bad-subcommand: | 8 | p42: '{{name}}' is not a valid subcommand. 9 | 10 | Run `p42 help ` for help on a specific command. 11 | 12 | not-implemented: "p42: Sorry, but this feature is not yet implemented." 13 | 14 | bad-option: | 15 | p42: '{{name}}' is not a valid option. 16 | 17 | help: | 18 | 19 | usage: p42 20 | 21 | Run Docker containers in an AWS VPC. 22 | 23 | Available commands are: 24 | 25 | {{join ", " (pluck "name" (filter "key" "command" (values .)))}} 26 | 27 | Options valid for all commands: 28 | 29 | {{#filter "type" "switch" (values .)}} 30 | {{sprintf "%16s " (join ", " flags) ~}} 31 | {{hang 20 80 help}} 32 | 33 | {{/filter~}} 34 | 35 | Run `p42 help ` for help on a specific command. 36 | 37 | cluster: 38 | 39 | # help: | 40 | # 41 | # usage: p42 cluster 42 | # 43 | # Create, manage, or remove clusters. 44 | # 45 | # Available subcommands are: 46 | # 47 | # {{options|values|filter>key>subcommand|pluck>name|join>, }} 48 | # 49 | # Run 'p42 cluster help ' for command specific help. 50 | 51 | not-found: | 52 | p42: can't find cluster [{{name}}]. 53 | 54 | create: 55 | 56 | starting: Creating cluster [{{name}}] ... 57 | 58 | complete: Cluster [{{name}}] created. 59 | 60 | help: | 61 | Usage: p42 cluster create 62 | Create a VPC and a master Swarm node. 63 | 64 | expand: 65 | 66 | starting: Expanding cluster [{{name}}] by {{count}} node(s) ... 67 | 68 | complete: Cluster [{{name}}] expanded by {{count}} node(s). 69 | 70 | rm: 71 | 72 | starting: Removing cluster [{{name}}] ... 73 | 74 | complete: Cluster [{{name}}] removed. 75 | 76 | build: 77 | 78 | starting: >- 79 | Building mixins 80 | {{#mixins~}} [{{.}}] {{/mixins~}} 81 | ... 82 | 83 | complete: >- 84 | Application mixins 85 | {{#mixins~}} [{{.}}] {{/mixins~}} 86 | built. 87 | 88 | init: 89 | 90 | help: p42 init 91 | 92 | determining-registry: >- 93 | Determining default registry domain ... 94 | 95 | application: 96 | 97 | no-configuration: >- 98 | p42: missing application configuration. Try running 'p42 init'. 99 | 100 | nothing-to-run: Missing run directory. 101 | 102 | bad-mixin: | 103 | [{{name}}] is not a valid mixin. 104 | 105 | no-target: >- 106 | p42: no target for this branch. 107 | Try: [ p42 target add ]. 108 | 109 | start: 110 | 111 | starting: >- 112 | Starting [{{mixin}}] ... 113 | 114 | complete: >- 115 | [{{mixin}}] started. 116 | 117 | # Summary: Initialize an application for use with p42. 118 | # Help: Initialize an application for use with p42. 119 | # This interactive command will ask you a few questions 120 | # about your application and then write a corresponding 121 | # configuration file. 122 | 123 | 124 | # Usage: p42 mixin [add] [