├── .gitignore ├── LICENSE ├── README.md ├── bin └── cim ├── lib ├── cim.js ├── plugins.json ├── plugins │ ├── Plugin.js │ ├── aws │ │ ├── CloudFormation │ │ │ ├── CloudFormation.js │ │ │ ├── lib │ │ │ │ ├── stack_delete.js │ │ │ │ ├── stack_show.js │ │ │ │ └── stack_up.js │ │ │ └── template │ │ │ │ ├── _cim.yml │ │ │ │ └── cloudformation.yml │ │ ├── ECR │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ └── cloudformation.yml │ │ ├── ECS │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ └── cloudformation.yml │ │ ├── ECSService │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ ├── cloudformation.yml │ │ │ │ └── src │ │ │ │ ├── .dockerignore │ │ │ │ ├── Dockerfile │ │ │ │ ├── package.json │ │ │ │ └── server.js │ │ ├── ECSServiceCI │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── .dockerignore │ │ │ │ ├── Dockerfile │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ ├── buildspec.yml │ │ │ │ ├── cloudformation.yml │ │ │ │ └── src │ │ │ │ ├── package.json │ │ │ │ └── server.js │ │ ├── Lambda │ │ │ ├── Lambda.js │ │ │ ├── lib │ │ │ │ ├── deploy.js │ │ │ │ └── logs.js │ │ │ └── nodejs │ │ │ │ ├── LambdaNode.js │ │ │ │ ├── cloudwatch-cron │ │ │ │ ├── LambdaNodeCloudwatchCron.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── cloudwatch-logs │ │ │ │ ├── LambdaNodeCloudwatchLogs.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── dynamodb │ │ │ │ ├── LambdaNodeDynamoDB.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── kinesis │ │ │ │ ├── LambdaNodeKinesis.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── s3 │ │ │ │ ├── LambdaNodeS3.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── sns │ │ │ │ ├── LambdaNodeSNS.js │ │ │ │ └── template │ │ │ │ │ ├── _cim.yml │ │ │ │ │ ├── cloudformation.yml │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── template │ │ │ │ ├── _cim.yml │ │ │ │ ├── cloudformation.yml │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ ├── ServerlessApi │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ ├── architecture.png │ │ │ │ ├── cloudformation.yml │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ ├── ServerlessWebApp │ │ │ ├── index.js │ │ │ └── template │ │ │ │ ├── README.md │ │ │ │ ├── _cim.yml │ │ │ │ ├── architecture.png │ │ │ │ ├── buildspec.yml │ │ │ │ ├── cloudformation.yml │ │ │ │ └── www │ │ │ │ ├── assets │ │ │ │ ├── css │ │ │ │ │ └── app.css │ │ │ │ ├── images │ │ │ │ │ └── img.jpeg │ │ │ │ └── js │ │ │ │ │ └── app.js │ │ │ │ └── index.html │ │ └── VPC │ │ │ ├── index.js │ │ │ └── template │ │ │ ├── README.md │ │ │ ├── _cim.yml │ │ │ ├── aws-vpc.template │ │ │ └── quickstart-vpc-design-fullscreen.png │ └── lib │ │ ├── create.js │ │ └── templates.js └── util │ ├── cache.js │ ├── cloudformation.js │ ├── configs.js │ └── load-3rd-party-plugins.js ├── package.json └── test ├── plugins ├── Plugin.js └── aws │ └── CloudFormation │ └── CloudFormation.js ├── resources ├── 3rd-party-plugin │ ├── index.js │ ├── lib │ │ └── test.js │ ├── package.json │ └── template │ │ ├── _cim.yml │ │ └── cloudformation.yml ├── base │ ├── _cim.yml │ └── cloudformation.yml ├── cim-test-plugin │ ├── index.js │ ├── lib │ │ └── test.js │ └── package.json ├── missing_cf │ └── _cim.yml ├── missing_parent │ ├── _cim.yml │ └── cloudformation.yml └── s3 │ ├── _cim.yml │ ├── cloudformation.yml │ ├── index.js │ └── package.json └── util ├── cloudformation.js └── configs.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .todo 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Gojko Adzic 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bin/cim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | (new (require('../lib/cim'))()).run(); -------------------------------------------------------------------------------- /lib/cim.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const async = require('async'); 5 | 6 | const load_3rd_party_plugins = require('./util/load-3rd-party-plugins'); 7 | 8 | class CIM { 9 | constructor() { 10 | // Init some vars. 11 | this.yargs; 12 | this.args; 13 | this.templates = []; 14 | this.stacks; 15 | this.npm_root_global; 16 | 17 | // Load the default plugins. 18 | this.plugins = []; 19 | this.third_party_plugins = []; 20 | } 21 | 22 | run() { 23 | let cim = this; 24 | async.waterfall([ 25 | function(next) { 26 | cim._load_plugins(next); 27 | }, 28 | function(next) { 29 | cim._run(next); 30 | } 31 | ], function (err) { 32 | if (err) console.log(err); 33 | }); 34 | } 35 | 36 | _load_plugins(done) { 37 | let cim = this; 38 | 39 | // Load default plugins. 40 | cim.plugins = _.map(require('./plugins.json'), function(plugin) { 41 | return new (require(plugin))(); 42 | }); 43 | 44 | // Search for 3rd party plugins. 45 | load_3rd_party_plugins(cim, done); 46 | } 47 | 48 | _run(done) { 49 | let cim = this; 50 | 51 | // 52 | // Compile all the hooks 53 | // 54 | var hooks = {}; 55 | _.forEach(cim.plugins, function(plugin) { 56 | hooks = _.mergeWith(hooks, plugin.hooks(), 57 | function(objValue, srcValue) { 58 | if (_.isArray(objValue)) { 59 | return objValue.concat(srcValue); 60 | } 61 | } 62 | ); 63 | }); 64 | 65 | // 66 | // Compile all the templates 67 | // 68 | _.forEach(cim.plugins, function(plugin) { 69 | if (!_.isNil(plugin.template()) && !_.isEmpty(plugin.template())) { 70 | cim.templates.push(plugin.template()); 71 | } 72 | }); 73 | 74 | // 75 | // Setup the commands within the default plugins. 76 | // 77 | 78 | // Init yargs. 79 | cim.yargs = require('yargs') 80 | .usage('cim [args]'); 81 | 82 | // Loop through and add all the commands 83 | _.forEach(cim.plugins, function (plugin) { 84 | _.forEach(plugin.commands(), function (command) { 85 | var cmd_str = command.command; 86 | if (!_.isEmpty(command.params)) { 87 | cmd_str += ' ' + _.map(_.keys(command.params), function (key) { 88 | return '[' + key + ']'; 89 | }).join(' '); 90 | } 91 | cim.yargs.command(cmd_str, command.description, command.params, function (args) { 92 | // Add the command to the args for easy reference later. 93 | args.command = command.command; 94 | cim.args = args; 95 | 96 | async.waterfall([ 97 | function (next) { 98 | // Run any 'before' hooks. 99 | cim._run_hooks(hooks, 'before', command.command, next); 100 | }, 101 | function (next) { 102 | // Run the command. 103 | //console.log(JSON.stringify(command, null, 3)); 104 | command.run(cim, next); 105 | }, 106 | function (next) { 107 | // Run any 'after' hooks. 108 | cim._run_hooks(hooks, 'after', command.command, next); 109 | } 110 | ], done); 111 | }); 112 | }); 113 | }); 114 | 115 | // Complete yargs 116 | cim.yargs.help() 117 | .argv; 118 | } 119 | 120 | _run_hooks(hooks, prefix, command, done) { 121 | let cim = this; 122 | const prefix_hooks = hooks[prefix+':'+command]; 123 | if (prefix_hooks && !_.isEmpty(prefix_hooks)) { 124 | async.eachSeries(prefix_hooks, function(hook, next) { 125 | hook(cim, next); 126 | }, done); 127 | } else { 128 | done(); 129 | } 130 | } 131 | 132 | } 133 | module.exports = CIM; -------------------------------------------------------------------------------- /lib/plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "./plugins/Plugin", 3 | "./plugins/aws/CloudFormation/CloudFormation", 4 | "./plugins/aws/ServerlessWebApp/index", 5 | "./plugins/aws/ServerlessApi/index", 6 | "./plugins/aws/Lambda/Lambda", 7 | "./plugins/aws/Lambda/nodejs/LambdaNode", 8 | "./plugins/aws/Lambda/nodejs/s3/LambdaNodeS3", 9 | "./plugins/aws/Lambda/nodejs/dynamodb/LambdaNodeDynamoDB", 10 | "./plugins/aws/Lambda/nodejs/kinesis/LambdaNodeKinesis", 11 | "./plugins/aws/Lambda/nodejs/sns/LambdaNodeSNS", 12 | "./plugins/aws/Lambda/nodejs/cloudwatch-cron/LambdaNodeCloudwatchCron", 13 | "./plugins/aws/Lambda/nodejs/cloudwatch-logs/LambdaNodeCloudwatchLogs", 14 | "./plugins/aws/VPC/index", 15 | "./plugins/aws/ECR/index", 16 | "./plugins/aws/ECS/index", 17 | "./plugins/aws/ECSService/index", 18 | "./plugins/aws/ECSServiceCI/index" 19 | ] -------------------------------------------------------------------------------- /lib/plugins/Plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Functions 4 | const create_fn = require('./lib/create'); 5 | const templates_fn = require('./lib/templates'); 6 | 7 | class Plugin { 8 | constructor() { 9 | Object.assign( 10 | this, 11 | create_fn, 12 | templates_fn); 13 | } 14 | 15 | /** 16 | * Get all the commands available for this plugin. 17 | * 18 | * @returns {Array} 19 | */ 20 | commands() { 21 | return [ 22 | { 23 | command: 'create', 24 | description: 'Create a new CIM project', 25 | params: { 26 | dir: { 27 | describe: 'The directory this command will create for your new CIM project.', 28 | required: false, 29 | default: process.cwd() 30 | }, 31 | template: { 32 | describe: 'The project template to use.', 33 | required: false, 34 | default: 'stack' 35 | } 36 | }, 37 | run: this.create 38 | }, 39 | { 40 | command: 'templates', 41 | description: 'Show the available templates', 42 | params: {}, 43 | run: this.templates 44 | } 45 | ] 46 | } 47 | 48 | before_templates(cim, done) { 49 | // Example hook. 50 | // Do something before the 'templates' command is executed. 51 | done(); 52 | } 53 | 54 | after_templates(cim, done) { 55 | // Example hook. 56 | // Do something after the 'templates' command is executed. 57 | done(); 58 | } 59 | 60 | hooks() { 61 | // Example hooks. 62 | return { 63 | 'before:templates': [ 64 | this.before_templates 65 | ], 66 | 'after:templates': [ 67 | this.after_templates 68 | ] 69 | }; 70 | } 71 | 72 | /** 73 | * Get the path to the template directory to copy during a 'create' command. 74 | * 75 | * @returns {null} 76 | */ 77 | template() { 78 | return null; 79 | } 80 | 81 | } 82 | module.exports = Plugin; -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/CloudFormation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Plugin = require('../../Plugin'); 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | 7 | // Functions 8 | const stack_up_fn = require('./lib/stack_up'); 9 | const stack_delete_fn = require('./lib/stack_delete'); 10 | const stack_show_fn = require('./lib/stack_show'); 11 | 12 | class CloudFormation extends Plugin { 13 | constructor() { 14 | super(); 15 | 16 | Object.assign( 17 | this, 18 | stack_up_fn, 19 | stack_delete_fn, 20 | stack_show_fn); 21 | } 22 | 23 | commands() { 24 | return [ 25 | { 26 | command: 'stack-up', 27 | description: 'Create or update the CloudFormation stack(s).', 28 | params: { 29 | dir: { 30 | describe: 'The directory this command will run in. Defaults to the current directory.', 31 | required: false, 32 | default: process.cwd() 33 | }, 34 | recursive: { 35 | describe: 'Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is \'false\'.', 36 | required: false, 37 | default: false 38 | }, 39 | stage: { 40 | describe: 'Create or update the stack(s) using the give stage.', 41 | required: false 42 | }, 43 | env: { 44 | describe: 'Create or update the stack(s) using the give env vars Ex. "key1=val1,key2=val2".', 45 | required: false 46 | }, 47 | profile: { 48 | describe: 'Your AWS credentials profile.', 49 | required: false 50 | } 51 | }, 52 | run: this.stack_up 53 | }, 54 | { 55 | command: 'stack-delete', 56 | description: 'Delete the CloudFormation stack(s).', 57 | params: { 58 | dir: { 59 | describe: 'The directory this command will run in. Defaults to the current directory.', 60 | required: false, 61 | default: process.cwd() 62 | }, 63 | recursive: { 64 | describe: 'Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is \'false\'.', 65 | required: false, 66 | default: false 67 | }, 68 | stage: { 69 | describe: 'Create or update the stack(s) using the give stage.', 70 | required: false 71 | }, 72 | profile: { 73 | describe: 'Your AWS credentials profile.', 74 | required: false 75 | } 76 | }, 77 | run: this.stack_delete 78 | }, 79 | { 80 | command: 'stack-show', 81 | description: 'Show the CloudFormation stack(s).', 82 | params: { 83 | dir: { 84 | describe: 'The directory this command will run in. Defaults to the current directory.', 85 | required: false, 86 | default: process.cwd() 87 | }, 88 | recursive: { 89 | describe: 'Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is \'false\'.', 90 | required: false, 91 | default: false 92 | }, 93 | stage: { 94 | describe: 'Create or update the stack(s) using the give stage.', 95 | required: false 96 | }, 97 | profile: { 98 | describe: 'Your AWS credentials profile.', 99 | required: false 100 | } 101 | }, 102 | run: this.stack_show 103 | } 104 | ]; 105 | } 106 | 107 | hooks() { 108 | return null; 109 | } 110 | 111 | template() { 112 | return { 113 | name: 'cloudformation', 114 | description: 'Basic CloudFormation setup.', 115 | path: path.resolve(__dirname, 'template') 116 | } 117 | } 118 | 119 | } 120 | module.exports = CloudFormation; -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/lib/stack_delete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require('async'); 4 | 5 | const configs = require('../../../../util/configs'); 6 | const cloudformation = require('../../../../util/cloudformation'); 7 | 8 | module.exports = { 9 | stack_delete(cim, done) { 10 | async.waterfall([ 11 | async.constant(cim), 12 | configs.load, 13 | function(stacks, next) { 14 | next(null, { stacks: stacks, params: cim.args}); 15 | }, 16 | cloudformation.delete 17 | ], done); 18 | } 19 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/lib/stack_show.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require('async'); 4 | 5 | const configs = require('../../../../util/configs'); 6 | const cloudformation = require('../../../../util/cloudformation'); 7 | 8 | module.exports = { 9 | stack_show(cim, done) { 10 | async.waterfall([ 11 | async.constant(cim), 12 | configs.load, 13 | function(stacks, next) { 14 | next(null, { stacks: stacks, params: cim.args}); 15 | }, 16 | cloudformation.show 17 | ], done); 18 | } 19 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/lib/stack_up.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require('async'); 4 | 5 | const configs = require('../../../../util/configs'); 6 | const cloudformation = require('../../../../util/cloudformation'); 7 | 8 | module.exports = { 9 | stack_up(cim, done) { 10 | async.waterfall([ 11 | async.constant(cim), 12 | configs.load, 13 | function(stacks, next) { 14 | next(null, { stacks: stacks, params: cim.args}); 15 | }, 16 | cloudformation.stackup 17 | ], done); 18 | } 19 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-stack # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | # Stacks are uploaded here prior to deployment. The bucket is created if it doesn't exist. 7 | # The name can be the same for all your _cim.yml files. 8 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. 9 | 10 | # 11 | # Reference parent stacks fo included shared information like stack name. 12 | # 13 | # parents: 14 | # vpc: '../vpc' 15 | 16 | # 17 | # Define stack input parameters. 18 | # 19 | # parameters: 20 | # VpcStackName: '${stack.parents.vpc.stack.name}' 21 | 22 | # 23 | # Define stack capabilities required. 24 | # 25 | # capabilities: 26 | # - 'CAPABILITY_IAM' 27 | 28 | # 29 | # Define global tags. 30 | # 31 | # tags: 32 | # app: 'cim-stack' 33 | -------------------------------------------------------------------------------- /lib/plugins/aws/CloudFormation/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Add your CloudFormation resources here... 18 | # 19 | 20 | # 21 | # S3 bucket 22 | # 23 | ResourcesS3Bucket: 24 | Type: 'AWS::S3::Bucket' 25 | Properties: 26 | AccessControl: 'Private' 27 | 28 | # 29 | # Outputs to be used by other CloudFormation templates if needed. 30 | # 31 | Outputs: 32 | ResourcesS3Bucket: 33 | Description: Resources S3 Bucket 34 | Value: !Ref ResourcesS3Bucket 35 | Export: 36 | Name: !Sub '${AWS::StackName}-ResourcesBucket' 37 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECR/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ECR { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'ecr', 20 | description: 'ECR - AWS Docker Container Registry.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ECR; -------------------------------------------------------------------------------- /lib/plugins/aws/ECR/template/README.md: -------------------------------------------------------------------------------- 1 | # ECR 2 | Creates the ECR repository for each service. 3 | 4 | For each `service` you will need to create a `AWS::ECR::Repository` within the cloudformation.yml. 5 | 6 | The `Service1RepositoryUrl` output parameter is used when uploading docker images. An image is required prior to deploying the `ecs` stack. 7 | 8 | You also need to update the `policy` for each Repository. Give permission to only those users who need it. 9 | 10 | See the [Services Stack](https://github.com/thestackshack/services-stack) example to see how the VPC, ECR, ECS, and this Service example all fit together. 11 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECR/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: ecr # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | parameters: 18 | Service1Name: 'services-stack/service1' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | # capabilities: 24 | # - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECR/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Parameters: 3 | Service1Name: 4 | Type: String 5 | Description: ECR Repository Name 6 | 7 | 8 | Resources: 9 | 10 | Repository: 11 | Type: AWS::ECR::Repository 12 | Properties: 13 | RepositoryName: !Ref Service1Name 14 | RepositoryPolicyText: 15 | Version: "2012-10-17" 16 | Statement: 17 | - 18 | Sid: AllowPushPull 19 | Effect: Allow 20 | Principal: 21 | AWS: 22 | - !Sub "arn:aws:iam::${AWS::AccountId}:user/user1" 23 | Action: 24 | - "ecr:GetDownloadUrlForLayer" 25 | - "ecr:BatchGetImage" 26 | - "ecr:BatchCheckLayerAvailability" 27 | - "ecr:PutImage" 28 | - "ecr:InitiateLayerUpload" 29 | - "ecr:UploadLayerPart" 30 | - "ecr:CompleteLayerUpload" 31 | 32 | Outputs: 33 | Service1Repository: 34 | Value: !Ref 'Repository' 35 | Export: 36 | Name: !Sub '${AWS::StackName}-Service1Repository' 37 | Service1RepositoryUrl: 38 | Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}" 39 | Export: 40 | Name: !Sub '${AWS::StackName}-Service1RepositoryUrl' 41 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECS/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ECS { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'ecs', 20 | description: 'ECS - AWS EC2 Docker Container Service.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ECS; -------------------------------------------------------------------------------- /lib/plugins/aws/ECS/template/README.md: -------------------------------------------------------------------------------- 1 | # AWS EC2 Container Service 2 | Creates the ECS cluster and ALB. 3 | 4 | Services are deployed into private subnets and the ALB is deployed into public subnets. 5 | 6 | A custom domain name and SSL are optional. 7 | 8 | See the [Services Stack](https://github.com/thestackshack/services-stack) example to see how the VPC, ECR, ECS, and this Service example all fit together. 9 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECS/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: services-stack-ecs # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | parents: 12 | vpc: '../../vpc' # Path to your VPC stack. This isn't required. You could also hard code the VPC params below. 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | parameters: 18 | VPCStack: '${stack.parents.vpc.stack.name}' 19 | KeyPairName: '${stack.parents.vpc.stack.parameters.KeyPairName}' 20 | #TLD: 'example.com' 21 | #Domain: 'api.example.com' 22 | 23 | # 24 | # Define stack capabilities required. 25 | # 26 | capabilities: 27 | - 'CAPABILITY_IAM' 28 | 29 | # 30 | # Define global tags. 31 | # 32 | # tags: 33 | # app: 'cim-stack' 34 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECS/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Parameters: 3 | KeyPairName: 4 | Type: AWS::EC2::KeyPair::KeyName 5 | Description: Name of an existing EC2 KeyPair to enable SSH access to the ECS instances. 6 | 7 | VPCStack: 8 | Type: String 9 | Description: VPC Stack Name 10 | 11 | DesiredCapacity: 12 | Type: Number 13 | Default: '2' 14 | Description: Number of instances to launch in your ECS cluster. 15 | 16 | MaxSize: 17 | Type: Number 18 | Default: '2' 19 | Description: Maximum number of instances that can be launched in your ECS cluster. 20 | 21 | InstanceType: 22 | Description: EC2 instance type 23 | Type: String 24 | Default: t2.micro 25 | AllowedValues: [t2.micro, t2.small, t2.medium, t2.large, m3.medium, m3.large, 26 | m3.xlarge, m3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge, 27 | c4.large, c4.xlarge, c4.2xlarge, c4.4xlarge, c4.8xlarge, c3.large, c3.xlarge, 28 | c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge, 29 | r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge] 30 | ConstraintDescription: Please choose a valid instance type. 31 | 32 | TLD: 33 | Type: String 34 | Description: TLD name needed by Route53 to perform DNS (example.com) 35 | Default: '' 36 | 37 | Domain: 38 | Type: String 39 | Description: Domain name for your api (api.example.com) 40 | Default: '' 41 | 42 | Conditions: 43 | UseCustomDomain: !And 44 | - !Not [!Equals [!Ref TLD, '']] 45 | - !Not [!Equals [!Ref Domain, '']] 46 | 47 | Mappings: 48 | 49 | # These are the latest ECS optimized AMIs as of August 2017: 50 | # 51 | # amzn-ami-2017.03.f-amazon-ecs-optimized 52 | # ECS agent: 1.14.4 53 | # Docker: 17.03.2-ce 54 | # ecs-init: 1.14.4-1 55 | # 56 | # You can find the latest available on this page of our documentation: 57 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html 58 | # (note the AMI identifier is region specific) 59 | 60 | AWSRegionToAMI: 61 | us-east-2: 62 | AMI: ami-34032e51 63 | us-east-1: 64 | AMI: ami-ec33cc96 65 | us-west-2: 66 | AMI: ami-29f80351 67 | us-west-1: 68 | AMI: ami-d5d0e0b5 69 | eu-west-2: 70 | AMI: ami-eb62708f 71 | eu-west-1: 72 | AMI: ami-13f7226a 73 | eu-central-1: 74 | AMI: ami-40d5672f 75 | ap-northeast-1: 76 | AMI: ami-21815747 77 | ap-southeast-2: 78 | AMI: ami-4f08e82d 79 | ap-southeast-1: 80 | AMI: ami-99f588fa 81 | ca-central-1: 82 | AMI: ami-9b54edff 83 | 84 | Resources: 85 | 86 | # 87 | # Security Groups 88 | # 89 | 90 | # This security group defines who/where is allowed to access the Application Load Balancer. 91 | # By default, we've opened this up to the public internet (0.0.0.0/0) but can you restrict 92 | # it further if you want. 93 | LoadBalancerSecurityGroup: 94 | Type: AWS::EC2::SecurityGroup 95 | Properties: 96 | VpcId: 97 | Fn::ImportValue: 98 | !Sub "${VPCStack}-VPCID" 99 | GroupDescription: Access to the load balancer that sits in front of ECS 100 | SecurityGroupIngress: 101 | # Allow access from anywhere to our ECS services 102 | - CidrIp: 0.0.0.0/0 103 | IpProtocol: -1 104 | 105 | # This security group defines who/where is allowed to access the ECS hosts directly. 106 | # By default we're just allowing access from the load balancer. If you want to SSH 107 | # into the hosts, or expose non-load balanced services you can open their ports here. 108 | ECSHostSecurityGroup: 109 | Type: AWS::EC2::SecurityGroup 110 | Properties: 111 | VpcId: 112 | Fn::ImportValue: 113 | !Sub "${VPCStack}-VPCID" 114 | GroupDescription: Access to the ECS hosts and the tasks/containers that run on them 115 | SecurityGroupIngress: 116 | # Only allow inbound access to ECS from the ELB 117 | - SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup 118 | IpProtocol: -1 119 | 120 | # 121 | # SSL 122 | # 123 | 124 | # 125 | # SSL Certificate needed by CloudFront. 126 | # 127 | SSL: 128 | Type: AWS::CertificateManager::Certificate 129 | Condition: UseCustomDomain 130 | Properties: 131 | DomainName: !Ref Domain 132 | DomainValidationOptions: 133 | - DomainName: !Ref Domain 134 | ValidationDomain: !Ref TLD 135 | 136 | # 137 | # Load Balancers 138 | # 139 | 140 | LoadBalancer: 141 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 142 | Properties: 143 | Name: !Ref AWS::StackName 144 | Subnets: 145 | - Fn::ImportValue: 146 | !Sub "${VPCStack}-PublicSubnet1ID" 147 | - Fn::ImportValue: 148 | !Sub "${VPCStack}-PublicSubnet2ID" 149 | SecurityGroups: 150 | - !Ref LoadBalancerSecurityGroup 151 | 152 | # We define a default target group here, as this is a mandatory Parameters 153 | # when creating an Application Load Balancer Listener. This is not used, instead 154 | # a target group is created per-service in each service template (../services/*) 155 | DefaultTargetGroup: 156 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 157 | Properties: 158 | Name: !Sub ${AWS::StackName}-default 159 | VpcId: 160 | Fn::ImportValue: 161 | !Sub "${VPCStack}-VPCID" 162 | Port: 80 163 | Protocol: HTTP 164 | 165 | LoadBalancerListener: 166 | Type: AWS::ElasticLoadBalancingV2::Listener 167 | Properties: 168 | LoadBalancerArn: !Ref LoadBalancer 169 | Port: !If [UseCustomDomain, 443, 80] 170 | Protocol: !If [UseCustomDomain, 'HTTPS', 'HTTP'] 171 | Certificates: !If [UseCustomDomain, [ {CertificateArn: !Ref SSL } ], []] 172 | DefaultActions: 173 | - Type: forward 174 | TargetGroupArn: !Ref DefaultTargetGroup 175 | 176 | # 177 | # Route53 DNS record set to map our domain to API Gateway 178 | # 179 | DomainDNS: 180 | Type: AWS::Route53::RecordSetGroup 181 | Condition: UseCustomDomain 182 | Properties: 183 | HostedZoneName: 184 | Fn::Join: 185 | - '' 186 | - - !Ref TLD 187 | - '.' 188 | RecordSets: 189 | - 190 | Name: !Ref Domain 191 | Type: 'A' 192 | AliasTarget: 193 | HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID 194 | DNSName: !GetAtt LoadBalancer.DNSName 195 | 196 | # 197 | # ECS 198 | # 199 | 200 | # ECS/EC2 Roles 201 | # 202 | # This IAM Role is attached to all of the ECS hosts. It is based on the default role 203 | # published here: 204 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html 205 | # 206 | # You can add other IAM policy statements here to allow access from your ECS hosts 207 | # to other AWS services. Please note that this role will be used by ALL containers 208 | # running on the ECS host. 209 | 210 | ECSRole: 211 | Type: AWS::IAM::Role 212 | Properties: 213 | AssumeRolePolicyDocument: 214 | Statement: 215 | - Effect: Allow 216 | Principal: 217 | Service: [ec2.amazonaws.com] 218 | Action: ['sts:AssumeRole'] 219 | Path: / 220 | Policies: 221 | - PolicyName: ecs-service 222 | PolicyDocument: 223 | Statement: 224 | - Effect: Allow 225 | Action: 226 | - 'ecs:CreateCluster' 227 | - 'ecs:DeregisterContainerInstance' 228 | - 'ecs:DiscoverPollEndpoint' 229 | - 'ecs:Poll' 230 | - 'ecs:RegisterContainerInstance' 231 | - 'ecs:StartTelemetrySession' 232 | - 'ecs:Submit*' 233 | - 'logs:CreateLogStream' 234 | - 'logs:PutLogEvents' 235 | - 'ecr:BatchCheckLayerAvailability' 236 | - 'ecr:BatchGetImage' 237 | - 'ecr:GetDownloadUrlForLayer' 238 | - 'ecr:GetAuthorizationToken' 239 | Resource: '*' 240 | 241 | ECSInstanceProfile: 242 | Type: AWS::IAM::InstanceProfile 243 | Properties: 244 | Path: / 245 | Roles: 246 | - !Ref ECSRole 247 | 248 | # ECS Cluster 249 | ECSCluster: 250 | Type: AWS::ECS::Cluster 251 | Properties: 252 | ClusterName: !Ref AWS::StackName 253 | 254 | ECSLaunchConfiguration: 255 | Type: AWS::AutoScaling::LaunchConfiguration 256 | Properties: 257 | ImageId: !FindInMap [AWSRegionToAMI, !Ref 'AWS::Region', AMI] 258 | SecurityGroups: 259 | - !Ref ECSHostSecurityGroup 260 | InstanceType: !Ref InstanceType 261 | IamInstanceProfile: !Ref ECSInstanceProfile 262 | KeyName: !Ref 'KeyPairName' 263 | UserData: 264 | Fn::Base64: !Sub | 265 | #!/bin/bash -xe 266 | echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config 267 | yum install -y aws-cfn-bootstrap 268 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} 269 | 270 | ECSAutoScalingGroup: 271 | Type: AWS::AutoScaling::AutoScalingGroup 272 | Properties: 273 | VPCZoneIdentifier: 274 | - Fn::ImportValue: 275 | !Sub "${VPCStack}-PrivateSubnet1AID" 276 | - Fn::ImportValue: 277 | !Sub "${VPCStack}-PrivateSubnet2AID" 278 | LaunchConfigurationName: !Ref ECSLaunchConfiguration 279 | MinSize: '1' 280 | MaxSize: !Ref MaxSize 281 | DesiredCapacity: !Ref DesiredCapacity 282 | CreationPolicy: 283 | ResourceSignal: 284 | Timeout: PT15M 285 | UpdatePolicy: 286 | AutoScalingReplacingUpdate: 287 | WillReplace: 'true' 288 | 289 | Outputs: 290 | ECSCluster: 291 | Value: !Ref 'ECSCluster' 292 | Export: 293 | Name: !Sub '${AWS::StackName}-ECSCluster' 294 | LoadBalancerListener: 295 | Value: !Ref 'LoadBalancerListener' 296 | Export: 297 | Name: !Sub '${AWS::StackName}-LoadBalancerListener' 298 | CustomDomainUrl: 299 | Description: URL of your API endpoint 300 | Condition: UseCustomDomain 301 | Value: !Join 302 | - '' 303 | - - 'https://' 304 | - !Ref Domain 305 | - '/' 306 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ECSService { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'ecs-service', 20 | description: 'Example ECS Service.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ECSService; -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/README.md: -------------------------------------------------------------------------------- 1 | # AWS EC2 Docker Container Service Example 2 | See the [Services Stack](https://github.com/thestackshack/services-stack) example to see how the VPC, ECR, ECS, and this Service example all fit together. 3 | ## Push Image 4 | - [Registry Authentication](http://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) 5 | - `aws ecr get-login --registry-ids ` 6 | - copy/past output to perform docker login, also append `/services-stack/service1` to the repository url. 7 | - Build Image 8 | - `docker build -t service1: .` 9 | - [Push Image](http://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html) 10 | - `docker tag service1: .dkr.ecr..amazonaws.com/services-stack/service1` 11 | - `docker tag service1: .dkr.ecr..amazonaws.com/services-stack/service1:` 12 | - `docker push .dkr.ecr..amazonaws.com/services-stack/service1` 13 | 14 | ## Update Version 15 | Make sure the `Version` parameter, in _cim.yml, matches the `version` tag from above. The ECS Task Definition will pull the image from ECR. 16 | 17 | Once the `Version` is set you can use `cim stack-up` to update the stack with the new version. 18 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: ecs-service1 # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | parents: 12 | vpc: '../../vpc' 13 | ecs: '../ecs' 14 | ecr: '../ecr' 15 | 16 | # 17 | # Define stack input parameters. 18 | # 19 | parameters: 20 | VPCStack: '${stack.parents.vpc.stack.name}' 21 | ECSStack: '${stack.parents.ecs.stack.name}' 22 | ECRStack: '${stack.parents.ecr.stack.name}' 23 | Path: '/service1' 24 | Version: '1.0.0' 25 | 26 | # 27 | # Define stack capabilities required. 28 | # 29 | capabilities: 30 | - 'CAPABILITY_IAM' 31 | - 'CAPABILITY_NAMED_IAM' 32 | 33 | # 34 | # Define global tags. 35 | # 36 | # tags: 37 | # app: 'cim-stack' 38 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Parameters: 3 | VPCStack: 4 | Type: String 5 | Description: VPC Stack Name 6 | ECSStack: 7 | Type: String 8 | Description: ECS Stack Name 9 | ECRStack: 10 | Type: String 11 | Description: ECR Stack Name 12 | DesiredCount: 13 | Type: Number 14 | Default: '2' 15 | Description: Desired task count 16 | Path: 17 | Type: String 18 | Default: '/service' 19 | Description: Service path 20 | Version: 21 | Type: String 22 | Default: 'latest' 23 | Description: Service version 24 | Resources: 25 | 26 | CloudWatchLogsGroup: 27 | Type: AWS::Logs::LogGroup 28 | Properties: 29 | LogGroupName: !Ref AWS::StackName 30 | RetentionInDays: 365 31 | 32 | # This IAM Role grants the service access to register/unregister with the 33 | # Application Load Balancer (ALB). It is based on the default documented here: 34 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_IAM_role.html 35 | ServiceRole: 36 | Type: AWS::IAM::Role 37 | Properties: 38 | RoleName: !Sub ecs-service-${AWS::StackName} 39 | Path: / 40 | AssumeRolePolicyDocument: | 41 | { 42 | "Statement": [{ 43 | "Effect": "Allow", 44 | "Principal": { "Service": [ "ecs.amazonaws.com" ]}, 45 | "Action": [ "sts:AssumeRole" ] 46 | }] 47 | } 48 | Policies: 49 | - PolicyName: !Sub ecs-service-${AWS::StackName} 50 | PolicyDocument: 51 | { 52 | "Version": "2012-10-17", 53 | "Statement": [{ 54 | "Effect": "Allow", 55 | "Action": [ 56 | "ec2:AuthorizeSecurityGroupIngress", 57 | "ec2:Describe*", 58 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", 59 | "elasticloadbalancing:Describe*", 60 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer", 61 | "elasticloadbalancing:DeregisterTargets", 62 | "elasticloadbalancing:DescribeTargetGroups", 63 | "elasticloadbalancing:DescribeTargetHealth", 64 | "elasticloadbalancing:RegisterTargets" 65 | ], 66 | "Resource": "*" 67 | }] 68 | } 69 | 70 | TaskDefinition: 71 | Type: AWS::ECS::TaskDefinition 72 | Properties: 73 | Family: !Ref AWS::StackName 74 | ContainerDefinitions: 75 | - Name: !Ref AWS::StackName 76 | Essential: true 77 | Image: !Sub 78 | - "${URL}:${Version}" 79 | - { 80 | URL: { "Fn::ImportValue" : {"Fn::Sub": "${ECRStack}-Service1RepositoryUrl" } }, 81 | Version: !Ref Version 82 | } 83 | Memory: 128 84 | PortMappings: 85 | - ContainerPort: 8000 86 | LogConfiguration: 87 | LogDriver: awslogs 88 | Options: 89 | awslogs-group: !Ref AWS::StackName 90 | awslogs-region: !Ref AWS::Region 91 | 92 | TargetGroup: 93 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 94 | Properties: 95 | VpcId: 96 | Fn::ImportValue: 97 | !Sub "${VPCStack}-VPCID" 98 | Port: 80 99 | Protocol: HTTP 100 | Matcher: 101 | HttpCode: 200-299 102 | HealthCheckIntervalSeconds: 10 103 | HealthCheckPath: '/' 104 | HealthCheckProtocol: HTTP 105 | HealthCheckTimeoutSeconds: 5 106 | HealthyThresholdCount: 2 107 | 108 | ListenerRule: 109 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 110 | Properties: 111 | ListenerArn: 112 | Fn::ImportValue: 113 | !Sub "${ECSStack}-LoadBalancerListener" 114 | Priority: 2 115 | Conditions: 116 | - Field: path-pattern 117 | Values: 118 | - !Ref Path 119 | Actions: 120 | - TargetGroupArn: !Ref TargetGroup 121 | Type: forward 122 | 123 | Service: 124 | Type: AWS::ECS::Service 125 | Properties: 126 | Cluster: 127 | Fn::ImportValue: 128 | !Sub "${ECSStack}-ECSCluster" 129 | Role: !Ref ServiceRole 130 | DesiredCount: !Ref DesiredCount 131 | TaskDefinition: !Ref TaskDefinition 132 | DeploymentConfiguration: 133 | MinimumHealthyPercent: 50 134 | LoadBalancers: 135 | - ContainerName: !Ref AWS::StackName 136 | ContainerPort: 8000 137 | TargetGroupArn: !Ref TargetGroup 138 | 139 | Outputs: 140 | Service: 141 | Value: !Ref 'Service' 142 | Export: 143 | Name: !Sub '${AWS::StackName}-Service' 144 | TaskDefinition: 145 | Value: !Ref 'TaskDefinition' 146 | Export: 147 | Name: !Sub '${AWS::StackName}-TaskDefinition' 148 | CloudWatchLogsGroup: 149 | Value: !Ref 'CloudWatchLogsGroup' 150 | Export: 151 | Name: !Sub '${AWS::StackName}-CloudWatchLogsGroup' -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/src/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package.json . 8 | # For npm@5 or later, copy package-lock.json as well 9 | # COPY package.json package-lock.json ./ 10 | 11 | RUN npm install 12 | 13 | # Bundle app source 14 | COPY . . 15 | 16 | EXPOSE 8000 17 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker_web_app", 3 | "version": "1.0.0", 4 | "description": "Node.js on Docker", 5 | "author": "First Last ", 6 | "main": "server.js", 7 | "scripts": { 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "express": "^4.13.3" 12 | } 13 | } -------------------------------------------------------------------------------- /lib/plugins/aws/ECSService/template/src/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | // Constants 6 | const PORT = 8000; 7 | const HOST = '0.0.0.0'; 8 | 9 | // App 10 | const app = express(); 11 | app.get('/*', (req, res) => { 12 | res.send('Thanks for using CIM! '+req.path+'\n'); 13 | }); 14 | 15 | app.listen(PORT, HOST); 16 | console.log(`Running on http://${HOST}:${PORT}`); -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ECSServiceCI { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'ecs-service-ci', 20 | description: 'Example ECS Service with continuous integration.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ECSServiceCI; -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/.dockerignore: -------------------------------------------------------------------------------- 1 | src/node_modules 2 | src/npm-debug.log -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY src/package.json . 8 | # For npm@5 or later, copy package-lock.json as well 9 | # COPY package.json package-lock.json ./ 10 | 11 | RUN npm install 12 | 13 | # Bundle app source 14 | COPY src/* ./ 15 | 16 | EXPOSE 8000 17 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/README.md: -------------------------------------------------------------------------------- 1 | # ECS Service with Continuous Integration 2 | Use this template to add continuous integration to your ECS service. 3 | 4 | When you push your commits CodePipeline and CodeBuild will build and deploy a new image to your ECR docker registry. 5 | 6 | You can then deploy that new version to your service. 7 | 8 | This project should be in its own GitHub repo. 9 | 10 | ## Setup 11 | See the [Services Stack](https://github.com/thestackshack/services-stack) for setup instructions. 12 | 13 | Before you `stack-up` this stack you need to push an image to your ECR. 14 | 15 | ## Stack Up 16 | ``` 17 | cim stack-up --profile=bluefin --env="version=" 18 | ``` 19 | 20 | ## Deploy new version 21 | After you push your commits, a new image will be built and pushed to your ECR repo. To deploy that new version to your ECS service, run the `stack-up` command with the new version. 22 | ``` 23 | cim stack-up --profile=bluefin --env="version=" 24 | ``` 25 | 26 | ## Build Notifications 27 | You can enable build notification via the `NotificationEmail` and/or `NotificationSMS` input params. You also need to un-comment the `sns publish` command in the buildspec.yml. 28 | 29 | When set you will receive a notification when your new ECR images are ready. -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: services-stack-service1 # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Define stack input parameters. 10 | # 11 | parameters: 12 | VPCStack: 'services-stack-vpc' 13 | ECSStack: 'services-stack-ecs' 14 | ECRStack: 'services-stack-ecr' 15 | Path: '/service1' 16 | Version: '${env.version}' 17 | 18 | # CI/CD 19 | GitHubOwner: 'thestackshack' 20 | GitHubRepo: 'ecs-service-cicd' 21 | GitHubToken: '${kms.decrypt(AQICAHgiu9XuQb4FZRXrLn/77g1P99ZhS7/g3xOsvbvNpb+/qQH+sxP+if0SN0/QR0I3M9ehAAAAhzCBhAYJKoZIhvcNAQcGoHcwdQIBADBwBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNTJCu1YrFM64MUL4AIBEIBD8zDL0Oc+8rQwr/7fJq+NyPB7vKJ/lNqqcmBBN9QS8XDFAqB9Vh9fkCUSilXs3HG3NL6EdLYaR9Z5blo7p2/HTmJrnw==)}' 22 | GitHubBranch: 'master' 23 | 24 | # Build Notification 25 | # NotificationEmail: 'xxxx@gmail.com' 26 | # NotificationSMS: '1-410-336-xxxx' 27 | 28 | # 29 | # Define stack capabilities required. 30 | # 31 | capabilities: 32 | - 'CAPABILITY_IAM' 33 | - 'CAPABILITY_NAMED_IAM' 34 | 35 | # 36 | # Define global tags. 37 | # 38 | # tags: 39 | # app: 'cim-stack' 40 | -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | phases: 3 | pre_build: 4 | commands: 5 | - $(aws ecr get-login) 6 | build: 7 | commands: 8 | - docker build --tag "$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" . 9 | post_build: 10 | commands: 11 | - docker tag "$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" "${REPOSITORY_URI}" 12 | - docker tag "$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" "${REPOSITORY_URI}:$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" 13 | - docker push "${REPOSITORY_URI}" 14 | # Uncomment if you wish to enable build notifications. You must also uncomment the 'NotificationEmail' and/or 'NotificationSMS' in _cim.yml. 15 | #- aws sns publish --topic-arn ${SNS_TOPIC_ARN} --message "${APP} version $(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8) is ready." --subject "${APP} version $(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8) is ready." -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Parameters: 3 | VPCStack: 4 | Type: String 5 | Description: VPC Stack Name 6 | ECSStack: 7 | Type: String 8 | Description: ECS Stack Name 9 | ECRStack: 10 | Type: String 11 | Description: ECR Stack Name 12 | DesiredCount: 13 | Type: Number 14 | Default: '2' 15 | Description: Desired task count 16 | Path: 17 | Type: String 18 | Default: '/service' 19 | Description: Service path 20 | Version: 21 | Type: String 22 | Default: 'latest' 23 | Description: Service version 24 | GitHubOwner: 25 | Type: String 26 | Description: GitHub repository owner 27 | Default: '' 28 | GitHubRepo: 29 | Type: String 30 | Default: aws-codepipeline-nested-stack 31 | Description: GitHub repository name 32 | Default: '' 33 | GitHubToken: 34 | Type: String 35 | Description: GitHub repository OAuth token 36 | Default: '' 37 | GitHubBranch: 38 | Type: String 39 | Description: GitHub repository branch 40 | Default: '' 41 | NotificationEmail: 42 | Type: String 43 | Description: Build notification email endpoint 44 | Default: '' 45 | NotificationSMS: 46 | Type: String 47 | Description: Build notification sns endpoint 48 | Default: '' 49 | 50 | Conditions: 51 | NeedsCICD: !And 52 | - !Not [!Equals [!Ref GitHubOwner, '']] 53 | - !Not [!Equals [!Ref GitHubRepo, '']] 54 | - !Not [!Equals [!Ref GitHubToken, '']] 55 | - !Not [!Equals [!Ref GitHubBranch, '']] 56 | NeedsBuildNotification: !Or 57 | - !Not [!Equals [!Ref NotificationEmail, '']] 58 | - !Not [!Equals [!Ref NotificationSMS, '']] 59 | NeedsBuildNotificationEmail: !Not [!Equals [!Ref NotificationEmail, '']] 60 | NeedsBuildNotificationSMS: !Not [!Equals [!Ref NotificationSMS, '']] 61 | 62 | Resources: 63 | 64 | CloudWatchLogsGroup: 65 | Type: AWS::Logs::LogGroup 66 | Properties: 67 | LogGroupName: !Ref AWS::StackName 68 | RetentionInDays: 365 69 | 70 | # This IAM Role grants the service access to register/unregister with the 71 | # Application Load Balancer (ALB). It is based on the default documented here: 72 | # http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_IAM_role.html 73 | ServiceRole: 74 | Type: AWS::IAM::Role 75 | Properties: 76 | RoleName: !Sub ecs-service-${AWS::StackName} 77 | Path: / 78 | AssumeRolePolicyDocument: | 79 | { 80 | "Statement": [{ 81 | "Effect": "Allow", 82 | "Principal": { "Service": [ "ecs.amazonaws.com" ]}, 83 | "Action": [ "sts:AssumeRole" ] 84 | }] 85 | } 86 | Policies: 87 | - PolicyName: !Sub ecs-service-${AWS::StackName} 88 | PolicyDocument: 89 | { 90 | "Version": "2012-10-17", 91 | "Statement": [{ 92 | "Effect": "Allow", 93 | "Action": [ 94 | "ec2:AuthorizeSecurityGroupIngress", 95 | "ec2:Describe*", 96 | "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", 97 | "elasticloadbalancing:Describe*", 98 | "elasticloadbalancing:RegisterInstancesWithLoadBalancer", 99 | "elasticloadbalancing:DeregisterTargets", 100 | "elasticloadbalancing:DescribeTargetGroups", 101 | "elasticloadbalancing:DescribeTargetHealth", 102 | "elasticloadbalancing:RegisterTargets" 103 | ], 104 | "Resource": "*" 105 | }] 106 | } 107 | 108 | TaskDefinition: 109 | Type: AWS::ECS::TaskDefinition 110 | Properties: 111 | Family: !Ref AWS::StackName 112 | ContainerDefinitions: 113 | - Name: !Ref AWS::StackName 114 | Essential: true 115 | Image: !Sub 116 | - "${URL}:${Version}" 117 | - { 118 | URL: { "Fn::ImportValue" : {"Fn::Sub": "${ECRStack}-Service1RepositoryUrl" } }, 119 | Version: { "Ref": "Version" } 120 | } 121 | Memory: 128 122 | PortMappings: 123 | - ContainerPort: 8000 124 | LogConfiguration: 125 | LogDriver: awslogs 126 | Options: 127 | awslogs-group: !Ref AWS::StackName 128 | awslogs-region: !Ref AWS::Region 129 | 130 | TargetGroup: 131 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 132 | Properties: 133 | VpcId: 134 | Fn::ImportValue: 135 | !Sub "${VPCStack}-VPCID" 136 | Port: 80 137 | Protocol: HTTP 138 | Matcher: 139 | HttpCode: 200-299 140 | HealthCheckIntervalSeconds: 10 141 | HealthCheckPath: '/' 142 | HealthCheckProtocol: HTTP 143 | HealthCheckTimeoutSeconds: 5 144 | HealthyThresholdCount: 2 145 | 146 | ListenerRule: 147 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 148 | Properties: 149 | ListenerArn: 150 | Fn::ImportValue: 151 | !Sub "${ECSStack}-LoadBalancerListener" 152 | Priority: 2 153 | Conditions: 154 | - Field: path-pattern 155 | Values: 156 | - !Ref Path 157 | Actions: 158 | - TargetGroupArn: !Ref TargetGroup 159 | Type: forward 160 | 161 | Service: 162 | Type: AWS::ECS::Service 163 | Properties: 164 | Cluster: 165 | Fn::ImportValue: 166 | !Sub "${ECSStack}-ECSCluster" 167 | Role: !Ref ServiceRole 168 | DesiredCount: !Ref DesiredCount 169 | TaskDefinition: !Ref TaskDefinition 170 | DeploymentConfiguration: 171 | MinimumHealthyPercent: 50 172 | LoadBalancers: 173 | - ContainerName: !Ref AWS::StackName 174 | ContainerPort: 8000 175 | TargetGroupArn: !Ref TargetGroup 176 | 177 | # 178 | # CI/CD 179 | # 180 | ArtifactsBucket: 181 | Type: AWS::S3::Bucket 182 | Condition: NeedsCICD 183 | Properties: 184 | AccessControl: Private 185 | 186 | # 187 | # CodeBuild Permissions 188 | # 189 | CodeBuildRole: 190 | Type: AWS::IAM::Role 191 | Condition: NeedsCICD 192 | Properties: 193 | AssumeRolePolicyDocument: 194 | Version: 2012-10-17 195 | Statement: 196 | - Effect: Allow 197 | Action: 198 | - sts:AssumeRole 199 | Principal: 200 | Service: 201 | - codebuild.amazonaws.com 202 | 203 | CodeBuildRolePolicy: 204 | Type: AWS::IAM::Policy 205 | Condition: NeedsCICD 206 | Properties: 207 | PolicyName: CodeBuildRolePolicy 208 | PolicyDocument: 209 | Version: 2012-10-17 210 | Statement: 211 | - Effect: Allow 212 | Action: 213 | - logs:CreateLogGroup 214 | - logs:CreateLogStream 215 | - logs:PutLogEvents 216 | Resource: '*' 217 | - Effect: Allow 218 | Action: 219 | - s3:* 220 | Resource: 221 | - !Sub 'arn:aws:s3:::${ArtifactsBucket}' 222 | - !Sub 'arn:aws:s3:::${ArtifactsBucket}/*' 223 | - Effect: Allow 224 | Action: 225 | - ecr:GetAuthorizationToken 226 | Resource: '*' 227 | - Effect: Allow 228 | Action: 229 | - ecr:GetDownloadUrlForLayer 230 | - ecr:BatchGetImage 231 | - ecr:BatchCheckLayerAvailability 232 | - ecr:PutImage 233 | - ecr:InitiateLayerUpload 234 | - ecr:UploadLayerPart 235 | - ecr:CompleteLayerUpload 236 | Resource: !Sub 237 | - "arn:aws:ecr:${Region}:${AccountId}:repository/${Repository}" 238 | - { 239 | Region: { "Ref": "AWS::Region" }, 240 | AccountId: { "Ref": "AWS::AccountId" }, 241 | Repository: { "Fn::ImportValue" : {"Fn::Sub": "${ECRStack}-Service1Repository" } } 242 | } 243 | - Effect: Allow 244 | Action: 245 | - sns:Publish 246 | Resource: !Sub 'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:*' 247 | Roles: 248 | - !Ref CodeBuildRole 249 | 250 | # 251 | # CodeBuild 252 | # 253 | CodeBuildProject: 254 | Type: AWS::CodeBuild::Project 255 | Condition: NeedsCICD 256 | Properties: 257 | Artifacts: 258 | Type: CODEPIPELINE 259 | Environment: 260 | ComputeType: BUILD_GENERAL1_SMALL 261 | Image: aws/codebuild/docker:1.12.1 262 | Type: LINUX_CONTAINER 263 | EnvironmentVariables: 264 | - Name: AWS_DEFAULT_REGION 265 | Value: !Ref AWS::Region 266 | - Name: REPOSITORY_URI 267 | Value: { "Fn::ImportValue" : {"Fn::Sub": "${ECRStack}-Service1RepositoryUrl" } } 268 | - Name: APP 269 | Value: !Ref AWS::StackName 270 | - Name: SNS_TOPIC_ARN 271 | Value: !If [NeedsBuildNotification, !Ref "BuildTopic", ''] 272 | Name: 273 | Fn::Join: 274 | - '' 275 | - - Ref: AWS::StackName 276 | - '-code-build' 277 | ServiceRole: !GetAtt CodeBuildRole.Arn 278 | Source: 279 | Type: CODEPIPELINE 280 | BuildSpec: buildspec.yml 281 | TimeoutInMinutes: 5 # must be between 5 minutes and 8 hours 282 | 283 | # 284 | # CodePipeline Permissions 285 | # 286 | CodePipelineRole: 287 | Type: AWS::IAM::Role 288 | Condition: NeedsCICD 289 | Properties: 290 | AssumeRolePolicyDocument: 291 | Version: 2012-10-17 292 | Statement: 293 | - Effect: Allow 294 | Action: 295 | - sts:AssumeRole 296 | Principal: 297 | Service: 298 | - codepipeline.amazonaws.com 299 | ManagedPolicyArns: 300 | - arn:aws:iam::aws:policy/AdministratorAccess 301 | 302 | # 303 | # CodePipeline 304 | # 305 | CodePipeline: 306 | Type: AWS::CodePipeline::Pipeline 307 | Condition: NeedsCICD 308 | Properties: 309 | ArtifactStore: 310 | Type: S3 311 | Location: !Ref ArtifactsBucket 312 | RoleArn: !GetAtt CodePipelineRole.Arn 313 | Stages: 314 | - Name: Source 315 | Actions: 316 | - Name: Source 317 | ActionTypeId: 318 | Category: Source 319 | Owner: ThirdParty 320 | Version: 1 321 | Provider: GitHub 322 | Configuration: 323 | Owner: !Ref GitHubOwner 324 | Repo: !Ref GitHubRepo 325 | Branch: !Ref GitHubBranch 326 | OAuthToken: !Ref GitHubToken 327 | OutputArtifacts: 328 | - Name: SourceOutput 329 | RunOrder: 1 330 | - Name: Build 331 | Actions: 332 | - Name: BuildPushImage 333 | ActionTypeId: 334 | Category: Build 335 | Owner: AWS 336 | Provider: CodeBuild 337 | Version: 1 338 | Configuration: 339 | ProjectName: !Ref CodeBuildProject 340 | InputArtifacts: 341 | - Name: SourceOutput 342 | OutputArtifacts: 343 | - Name: BuildOutput 344 | 345 | # 346 | # Build Notification 347 | # 348 | BuildTopic: 349 | Type: AWS::SNS::Topic 350 | Condition: NeedsBuildNotification 351 | Properties: 352 | TopicName: !Sub "${AWS::StackName}-build-notifications" 353 | 354 | EmailSubscription: 355 | Type: AWS::SNS::Subscription 356 | Condition: NeedsBuildNotificationEmail 357 | Properties: 358 | Endpoint: !Ref NotificationEmail 359 | Protocol: email 360 | TopicArn: !Ref BuildTopic 361 | 362 | SMSSubscription: 363 | Type: AWS::SNS::Subscription 364 | Condition: NeedsBuildNotificationSMS 365 | Properties: 366 | Endpoint: !Ref NotificationSMS 367 | Protocol: sms 368 | TopicArn: !Ref BuildTopic 369 | 370 | Outputs: 371 | Service: 372 | Value: !Ref 'Service' 373 | Export: 374 | Name: !Sub '${AWS::StackName}-Service' 375 | TaskDefinition: 376 | Value: !Ref 'TaskDefinition' 377 | Export: 378 | Name: !Sub '${AWS::StackName}-TaskDefinition' 379 | CloudWatchLogsGroup: 380 | Value: !Ref 'CloudWatchLogsGroup' 381 | Export: 382 | Name: !Sub '${AWS::StackName}-CloudWatchLogsGroup' -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker_web_app", 3 | "version": "1.0.0", 4 | "description": "Node.js on Docker", 5 | "author": "First Last ", 6 | "main": "server.js", 7 | "scripts": { 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "express": "^4.13.3" 12 | } 13 | } -------------------------------------------------------------------------------- /lib/plugins/aws/ECSServiceCI/template/src/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | // Constants 6 | const PORT = 8000; 7 | const HOST = '0.0.0.0'; 8 | 9 | // App 10 | const app = express(); 11 | app.get('/*', (req, res) => { 12 | res.send('Thanks for using CIM! '+req.path+'\n'); 13 | }); 14 | 15 | app.listen(PORT, HOST); 16 | console.log(`Running on http://${HOST}:${PORT}`); -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/Lambda.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Plugin = require('../../Plugin'); 4 | 5 | // Functions 6 | const deploy_fn = require('./lib/deploy'); 7 | const logs_fn = require('./lib/logs'); 8 | 9 | class Lambda extends Plugin { 10 | constructor() { 11 | super(); 12 | 13 | Object.assign( 14 | this, 15 | deploy_fn, 16 | logs_fn); 17 | } 18 | 19 | commands() { 20 | return [ 21 | { 22 | command: 'lambda-deploy', 23 | description: 'Deploy the lambda functions', 24 | params: { 25 | dir: { 26 | describe: 'The directory this command will run in. Defaults to the current directory.', 27 | required: false, 28 | default: process.cwd() 29 | }, 30 | recursive: { 31 | describe: 'Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is \'false\'.', 32 | required: false, 33 | default: false 34 | }, 35 | function: { 36 | describe: 'Lambda function name.', 37 | required: false 38 | }, 39 | alias: { 40 | describe: 'Lambda alias to deploy version to.', 41 | required: false 42 | }, 43 | 'lambda-version': { 44 | describe: 'Lambda function version.', 45 | required: false 46 | }, 47 | prune: { 48 | describe: 'Prune all unused Lambda versions. Defaults to \'false\'.', 49 | required: false, 50 | default: false 51 | }, 52 | stage: { 53 | describe: 'Create or update the stack(s) using the give stage.', 54 | required: false 55 | }, 56 | profile: { 57 | describe: 'Your AWS credentials profile.', 58 | required: false 59 | } 60 | }, 61 | run: this.deploy 62 | }, 63 | { 64 | command: 'lambda-publish', 65 | description: 'Publish a new lambda function', 66 | params: { 67 | dir: { 68 | describe: 'The directory this command will run in. Defaults to the current directory.', 69 | required: false, 70 | default: process.cwd() 71 | }, 72 | function: { 73 | describe: 'Lambda function name.', 74 | required: false 75 | }, 76 | stage: { 77 | describe: 'Create or update the stack(s) using the give stage.', 78 | required: false 79 | }, 80 | profile: { 81 | describe: 'Your AWS credentials profile.', 82 | required: false 83 | } 84 | }, 85 | run: this.deploy 86 | }, 87 | { 88 | command: 'lambda-prune', 89 | description: 'Delete one or all unused Lambda versions.', 90 | params: { 91 | dir: { 92 | describe: 'The directory this command will run in. Defaults to the current directory.', 93 | required: false, 94 | default: process.cwd() 95 | }, 96 | function: { 97 | describe: 'Lambda function name.', 98 | required: false 99 | }, 100 | 'lambda-version': { 101 | describe: 'Lambda function version or \'all\' to remove all unused versions.', 102 | required: true 103 | }, 104 | stage: { 105 | describe: 'Create or update the stack(s) using the give stage.', 106 | required: false 107 | }, 108 | profile: { 109 | describe: 'Your AWS credentials profile.', 110 | required: false 111 | } 112 | }, 113 | run: this.deploy 114 | }, 115 | { 116 | command: 'lambda-versions', 117 | description: 'Show all lambda function versions', 118 | params: { 119 | dir: { 120 | describe: 'The directory this command will run in. Defaults to the current directory.', 121 | required: false, 122 | default: process.cwd() 123 | }, 124 | function: { 125 | describe: 'Lambda function name.', 126 | required: false 127 | }, 128 | stage: { 129 | describe: 'Create or update the stack(s) using the give stage.', 130 | required: false 131 | }, 132 | profile: { 133 | describe: 'Your AWS credentials profile.', 134 | required: false 135 | } 136 | }, 137 | run: this.deploy 138 | }, 139 | { 140 | command: 'lambda-logs', 141 | description: 'Show the lambda function logs', 142 | params: { 143 | dir: { 144 | describe: 'The directory this command will run in. Defaults to the current directory.', 145 | required: false, 146 | default: process.cwd() 147 | }, 148 | function: { 149 | describe: 'Lambda function name.', 150 | required: true 151 | }, 152 | tail: { 153 | describe: 'Tail the logs.', 154 | required: false, 155 | default: false 156 | }, 157 | startTime: { 158 | describe: 'Start fetching logs from this time. Time in minutes. Ex. \'30s\' minutes ago.', 159 | required: false, 160 | default: '30s' 161 | }, 162 | interval: { 163 | describe: 'Interval between calls to CloudWatch Logs when using \'tail\'.', 164 | required: false, 165 | default: 5000 166 | }, 167 | filterPattern: { 168 | describe: 'CloudWatch Logs filter pattern.', 169 | required: false 170 | }, 171 | profile: { 172 | describe: 'Your AWS credentials profile.', 173 | required: false 174 | } 175 | }, 176 | run: this.logs 177 | } 178 | ]; 179 | } 180 | 181 | hooks() { 182 | return null; 183 | } 184 | 185 | template() { 186 | return null 187 | } 188 | 189 | } 190 | module.exports = Lambda; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/lib/deploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = { 4 | log: function() { 5 | console.log('DEBUG:', ...arguments) 6 | } 7 | } 8 | 9 | const _ = require('lodash'); 10 | const async = require('async'); 11 | const path = require('path'); 12 | const fs = require('fs'); 13 | const exec = require('child_process').exec; 14 | 15 | const configs = require('../../../../util/configs'); 16 | const cloudformation = require('../../../../util/cloudformation'); 17 | 18 | var AWS = require('aws-sdk'); 19 | 20 | var lambda; 21 | 22 | var init = function(input) { 23 | if (!lambda) { 24 | if (input.params.debug == 'aws') AWS.config.logger = logger; 25 | if (input.params.profile) { 26 | var credentials = new AWS.SharedIniFileCredentials({profile: input.params.profile}); 27 | AWS.config.credentials = credentials; 28 | } 29 | var aws_sdk_config = {}; 30 | ['region'].forEach(function(key) { aws_sdk_config[key] = input.params[key] }); 31 | lambda = new AWS.Lambda(aws_sdk_config); 32 | } 33 | }; 34 | 35 | var functions = {}; 36 | 37 | functions.resolve_stack = function(obj, stack, input, done) { 38 | async.eachOf(obj, function(value, key, next) { 39 | if (!_.isNil(value) && _.isString(value) && _.includes(value, '${')) { 40 | try { 41 | obj[key] = configs.do_resolve_stack_params(value, stack, input.params); 42 | next(); 43 | } catch (err) { 44 | next(err); 45 | } 46 | } else if (!_.isNil(value) && _.isObject(value)) { 47 | functions.resolve_stack(value, stack, input, next); 48 | } else { 49 | next(); 50 | } 51 | }, done); 52 | }; 53 | 54 | functions.resolve_stack_params = function(input, done) { 55 | functions.resolve_stack(input.stack, input.stack, input, done); 56 | }; 57 | 58 | functions.parse_commands = function(input, phase) { 59 | var commands = []; 60 | if (input.stack.lambda.deploy.phases) { 61 | if (input.stack.lambda.deploy.phases[phase] && input.stack.lambda.deploy.phases[phase].commands && 62 | !_.isEmpty(input.stack.lambda.deploy.phases[phase].commands)) { 63 | input.stack.lambda.deploy.phases[phase].commands = _.map(input.stack.lambda.deploy.phases[phase].commands, function(str) { 64 | if (_.startsWith(str, 'aws') && !_.includes(str, '--profile') && input.params.profile) { 65 | return str + ' --profile '+input.params.profile; 66 | } else { 67 | return str; 68 | } 69 | }); 70 | commands = commands.concat(input.stack.lambda.deploy.phases[phase].commands); 71 | } 72 | } 73 | return commands; 74 | }; 75 | 76 | functions.process_commands = function(input, phase, done) { 77 | if (_.includes(input.params._, 'lambda-prune') || 78 | _.includes(input.params._, 'lambda-versions') || 79 | (_.includes(input.params._, 'lambda-deploy') && !_.isNil(input.params['lambda-version']))) { 80 | return done(null, input); 81 | } 82 | var commands = functions.parse_commands(input, phase); 83 | console.log(phase+' phase'); 84 | async.eachSeries(commands, function(command, next) { 85 | if (input.params.debug) logger.log('exec', command); 86 | exec(command, {maxBuffer: 1024 * 5000, cwd: path.dirname(input.stack._cim.file) }, function(err, stdout, stderr) { 87 | process.stdout.write('.'); 88 | next(err); 89 | }); 90 | }, function(err) { 91 | console.log(''); 92 | done(err, input); 93 | }); 94 | }; 95 | 96 | functions.deploy_function = function(input, done) { 97 | fs.readFile(input.function_obj.zip_file, function (err, data) { 98 | if (err) { 99 | throw err; 100 | } 101 | var base64data = new Buffer(data, 'binary'); 102 | var params = { 103 | FunctionName: input.function_obj.function, /* required */ 104 | DryRun: false, 105 | Publish: true, 106 | ZipFile: base64data 107 | }; 108 | lambda.updateFunctionCode(params, function(err, data) { 109 | if (err) { 110 | return done(err, null); 111 | } else { 112 | console.log('Function: ' + input.function_obj.function); 113 | console.log('Version ' + data.Version + ' has been deployed. alias=$LATEST'); 114 | if (_.isEqual(input.params.prune, 'true')) { 115 | async.waterfall([ 116 | async.constant(input, null), 117 | functions.fetch_unused_versions, 118 | functions.delete_function_versions 119 | ], done); 120 | } else { 121 | done(null, data); 122 | } 123 | } 124 | }); 125 | }); 126 | }; 127 | 128 | functions.publish_function = function(input, done) { 129 | if (_.isNil(input.function_obj.aliases) || _.isEmpty(input.function_obj.aliases)) { 130 | return done('Missing \'function.aliases\'.', null); 131 | } 132 | fs.readFile(input.function_obj.zip_file, function (err, data) { 133 | if (err) { 134 | throw err; 135 | } 136 | var base64data = new Buffer(data, 'binary'); 137 | var params = { 138 | FunctionName: input.function_obj.function, /* required */ 139 | DryRun: false, 140 | Publish: false, 141 | ZipFile: base64data 142 | }; 143 | lambda.updateFunctionCode(params, function(err, data) { 144 | if (err) { 145 | return done(err, null); 146 | } else { 147 | var params = { 148 | CodeSha256: data.CodeSha256, 149 | FunctionName: input.function_obj.function 150 | }; 151 | lambda.publishVersion(params, function (err, data) { 152 | if (err) { 153 | return done(err, null); 154 | } else { 155 | console.log('Function: ' + input.function_obj.function); 156 | console.log('Version ' + data.Version +' has been published.'); 157 | done(null, data); 158 | } 159 | }); 160 | } 161 | }); 162 | }); 163 | }; 164 | 165 | functions.deploy_function_version = function(input, done) { 166 | if (_.isNil(input.function_obj.aliases) || _.isEmpty(input.function_obj.aliases)) { 167 | return done('Missing \'function.aliases\'.', null); 168 | } 169 | if (_.isNil(input.params.alias)) { 170 | return done('--alias option is required when deploying a version.', null); 171 | } 172 | if (_.isNil(input.function_obj.aliases[input.params.alias])) { 173 | return done('Missing \'function.aliases.'+input.params.alias+'\'.', null); 174 | } 175 | var alias = input.params.alias; 176 | var params = { 177 | FunctionName: input.function_obj.function, 178 | FunctionVersion: input.params['lambda-version']+'', 179 | Name: alias 180 | }; 181 | lambda.updateAlias(params, function(err, data) { 182 | if (err) { 183 | return done(err, null); 184 | } else { 185 | console.log('Function: ' + input.function_obj.function); 186 | console.log('Version ' + input.params['lambda-version'] + ' has been deployed. alias='+alias); 187 | if (_.isEqual(input.params.prune, 'true')) { 188 | async.waterfall([ 189 | async.constant(input, null), 190 | functions.fetch_unused_versions, 191 | functions.delete_function_versions 192 | ], done); 193 | } else { 194 | done(null, data); 195 | } 196 | } 197 | }); 198 | }; 199 | 200 | functions.prune_function = function(input, done) { 201 | if (_.isEqual(input.params['lambda-version'], 'all')) { 202 | async.waterfall([ 203 | async.constant(input, null), 204 | functions.fetch_unused_versions, 205 | functions.delete_function_versions 206 | ], done); 207 | } else { 208 | console.log('Function: ' + input.function_obj.function); 209 | functions.delete_function_version({ 210 | function: input.function_obj.function, 211 | version: input.params['lambda-version'] 212 | }, done); 213 | } 214 | }; 215 | 216 | functions.fetch_unused_versions = function(input, nextMarker, done) { 217 | var params = { 218 | FunctionName: input.function_obj.function, 219 | Marker: nextMarker, 220 | MaxItems: 100 221 | }; 222 | lambda.listVersionsByFunction(params, function(err, data) { 223 | if (err) { 224 | return done(err, null); 225 | } else { 226 | async.mapSeries(data.Versions, function(version, next) { 227 | functions.has_alias(input, version.Version, function(err, has_alias) { 228 | if (err) { 229 | return next(err, null); 230 | } else { 231 | next(null, has_alias ? null : version.Version); 232 | } 233 | }); 234 | }, function(err, versions) { 235 | if (err) { 236 | done(err, null); 237 | } else { 238 | input.versions = versions; 239 | done(null, input); 240 | } 241 | }); 242 | } 243 | }); 244 | }; 245 | 246 | functions.has_alias = function(input, version, done) { 247 | var params = { 248 | FunctionName: input.function_obj.function, 249 | FunctionVersion: version, 250 | Marker: null, 251 | MaxItems: 1 252 | }; 253 | lambda.listAliases(params, function(err, data) { 254 | if (err) { 255 | return done(err, null); 256 | } else { 257 | done(null, !_.isEmpty(data.Aliases)) 258 | } 259 | }); 260 | }; 261 | 262 | functions.delete_function_versions = function(input, done) { 263 | const versions = _.remove(input.versions, function(version) { return !_.isNil(version) && !_.isEqual(version, '$LATEST'); }); 264 | async.eachSeries(versions, function(version, next) { 265 | functions.delete_function_version({ 266 | function: input.function_obj.function, 267 | version: version 268 | }, next); 269 | }, function(err) { 270 | done(err, null); 271 | }); 272 | }; 273 | 274 | functions.delete_function_version = function(input, done) { 275 | var params = { 276 | FunctionName: input.function, 277 | Qualifier: input.version+'' 278 | }; 279 | lambda.deleteFunction(params, function(err, data) { 280 | if (err) { 281 | return done(err, null); 282 | } else { 283 | console.log('Version ' + input.version + ' has been removed.'); 284 | done(null, data); 285 | } 286 | }); 287 | }; 288 | 289 | functions.function_aliases = function(input, version, nextMarker, done) { 290 | var params = { 291 | FunctionName: input.function_obj.function, 292 | FunctionVersion: version, 293 | Marker: nextMarker, 294 | MaxItems: 100 295 | }; 296 | lambda.listAliases(params, function(err, data) { 297 | if (err) { 298 | return done(err, null); 299 | } else { 300 | if (_.isNil(nextMarker)) { 301 | console.log('Version = '+version); 302 | } 303 | _.forEach(data.Aliases, function(alias) { 304 | console.log(' - Alias = '+alias.Name); 305 | }); 306 | if (!_.isNil(data.NextMarker)) { 307 | functions.function_aliases(input, version, data.NextMarker, done); 308 | } else { 309 | done(null, data); 310 | } 311 | } 312 | }); 313 | }; 314 | 315 | functions.function_versions = function(input, nextMarker, done) { 316 | var params = { 317 | FunctionName: input.function_obj.function, 318 | Marker: nextMarker, 319 | MaxItems: 100 320 | }; 321 | lambda.listVersionsByFunction(params, function(err, data) { 322 | if (err) { 323 | return done(err, null); 324 | } else { 325 | async.eachSeries(data.Versions, function(version, next) { 326 | functions.function_aliases(input, version.Version, null, next); 327 | }, function(err) { 328 | if (err) { 329 | return done(err, null); 330 | } else { 331 | if (!_.isNil(data.NextMarker)) { 332 | functions.function_versions(input, data.NextMarker, done); 333 | } else { 334 | done(null, data); 335 | } 336 | } 337 | }); 338 | } 339 | }); 340 | }; 341 | 342 | functions.deploy_phase = function(input, done) { 343 | if (input.stack.lambda.functions && _.isArray(input.stack.lambda.functions) && 344 | !_.isEmpty(input.stack.lambda.functions)) { 345 | async.eachSeries(input.stack.lambda.functions, function (function_obj, next) { 346 | if (!input.params.function || _.isEqual(input.params.function, function_obj.function)) { 347 | function_obj.zip_file = path.resolve(path.dirname(input.stack._cim.file), function_obj.zip_file); 348 | if (_.includes(input.params._, 'lambda-deploy')) { 349 | console.log('deploy phase'); 350 | if (_.isNil(input.params['lambda-version'])) { 351 | functions.deploy_function({function_obj: function_obj, params: input.params}, next); 352 | } else { 353 | functions.deploy_function_version({function_obj: function_obj, params: input.params}, next); 354 | } 355 | } else if (_.includes(input.params._, 'lambda-publish')) { 356 | console.log('publish phase'); 357 | functions.publish_function({function_obj: function_obj, params: input.params}, next); 358 | } else if (_.includes(input.params._, 'lambda-prune')) { 359 | functions.prune_function({function_obj: function_obj, params: input.params}, next); 360 | } else if (_.includes(input.params._, 'lambda-versions')) { 361 | console.log('function versions: ' + function_obj.function); 362 | functions.function_versions({function_obj: function_obj, params: input.params}, null, next); 363 | } 364 | } else { 365 | next(); 366 | } 367 | }, function (err) { 368 | done(err, input); 369 | }); 370 | } else { 371 | done(null, input); 372 | } 373 | }; 374 | 375 | functions.deploy = function(input, done) { 376 | init(input); 377 | if (input.stack.lambda.deploy) { 378 | if (_.includes(input.params._, 'lambda-deploy')) { 379 | console.log('Deploying: ' + input.stack.stack.name); 380 | } else if (_.includes(input.params._, 'lambda-publish')) { 381 | console.log('Publishing: ' + input.stack.stack.name); 382 | } else if (_.includes(input.params._, 'lambda-prune')) { 383 | console.log('Pruning: ' + input.stack.stack.name); 384 | } 385 | async.waterfall([ 386 | async.constant(input), 387 | functions.resolve_stack_params, 388 | async.constant(input, 'pre_deploy'), 389 | functions.process_commands, 390 | functions.deploy_phase, 391 | async.constant(input, 'post_deploy'), 392 | functions.process_commands 393 | ], done); 394 | } else { 395 | done(null, input); 396 | } 397 | }; 398 | 399 | functions.deploy_stacks = function(input, done) { 400 | async.eachSeries(input.stacks, function(stack, next) { 401 | functions.deploy({stack: stack, params: input.params}, next); 402 | }, function(err) { 403 | done(err); 404 | }); 405 | }; 406 | 407 | module.exports = { 408 | deploy(cim, done) { 409 | async.waterfall([ 410 | async.constant(cim), 411 | configs.load, 412 | function(stacks, next) { 413 | next(null, { stacks: stacks, params: cim.args}); 414 | }, 415 | cloudformation.fetch_statuses, 416 | functions.deploy_stacks 417 | ], done); 418 | } 419 | }; 420 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/lib/logs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const async = require('async'); 5 | const moment = require("moment"); 6 | 7 | var AWS = require('aws-sdk'); 8 | var cloudwatchlogs; 9 | 10 | var init = function(input) { 11 | if (!cloudwatchlogs) { 12 | if (input.params.profile) { 13 | var credentials = new AWS.SharedIniFileCredentials({profile: input.params.profile}); 14 | AWS.config.credentials = credentials; 15 | } 16 | cloudwatchlogs = new AWS.CloudWatchLogs(); 17 | } 18 | }; 19 | 20 | var functions = {}; 21 | 22 | functions.show_logs = function(input, done) { 23 | //console.log(JSON.stringify(input.params, null, 3)); 24 | init(input); 25 | 26 | const params = { 27 | logGroupName: '/aws/lambda/' + input.params.function, 28 | interleaved: true, 29 | startTime: input.params.startTime, 30 | }; 31 | 32 | if (input.params.filterPattern) params.filterPattern = input.params.filterPattern; 33 | if (input.params.nextToken) params.nextToken = input.params.nextToken; 34 | if (input.params.startTime) { 35 | const since = (['s', 'm', 'h', 'd'] 36 | .indexOf(input.params.startTime[input.params.startTime.length - 1]) !== -1); 37 | if (since) { 38 | params.startTime = moment().subtract(input.params.startTime.replace(/\D/g, ''), 39 | input.params.startTime.replace(/\d/g, '')).unix(); 40 | } else if (!_.isNumber(input.params.startTime)) { 41 | params.startTime = moment(input.params.startTime).unix(); 42 | } 43 | } 44 | 45 | //console.log(JSON.stringify(params, null, 3)); 46 | cloudwatchlogs.filterLogEvents(params, function(err, data) { 47 | if (err) return done(err); 48 | _.forEach(data.events, function(event) { 49 | //console.log(JSON.stringify(event, null, 3)); 50 | var ts = new Date(event.timestamp); 51 | console.log(ts + ' : ' + _.trim(event.message)); 52 | }); 53 | 54 | if (data.nextToken) { 55 | input.params.nextToken = data.nextToken; 56 | } else { 57 | delete input.params.nextToken; 58 | } 59 | 60 | if (input.params.tail) { 61 | if (data.events && data.events.length) { 62 | input.params.startTime = _.last(data.events).timestamp + 1; 63 | } 64 | 65 | if (input.params.tail) { 66 | return setTimeout(function() { 67 | functions.show_logs(input, done); 68 | }, input.params.interval); 69 | } 70 | } else { 71 | done(); 72 | } 73 | }); 74 | }; 75 | 76 | module.exports = { 77 | logs(cim, done) { 78 | async.waterfall([ 79 | async.constant({params: cim.args}), 80 | functions.show_logs 81 | ], done); 82 | } 83 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/LambdaNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNode extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node', 22 | description: 'Single Lambda.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNode; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-cron/LambdaNodeCloudwatchCron.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeCloudwatchCron extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-cloudwatch-cron', 22 | description: 'Lambda with scheduled CloudWatch cron event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeCloudwatchCron; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-cron/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-cloudwatch-cron # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | # parameters: 18 | # VpcStackName: '${stack.parents.vpc.stack.name}' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | capabilities: 24 | - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | 32 | lambda: 33 | functions: 34 | - 35 | function: ${stack.outputs.LambdaFunction} 36 | zip_file: index.zip 37 | deploy: 38 | phases: 39 | pre_deploy: 40 | commands: 41 | # Install all npm packages including dev packages. 42 | - npm install 43 | 44 | # Run the tests 45 | # - npm test 46 | 47 | # Remove all the npm packages. 48 | - rm -Rf node_modules 49 | 50 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 51 | - npm install --production 52 | 53 | # Zip the Lambda for upload to S3. 54 | - zip -r index.zip . 55 | post_deploy: 56 | commands: 57 | # Remove the zip file. 58 | - rm -Rf index.zip 59 | 60 | # Reinstall the dev npm packages. 61 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-cron/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Role that our Lambda will assume to provide access to other AWS resources 18 | # 19 | IamRoleLambdaExecution: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | AssumeRolePolicyDocument: 23 | Version: '2012-10-17' 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: 28 | - lambda.amazonaws.com 29 | Action: 30 | - sts:AssumeRole 31 | Path: '/' 32 | 33 | # 34 | # Create a Policy and attach it to our Lambda Role. 35 | # 36 | IamPolicyLambdaExecution: 37 | Type: AWS::IAM::Policy 38 | Properties: 39 | PolicyName: IamPolicyLambdaExecution 40 | PolicyDocument: 41 | Version: '2012-10-17' 42 | Statement: 43 | - Effect: Allow 44 | Action: 45 | - logs:CreateLogGroup 46 | - logs:CreateLogStream 47 | - logs:PutLogEvents 48 | Resource: '*' 49 | - Effect: Allow 50 | Action: 51 | - s3:* 52 | Resource: '*' 53 | Roles: 54 | - Ref: IamRoleLambdaExecution 55 | 56 | # 57 | # Our Lambda function. 58 | # 59 | LambdaFunction: 60 | Type: AWS::Lambda::Function 61 | Properties: 62 | Handler: index.handler 63 | Timeout: 5 64 | Role: 65 | Fn::GetAtt: 66 | - IamRoleLambdaExecution 67 | - Arn 68 | Code: 69 | ZipFile: !Sub | 70 | 'use strict'; 71 | 72 | exports.handler = function(event, context) { 73 | console.log(JSON.stringify(event)); 74 | context.succeed('Hello CIM!'); 75 | }; 76 | Runtime: nodejs6.10 77 | 78 | 79 | # 80 | # CloudWatch Event to trigger lambda 81 | # 82 | ScheduledRule: 83 | Type: AWS::Events::Rule 84 | Properties: 85 | Description: 'ScheduledRule' 86 | ScheduleExpression: 'rate(10 minutes)' 87 | State: 'ENABLED' 88 | Targets: 89 | - 90 | Arn: !GetAtt LambdaFunction.Arn 91 | Id: 'ScheduledRuleLambdaTarget' 92 | 93 | # 94 | # Permission for CloudWatch to invoke Lambda 95 | # 96 | PermissionForEventsToInvokeLambda: 97 | Type: AWS::Lambda::Permission 98 | Properties: 99 | FunctionName: !Ref LambdaFunction 100 | Action: 'lambda:InvokeFunction' 101 | Principal: 'events.amazonaws.com' 102 | SourceArn: !GetAtt ScheduledRule.Arn 103 | 104 | # 105 | # Outputs to be used by other CloudFormation templates if needed. 106 | # 107 | Outputs: 108 | LambdaFunction: 109 | Description: Lambda Function 110 | Value: !Ref LambdaFunction 111 | Export: 112 | Name: !Sub '${AWS::StackName}-LambdaFunction' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-cron/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM!'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-cron/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-sns", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-logs/LambdaNodeCloudwatchLogs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeCloudwatchLogs extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-cloudwatch-logs', 22 | description: 'Lambda with CloudWatch Logs event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeCloudwatchLogs; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-logs/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-cloudwatch-logs # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | # parameters: 18 | # VpcStackName: '${stack.parents.vpc.stack.name}' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | capabilities: 24 | - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | 32 | lambda: 33 | functions: 34 | - 35 | function: ${stack.outputs.LambdaFunction} 36 | zip_file: index.zip 37 | deploy: 38 | phases: 39 | pre_deploy: 40 | commands: 41 | # Install all npm packages including dev packages. 42 | - npm install 43 | 44 | # Run the tests 45 | # - npm test 46 | 47 | # Remove all the npm packages. 48 | - rm -Rf node_modules 49 | 50 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 51 | - npm install --production 52 | 53 | # Zip the Lambda for upload to S3. 54 | - zip -r index.zip . 55 | post_deploy: 56 | commands: 57 | # Remove the zip file. 58 | - rm -Rf index.zip 59 | 60 | # Reinstall the dev npm packages. 61 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-logs/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Role that our Lambda will assume to provide access to other AWS resources 18 | # 19 | IamRoleLambdaExecution: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | AssumeRolePolicyDocument: 23 | Version: '2012-10-17' 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: 28 | - lambda.amazonaws.com 29 | Action: 30 | - sts:AssumeRole 31 | Path: '/' 32 | 33 | # 34 | # Create a Policy and attach it to our Lambda Role. 35 | # 36 | IamPolicyLambdaExecution: 37 | Type: AWS::IAM::Policy 38 | Properties: 39 | PolicyName: IamPolicyLambdaExecution 40 | PolicyDocument: 41 | Version: '2012-10-17' 42 | Statement: 43 | - Effect: Allow 44 | Action: 45 | - logs:CreateLogGroup 46 | - logs:CreateLogStream 47 | - logs:PutLogEvents 48 | Resource: '*' 49 | - Effect: Allow 50 | Action: 51 | - s3:* 52 | Resource: '*' 53 | Roles: 54 | - Ref: IamRoleLambdaExecution 55 | 56 | # 57 | # Our Lambda function. 58 | # 59 | LambdaFunction: 60 | Type: AWS::Lambda::Function 61 | Properties: 62 | Handler: index.handler 63 | Timeout: 5 64 | Role: 65 | Fn::GetAtt: 66 | - IamRoleLambdaExecution 67 | - Arn 68 | Code: 69 | ZipFile: !Sub | 70 | 'use strict'; 71 | 72 | exports.handler = function(event, context) { 73 | console.log(JSON.stringify(event)); 74 | context.succeed('Hello CIM!'); 75 | }; 76 | Runtime: nodejs6.10 77 | 78 | # 79 | # CloudWatch Log Group 80 | # 81 | LogGroup: 82 | Type: AWS::Logs::LogGroup 83 | Properties: 84 | RetentionInDays: 7 85 | 86 | # 87 | # CloudWatch Logs Subscription 88 | # 89 | SubscriptionFilter: 90 | Type: AWS::Logs::SubscriptionFilter 91 | Properties: 92 | LogGroupName: !Ref LogGroup 93 | FilterPattern: 'ERROR' # TODO Modify to fit your use case. 94 | DestinationArn: !GetAtt LambdaFunction.Arn 95 | 96 | # 97 | # CloudWatch permission to invoke Lambda 98 | # 99 | PermissionForCloudWatchToInvokeLambda: 100 | Type: AWS::Lambda::Permission 101 | Properties: 102 | FunctionName: !Ref LambdaFunction 103 | Action: 'lambda:InvokeFunction' 104 | Principal: 'logs.amazonaws.com' 105 | SourceAccount: !Ref AWS::AccountId 106 | 107 | # 108 | # Outputs to be used by other CloudFormation templates if needed. 109 | # 110 | Outputs: 111 | LambdaFunction: 112 | Description: Lambda Function 113 | Value: !Ref LambdaFunction 114 | Export: 115 | Name: !Sub '${AWS::StackName}-LambdaFunction' 116 | LogGroup: 117 | Description: LogGroup 118 | Value: !Ref LogGroup 119 | Export: 120 | Name: !Sub '${AWS::StackName}-LogGroup' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-logs/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM!'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/cloudwatch-logs/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-logs", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/dynamodb/LambdaNodeDynamoDB.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeDynamoDB extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-dynamodb', 22 | description: 'Lambda with DynamoDB stream event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeDynamoDB; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/dynamodb/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-dynamodb # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | # 8 | # Reference parent stacks fo included shared information like stack name. 9 | # 10 | # parents: 11 | # vpc: '../vpc' 12 | 13 | # 14 | # Define stack input parameters. 15 | # 16 | # parameters: 17 | # VpcStackName: '${stack.parents.vpc.stack.name}' 18 | 19 | # 20 | # Define stack capabilities required. 21 | # 22 | capabilities: 23 | - 'CAPABILITY_IAM' 24 | 25 | # 26 | # Define global tags. 27 | # 28 | # tags: 29 | # app: 'cim-stack' 30 | 31 | lambda: 32 | functions: 33 | - 34 | function: ${stack.outputs.LambdaFunction} 35 | zip_file: index.zip 36 | deploy: 37 | phases: 38 | pre_deploy: 39 | commands: 40 | # Install all npm packages including dev packages. 41 | - npm install 42 | 43 | # Run the tests 44 | # - npm test 45 | 46 | # Remove all the npm packages. 47 | - rm -Rf node_modules 48 | 49 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 50 | - npm install --production 51 | 52 | # Zip the Lambda for upload to S3. 53 | - zip -r index.zip . 54 | post_deploy: 55 | commands: 56 | # Remove the zip file. 57 | - rm -Rf index.zip 58 | 59 | # Reinstall the dev npm packages. 60 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/dynamodb/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # DynamoDB Table 18 | # * Change this table as needed. 19 | # 20 | DynamoDBTable: 21 | Type: 'AWS::DynamoDB::Table' 22 | Properties: 23 | AttributeDefinitions: 24 | - 25 | AttributeName: 'status' 26 | AttributeType: 'S' 27 | - 28 | AttributeName: 'org_env' 29 | AttributeType: 'S' 30 | KeySchema: 31 | - 32 | AttributeName: 'status' 33 | KeyType: 'HASH' 34 | - 35 | AttributeName: 'org_env' 36 | KeyType: 'RANGE' 37 | ProvisionedThroughput: 38 | ReadCapacityUnits: '5' 39 | WriteCapacityUnits: '5' 40 | StreamSpecification: 41 | StreamViewType: NEW_AND_OLD_IMAGES 42 | 43 | # 44 | # Role that our Lambda will assume to provide access to other AWS resources 45 | # 46 | IamRoleLambdaExecution: 47 | Type: AWS::IAM::Role 48 | Properties: 49 | AssumeRolePolicyDocument: 50 | Version: '2012-10-17' 51 | Statement: 52 | - Effect: Allow 53 | Principal: 54 | Service: 55 | - lambda.amazonaws.com 56 | Action: 57 | - sts:AssumeRole 58 | Path: '/' 59 | 60 | # 61 | # Create a Policy and attach it to our Lambda Role. 62 | # 63 | IamPolicyLambdaExecution: 64 | Type: AWS::IAM::Policy 65 | Properties: 66 | PolicyName: IamPolicyLambdaExecution 67 | PolicyDocument: 68 | Version: '2012-10-17' 69 | Statement: 70 | - Effect: Allow 71 | Action: 72 | - logs:CreateLogGroup 73 | - logs:CreateLogStream 74 | - logs:PutLogEvents 75 | Resource: '*' 76 | - Effect: Allow 77 | Action: 78 | - dynamodb:DescribeStream 79 | - dynamodb:GetRecords 80 | - dynamodb:GetShardIterator 81 | - dynamodb:ListStreams 82 | Resource: 83 | Fn::Join: 84 | - '' 85 | - - 'arn:aws:dynamodb:' 86 | - Ref: AWS::Region 87 | - ':' 88 | - Ref: AWS::AccountId 89 | - ':table/' 90 | - Ref: DynamoDBTable 91 | - '/stream/*' 92 | Roles: 93 | - Ref: IamRoleLambdaExecution 94 | 95 | # 96 | # Our Lambda function. 97 | # 98 | LambdaFunction: 99 | Type: AWS::Lambda::Function 100 | Properties: 101 | Handler: index.handler 102 | Timeout: 5 103 | Role: 104 | Fn::GetAtt: 105 | - IamRoleLambdaExecution 106 | - Arn 107 | Code: 108 | ZipFile: !Sub | 109 | 'use strict'; 110 | 111 | exports.handler = function(event, context) { 112 | console.log(JSON.stringify(event)); 113 | context.succeed('Hello CIM!'); 114 | }; 115 | Runtime: nodejs6.10 116 | 117 | 118 | # 119 | # Tell our Lambda to poll the DynamoDB Kinesis Stream 120 | # 121 | EventSourceMapping: 122 | Type: AWS::Lambda::EventSourceMapping 123 | DependsOn: IamPolicyLambdaExecution # We need our policy to complete first or this step throws an error. 124 | Properties: 125 | EventSourceArn: !GetAtt DynamoDBTable.StreamArn 126 | FunctionName: !GetAtt LambdaFunction.Arn 127 | StartingPosition: "TRIM_HORIZON" 128 | 129 | # 130 | # Outputs to be used by other CloudFormation templates if needed. 131 | # 132 | Outputs: 133 | LambdaFunction: 134 | Description: Lambda Function 135 | Value: !Ref LambdaFunction 136 | Export: 137 | Name: !Sub '${AWS::StackName}-LambdaFunction' 138 | DynamoDBTable: 139 | Description: DynamoDB Table 140 | Value: !Ref DynamoDBTable 141 | Export: 142 | Name: !Sub '${AWS::StackName}-DynamoDBTable' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/dynamodb/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/dynamodb/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-dynamodb", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/kinesis/LambdaNodeKinesis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeKinesis extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-kinesis', 22 | description: 'Lambda with Kinesis stream event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeKinesis; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/kinesis/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-kinesis # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | # 8 | # Reference parent stacks fo included shared information like stack name. 9 | # 10 | # parents: 11 | # vpc: '../vpc' 12 | 13 | # 14 | # Define stack input parameters. 15 | # 16 | # parameters: 17 | # VpcStackName: '${stack.parents.vpc.stack.name}' 18 | 19 | # 20 | # Define stack capabilities required. 21 | # 22 | capabilities: 23 | - 'CAPABILITY_IAM' 24 | 25 | # 26 | # Define global tags. 27 | # 28 | # tags: 29 | # app: 'cim-stack' 30 | 31 | lambda: 32 | functions: 33 | - 34 | function: ${stack.outputs.LambdaFunction} 35 | zip_file: index.zip 36 | deploy: 37 | phases: 38 | pre_deploy: 39 | commands: 40 | # Install all npm packages including dev packages. 41 | - npm install 42 | 43 | # Run the tests 44 | # - npm test 45 | 46 | # Remove all the npm packages. 47 | - rm -Rf node_modules 48 | 49 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 50 | - npm install --production 51 | 52 | # Zip the Lambda for upload to S3. 53 | - zip -r index.zip . 54 | post_deploy: 55 | commands: 56 | # Remove the zip file. 57 | - rm -Rf index.zip 58 | 59 | # Reinstall the dev npm packages. 60 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/kinesis/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Kinesis Stream 18 | # 19 | KinesisStream: 20 | Type: AWS::Kinesis::Stream 21 | Properties: 22 | ShardCount: 1 23 | 24 | # 25 | # Role that our Lambda will assume to provide access to other AWS resources 26 | # 27 | IamRoleLambdaExecution: 28 | Type: AWS::IAM::Role 29 | Properties: 30 | AssumeRolePolicyDocument: 31 | Version: '2012-10-17' 32 | Statement: 33 | - Effect: Allow 34 | Principal: 35 | Service: 36 | - lambda.amazonaws.com 37 | Action: 38 | - sts:AssumeRole 39 | Path: '/' 40 | 41 | # 42 | # Create a Policy and attach it to our Lambda Role. 43 | # 44 | IamPolicyLambdaExecution: 45 | Type: AWS::IAM::Policy 46 | Properties: 47 | PolicyName: IamPolicyLambdaExecution 48 | PolicyDocument: 49 | Version: '2012-10-17' 50 | Statement: 51 | - Effect: Allow 52 | Action: 53 | - logs:CreateLogGroup 54 | - logs:CreateLogStream 55 | - logs:PutLogEvents 56 | Resource: '*' 57 | - Effect: Allow 58 | Action: 59 | - kinesis:DescribeStream 60 | - kinesis:GetRecords 61 | - kinesis:GetShardIterator 62 | - kinesis:ListStreams 63 | Resource: 64 | Fn::Join: 65 | - '' 66 | - - 'arn:aws:kinesis:' 67 | - Ref: AWS::Region 68 | - ':' 69 | - Ref: AWS::AccountId 70 | - ':stream/' 71 | - Ref: KinesisStream 72 | - Effect: Allow 73 | Action: 74 | - kinesis:ListStreams 75 | Resource: '*' 76 | Roles: 77 | - Ref: IamRoleLambdaExecution 78 | 79 | # 80 | # Our Lambda function. 81 | # 82 | LambdaFunction: 83 | Type: AWS::Lambda::Function 84 | Properties: 85 | Handler: index.handler 86 | Timeout: 5 87 | Role: 88 | Fn::GetAtt: 89 | - IamRoleLambdaExecution 90 | - Arn 91 | Code: 92 | ZipFile: !Sub | 93 | 'use strict'; 94 | 95 | exports.handler = function(event, context) { 96 | console.log(JSON.stringify(event)); 97 | context.succeed('Hello CIM!'); 98 | }; 99 | Runtime: nodejs6.10 100 | 101 | 102 | # 103 | # Tell our Lambda to poll the DynamoDB Kinesis Stream 104 | # 105 | EventSourceMapping: 106 | Type: AWS::Lambda::EventSourceMapping 107 | DependsOn: IamPolicyLambdaExecution # We need our policy to complete first or this step throws an error. 108 | Properties: 109 | EventSourceArn: !GetAtt KinesisStream.Arn 110 | FunctionName: !GetAtt LambdaFunction.Arn 111 | StartingPosition: "TRIM_HORIZON" 112 | 113 | # 114 | # Outputs to be used by other CloudFormation templates if needed. 115 | # 116 | Outputs: 117 | LambdaFunction: 118 | Description: Lambda Function 119 | Value: !Ref LambdaFunction 120 | Export: 121 | Name: !Sub '${AWS::StackName}-LambdaFunction' 122 | KinesisStream: 123 | Description: Kinesis Stream 124 | Value: !Ref KinesisStream 125 | Export: 126 | Name: !Sub '${AWS::StackName}-KinesisStream' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/kinesis/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/kinesis/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-kinesis", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/s3/LambdaNodeS3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeS3 extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-s3', 22 | description: 'Lambda with S3 event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeS3; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/s3/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-s3 # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | # parameters: 18 | # VpcStackName: '${stack.parents.vpc.stack.name}' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | capabilities: 24 | - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | 32 | lambda: 33 | functions: 34 | - 35 | function: ${stack.outputs.LambdaFunction} 36 | zip_file: index.zip 37 | deploy: 38 | phases: 39 | pre_deploy: 40 | commands: 41 | # Install all npm packages including dev packages. 42 | - npm install 43 | 44 | # Run the tests 45 | # - npm test 46 | 47 | # Remove all the npm packages. 48 | - rm -Rf node_modules 49 | 50 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 51 | - npm install --production 52 | 53 | # Zip the Lambda for upload to S3. 54 | - zip -r index.zip . 55 | post_deploy: 56 | commands: 57 | # Remove the zip file. 58 | - rm -Rf index.zip 59 | 60 | # Reinstall the dev npm packages. 61 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/s3/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Role that our Lambda will assume to provide access to other AWS resources 18 | # 19 | IamRoleLambdaExecution: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | AssumeRolePolicyDocument: 23 | Version: '2012-10-17' 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: 28 | - lambda.amazonaws.com 29 | Action: 30 | - sts:AssumeRole 31 | Path: '/' 32 | 33 | # 34 | # Create a Policy and attach it to our Lambda Role. 35 | # 36 | IamPolicyLambdaExecution: 37 | Type: AWS::IAM::Policy 38 | Properties: 39 | PolicyName: IamPolicyLambdaExecution 40 | PolicyDocument: 41 | Version: '2012-10-17' 42 | Statement: 43 | - Effect: Allow 44 | Action: 45 | - logs:CreateLogGroup 46 | - logs:CreateLogStream 47 | - logs:PutLogEvents 48 | Resource: '*' 49 | 50 | # Add additional permissions here. What other AWS resources does your Lambda need access to? 51 | 52 | Roles: 53 | - Ref: IamRoleLambdaExecution 54 | 55 | # 56 | # Our Lambda function. 57 | # 58 | LambdaFunction: 59 | Type: AWS::Lambda::Function 60 | Properties: 61 | Handler: index.handler 62 | Timeout: 5 63 | Role: 64 | Fn::GetAtt: 65 | - IamRoleLambdaExecution 66 | - Arn 67 | Code: 68 | ZipFile: !Sub | 69 | 'use strict'; 70 | 71 | exports.handler = function(event, context) { 72 | console.log(JSON.stringify(event)); 73 | context.succeed('Hello CIM!'); 74 | }; 75 | Runtime: nodejs6.10 76 | 77 | # 78 | # Sample Lambda Event Trigger 79 | # 80 | 81 | # 82 | # S3 bucket 83 | # 84 | S3Bucket: 85 | Type: 'AWS::S3::Bucket' 86 | Properties: 87 | AccessControl: 'Private' 88 | NotificationConfiguration: 89 | LambdaConfigurations: 90 | - 91 | Function: !GetAtt LambdaFunction.Arn 92 | Event: 's3:ObjectCreated:*' 93 | # Filter: 94 | # S3Key: 95 | # Rules: 96 | # - 97 | # Name: suffix 98 | # Value: .png 99 | 100 | # 101 | # S3 permission to invoke Lambda 102 | # 103 | PermissionForS3ToInvokeLambda: 104 | Type: 'AWS::Lambda::Permission' 105 | Properties: 106 | FunctionName: !Ref LambdaFunction 107 | Action: 'lambda:InvokeFunction' 108 | Principal: 's3.amazonaws.com' 109 | SourceAccount: !Ref AWS::AccountId 110 | 111 | # 112 | # Outputs to be used by other CloudFormation templates if needed. 113 | # 114 | Outputs: 115 | LambdaFunction: 116 | Description: Lambda Function 117 | Value: !Ref LambdaFunction 118 | Export: 119 | Name: !Sub '${AWS::StackName}-LambdaFunction' 120 | S3Bucket: 121 | Description: S3 Bucket 122 | Value: !Ref S3Bucket 123 | Export: 124 | Name: !Sub '${AWS::StackName}-S3Bucket' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/s3/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM!'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/s3/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-s3", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/sns/LambdaNodeSNS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lambda = require('../../Lambda'); 4 | const path = require('path'); 5 | 6 | class LambdaNodeSNS extends Lambda { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | commands() { 12 | return []; 13 | } 14 | 15 | hooks() { 16 | return null; 17 | } 18 | 19 | template() { 20 | return { 21 | name: 'lambda-node-sns', 22 | description: 'Lambda with SNS event trigger.', 23 | path: path.resolve(__dirname, 'template') 24 | } 25 | } 26 | 27 | } 28 | module.exports = LambdaNodeSNS; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/sns/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda-sns # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | # parameters: 18 | # VpcStackName: '${stack.parents.vpc.stack.name}' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | capabilities: 24 | - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | 32 | lambda: 33 | functions: 34 | - 35 | function: ${stack.outputs.LambdaFunction} 36 | zip_file: index.zip 37 | deploy: 38 | phases: 39 | pre_deploy: 40 | commands: 41 | # Install all npm packages including dev packages. 42 | - npm install 43 | 44 | # Run the tests 45 | # - npm test 46 | 47 | # Remove all the npm packages. 48 | - rm -Rf node_modules 49 | 50 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 51 | - npm install --production 52 | 53 | # Zip the Lambda for upload to S3. 54 | - zip -r index.zip . 55 | post_deploy: 56 | commands: 57 | # Remove the zip file. 58 | - rm -Rf index.zip 59 | 60 | # Reinstall the dev npm packages. 61 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/sns/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # SNS Topic 18 | # 19 | SNSTopic: 20 | Type: AWS::SNS::Topic 21 | Properties: 22 | DisplayName: 23 | Fn::Join: 24 | - '' 25 | - - Ref: AWS::StackName 26 | - ' Topic' 27 | TopicName: 28 | Fn::Join: 29 | - '' 30 | - - Ref: AWS::StackName 31 | - '-topic' 32 | 33 | # 34 | # Role that our Lambda will assume to provide access to other AWS resources 35 | # 36 | IamRoleLambdaExecution: 37 | Type: AWS::IAM::Role 38 | Properties: 39 | AssumeRolePolicyDocument: 40 | Version: '2012-10-17' 41 | Statement: 42 | - Effect: Allow 43 | Principal: 44 | Service: 45 | - lambda.amazonaws.com 46 | Action: 47 | - sts:AssumeRole 48 | Path: '/' 49 | 50 | # 51 | # Create a Policy and attach it to our Lambda Role. 52 | # 53 | IamPolicyLambdaExecution: 54 | Type: AWS::IAM::Policy 55 | Properties: 56 | PolicyName: IamPolicyLambdaExecution 57 | PolicyDocument: 58 | Version: '2012-10-17' 59 | Statement: 60 | - Effect: Allow 61 | Action: 62 | - logs:CreateLogGroup 63 | - logs:CreateLogStream 64 | - logs:PutLogEvents 65 | Resource: '*' 66 | - Effect: Allow 67 | Action: 68 | - s3:* 69 | Resource: '*' 70 | Roles: 71 | - Ref: IamRoleLambdaExecution 72 | 73 | # 74 | # Our Lambda function. 75 | # 76 | LambdaFunction: 77 | Type: AWS::Lambda::Function 78 | Properties: 79 | Handler: index.handler 80 | Timeout: 5 81 | Role: 82 | Fn::GetAtt: 83 | - IamRoleLambdaExecution 84 | - Arn 85 | Code: 86 | ZipFile: !Sub | 87 | 'use strict'; 88 | 89 | exports.handler = function(event, context) { 90 | console.log(JSON.stringify(event)); 91 | context.succeed('Hello CIM!'); 92 | }; 93 | Runtime: nodejs6.10 94 | 95 | # 96 | # Subscribe our new Lambda function to the SNS topic. 97 | # 98 | LambdaSNSSubscription: 99 | Type: AWS::SNS::Subscription 100 | DependsOn: LambdaFunction 101 | Properties: 102 | TopicArn: !Ref SNSTopic 103 | Protocol: lambda 104 | Endpoint: !GetAtt LambdaFunction.Arn 105 | 106 | # 107 | # Give SNS permission to invoke our Lambda 108 | # 109 | PermissionForSNSToInvokeLambda: 110 | Type: AWS::Lambda::Permission 111 | Properties: 112 | FunctionName: !Ref LambdaFunction 113 | Action: 'lambda:InvokeFunction' 114 | Principal: 'sns.amazonaws.com' 115 | SourceArn: !Ref SNSTopic 116 | 117 | # 118 | # Outputs to be used by other CloudFormation templates if needed. 119 | # 120 | Outputs: 121 | LambdaFunction: 122 | Description: Lambda Function 123 | Value: !Ref LambdaFunction 124 | Export: 125 | Name: !Sub '${AWS::StackName}-LambdaFunction' 126 | SNSTopic: 127 | Description: SNS Topic 128 | Value: !Ref SNSTopic 129 | Export: 130 | Name: !Sub '${AWS::StackName}-SNSTopic' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/sns/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM!'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/sns/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-lambda-sns", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-lambda # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # vpc: '../vpc' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | # parameters: 18 | # VpcStackName: '${stack.parents.vpc.stack.name}' 19 | 20 | # 21 | # Define stack capabilities required. 22 | # 23 | capabilities: 24 | - 'CAPABILITY_IAM' 25 | 26 | # 27 | # Define global tags. 28 | # 29 | # tags: 30 | # app: 'cim-stack' 31 | 32 | lambda: 33 | functions: 34 | - 35 | function: ${stack.outputs.LambdaFunction} 36 | zip_file: index.zip 37 | deploy: 38 | phases: 39 | pre_deploy: 40 | commands: 41 | # Install all npm packages including dev packages. 42 | - npm install 43 | 44 | # Run the tests 45 | # - npm test 46 | 47 | # Remove all the npm packages. 48 | - rm -Rf node_modules 49 | 50 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 51 | - npm install --production 52 | 53 | # Zip the Lambda for upload to S3. 54 | - zip -r index.zip . 55 | post_deploy: 56 | commands: 57 | # Remove the zip file. 58 | - rm -Rf index.zip 59 | 60 | # Reinstall the dev npm packages. 61 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Role that our Lambda will assume to provide access to other AWS resources 18 | # 19 | IamRoleLambdaExecution: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | AssumeRolePolicyDocument: 23 | Version: '2012-10-17' 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: 28 | - lambda.amazonaws.com 29 | Action: 30 | - sts:AssumeRole 31 | Path: '/' 32 | 33 | # 34 | # Create a Policy and attach it to our Lambda Role. 35 | # 36 | IamPolicyLambdaExecution: 37 | Type: AWS::IAM::Policy 38 | Properties: 39 | PolicyName: IamPolicyLambdaExecution 40 | PolicyDocument: 41 | Version: '2012-10-17' 42 | Statement: 43 | - Effect: Allow 44 | Action: 45 | - logs:CreateLogGroup 46 | - logs:CreateLogStream 47 | - logs:PutLogEvents 48 | Resource: '*' 49 | 50 | # Add additional permissions here. What other AWS resources does your Lambda need access to? 51 | 52 | Roles: 53 | - Ref: IamRoleLambdaExecution 54 | 55 | # 56 | # Our Lambda function. 57 | # 58 | LambdaFunction: 59 | Type: AWS::Lambda::Function 60 | Properties: 61 | Handler: index.handler 62 | Timeout: 5 63 | Role: 64 | Fn::GetAtt: 65 | - IamRoleLambdaExecution 66 | - Arn 67 | Code: 68 | ZipFile: !Sub | 69 | 'use strict'; 70 | 71 | exports.handler = function(event, context) { 72 | console.log(JSON.stringify(event)); 73 | context.succeed('Hello CIM!'); 74 | }; 75 | Runtime: nodejs6.10 76 | 77 | # 78 | # Outputs to be used by other CloudFormation templates if needed. 79 | # 80 | Outputs: 81 | LambdaFunction: 82 | Description: Lambda Function 83 | Value: !Ref LambdaFunction 84 | Export: 85 | Name: !Sub '${AWS::StackName}-LambdaFunction' -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | // 6 | // Update this function as needed to support your business logic. 7 | // 8 | exports.handler = function(event, context) { 9 | winston.info(JSON.stringify(event)); 10 | context.succeed('Hello CIM!'); 11 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/Lambda/nodejs/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim-stack", 3 | "version": "1.0.0", 4 | "description": "CIM lambda-nodejs stack.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ServerlessApi { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'serverless-api', 20 | description: 'API Gateway proxying calls to a Lambda backend. Optional custom domain.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ServerlessApi; -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/README.md: -------------------------------------------------------------------------------- 1 | # API Gateway proxying calls to a Lambda backend. Optional custom domain. 2 | This template will create the following AWS resources: 3 | - Lambda to handle api calls 4 | - API Gateway to proxy HTTP requests to the Lambda backend 5 | - Certificate Manager to provide the SSL cert 6 | - Route53 to perform the DNS routing 7 | 8 | [![](architecture.png)](architecture.png) 9 | 10 | # Setup 11 | ## Prerequisites 12 | - Register your domain with Route53 13 | - Or point your existing domain to Route53 14 | - Configure 'admin@yourdomain.com' to receive the SSL verification email 15 | - You will have to confirm this email address. This is annoying and I've asked AWS to remove this step if the domain is used with Route53. 16 | 17 | ## Stack Up 18 | - Replace 'example.com' in _cim.yml with your domain name. 19 | - Run `cim stack-up` 20 | 21 | ## Deploy Lambda 22 | `cim lambda-deploy` 23 | 24 | ## dev stage 25 | You can also deploy a dev environment for testing. 26 | 27 | ``` 28 | cim stack-up --stage=dev 29 | cim deploy-lambda --stage=dev 30 | ``` 31 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-serverless-api # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | # 8 | # Reference parent stacks fo included shared information like stack name. 9 | # 10 | #parents: 11 | # vpc: '../vpc' 12 | 13 | # 14 | # Define stack input parameters. 15 | # 16 | # Optional - Custom domain name settings. If you specify 'TLD' you must also specify a 'Domain'. 17 | # After you 'stack-up' you will need to verify your email address so that AWS can issue the SSL certificate for you domain. 18 | # More info here: http://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate.html 19 | parameters: 20 | # The top level domain that you created in Route53. 21 | TLD: 'example.com' 22 | # The domain you wish to use for this api. 23 | Domain: 'api.example.com' 24 | 25 | # 26 | # Define stack capabilities required. 27 | # 28 | capabilities: 29 | - 'CAPABILITY_IAM' 30 | 31 | # 32 | # Override for 'dev' environment. For testing purposes. 33 | # 34 | stage: 35 | dev: 36 | stack: 37 | name: cim-serverless-api-dev 38 | parameters: 39 | Domain: 'dev-api.example.com' 40 | 41 | # 42 | # Define global tags. 43 | # 44 | #tags: 45 | # app: 'cim-stack' 46 | 47 | lambda: 48 | functions: 49 | - 50 | function: ${stack.outputs.LambdaFunction} 51 | zip_file: index.zip 52 | deploy: 53 | phases: 54 | pre_deploy: 55 | commands: 56 | # Install all npm packages including dev packages. 57 | - npm install 58 | 59 | # Run the tests 60 | # - npm test 61 | 62 | # Remove all the npm packages. 63 | - rm -Rf node_modules 64 | 65 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 66 | - npm install --production 67 | 68 | # Zip the Lambda for upload to S3. 69 | - zip -r index.zip . 70 | post_deploy: 71 | commands: 72 | # Remove the zip file. 73 | - rm -Rf index.zip 74 | 75 | # Reinstall the dev npm packages. 76 | - npm install -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thestackshack/cim/e4badb8c24af2a38ba6a3719c8e67d7ac3d5b8d1/lib/plugins/aws/ServerlessApi/template/architecture.png -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Parameters: 4 | TLD: 5 | Type: String 6 | Description: TLD name needed by Route53 to perform DNS (example.com) 7 | Default: '' 8 | Domain: 9 | Type: String 10 | Description: Domain name for your api (api.example.com) 11 | Default: '' 12 | Path: 13 | Type: String 14 | Description: The path part of your api (api.example.com/path) 15 | Default: 'api' 16 | Stage: 17 | Type: String 18 | Description: The deployment stage used by API Gateway 19 | Default: 'api' 20 | 21 | Conditions: 22 | UseCustomDomain: !And 23 | - !Not [!Equals [!Ref TLD, '']] 24 | - !Not [!Equals [!Ref Domain, '']] 25 | 26 | Resources: 27 | 28 | # 29 | # Role that our Lambda will assume to provide access to other AWS resources 30 | # 31 | IamRoleLambdaExecution: 32 | Type: AWS::IAM::Role 33 | Properties: 34 | AssumeRolePolicyDocument: 35 | Version: '2012-10-17' 36 | Statement: 37 | - Effect: Allow 38 | Principal: 39 | Service: 40 | - lambda.amazonaws.com 41 | Action: 42 | - sts:AssumeRole 43 | Path: '/' 44 | 45 | # 46 | # Create a Policy and attach it to our Lambda Role. 47 | # 48 | IamPolicyLambdaExecution: 49 | Type: AWS::IAM::Policy 50 | Properties: 51 | PolicyName: IamPolicyLambdaExecution 52 | PolicyDocument: 53 | Version: '2012-10-17' 54 | Statement: 55 | - Effect: Allow 56 | Action: 57 | - logs:CreateLogGroup 58 | - logs:CreateLogStream 59 | Resource: arn:aws:logs:us-east-1:*:* 60 | - Effect: Allow 61 | Action: 62 | - logs:PutLogEvents 63 | Resource: arn:aws:logs:us-east-1:*:* 64 | Resource: '*' 65 | Roles: 66 | - Ref: IamRoleLambdaExecution 67 | 68 | # 69 | # Our Lambda function. Basic code has been added. You will replace the code later via your Github repo. 70 | # 71 | LambdaFunction: 72 | Type: AWS::Lambda::Function 73 | Properties: 74 | Handler: index.handler 75 | Timeout: 5 76 | Role: 77 | Fn::GetAtt: 78 | - IamRoleLambdaExecution 79 | - Arn 80 | Code: 81 | ZipFile: !Sub | 82 | 'use strict'; 83 | 84 | exports.handler = function(event, context, callback) { 85 | const response = { 86 | statusCode: 200, 87 | body: JSON.stringify({ 88 | message: `Hello CIM`, 89 | event: event 90 | }) 91 | }; 92 | 93 | callback(null, response); 94 | }; 95 | Runtime: nodejs6.10 96 | 97 | # 98 | # Create the API Gateway 99 | # 100 | RestApi: 101 | Type: AWS::ApiGateway::RestApi 102 | Properties: 103 | Name: ApiGatewayRestApi 104 | 105 | ApiGatewayResource: 106 | Type: AWS::ApiGateway::Resource 107 | Properties: 108 | ParentId: !GetAtt RestApi.RootResourceId 109 | PathPart: !Ref Path #ex. example.com/api. 110 | RestApiId: !Ref RestApi 111 | 112 | ApiGatewayMethodOptions: 113 | Type: AWS::ApiGateway::Method 114 | Properties: 115 | AuthorizationType: NONE 116 | ResourceId: !Ref ApiGatewayResource 117 | RestApiId: !Ref RestApi 118 | HttpMethod: OPTIONS 119 | Integration: 120 | IntegrationResponses: 121 | - StatusCode: 200 122 | ResponseParameters: 123 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'" 124 | method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" 125 | method.response.header.Access-Control-Allow-Origin: "'*'" 126 | method.response.header.Access-Control-Allow-Credentials: "'false'" 127 | ResponseTemplates: 128 | application/json: '' 129 | PassthroughBehavior: WHEN_NO_MATCH 130 | RequestTemplates: 131 | application/json: '{"statusCode": 200}' 132 | Type: MOCK 133 | MethodResponses: 134 | - StatusCode: 200 135 | ResponseModels: 136 | application/json: 'Empty' 137 | ResponseParameters: 138 | method.response.header.Access-Control-Allow-Headers: false 139 | method.response.header.Access-Control-Allow-Methods: false 140 | method.response.header.Access-Control-Allow-Origin: false 141 | method.response.header.Access-Control-Allow-Credentials: true 142 | 143 | ApiGatewayMethodPost: 144 | Type: AWS::ApiGateway::Method 145 | Properties: 146 | HttpMethod: POST 147 | RequestParameters: {} 148 | ResourceId: !Ref ApiGatewayResource 149 | RestApiId: !Ref RestApi 150 | AuthorizationType: NONE 151 | Integration: 152 | IntegrationHttpMethod: POST 153 | Type: AWS_PROXY 154 | Uri: 155 | Fn::Join: 156 | - '' 157 | - - 'arn:aws:apigateway:' 158 | - Ref: AWS::Region 159 | - ':lambda:path/2015-03-31/functions/' 160 | - !GetAtt LambdaFunction.Arn 161 | - '/invocations' 162 | MethodResponses: [] 163 | 164 | ApiGatewayMethodGet: 165 | Type: AWS::ApiGateway::Method 166 | Properties: 167 | HttpMethod: GET 168 | RequestParameters: {} 169 | ResourceId: !Ref ApiGatewayResource 170 | RestApiId: !Ref RestApi 171 | AuthorizationType: NONE 172 | Integration: 173 | IntegrationHttpMethod: POST 174 | Type: AWS_PROXY 175 | Uri: 176 | Fn::Join: 177 | - '' 178 | - - 'arn:aws:apigateway:' 179 | - Ref: AWS::Region 180 | - ':lambda:path/2015-03-31/functions/' 181 | - !GetAtt LambdaFunction.Arn 182 | - '/invocations' 183 | MethodResponses: [] 184 | 185 | ApiGatewayDeployment: 186 | Type: AWS::ApiGateway::Deployment 187 | Properties: 188 | RestApiId: !Ref RestApi 189 | StageName: !Ref Stage # Maps to the custom domain name. BasePathMapping.Stage 190 | DependsOn: 191 | - ApiGatewayMethodPost 192 | - ApiGatewayMethodGet 193 | - ApiGatewayMethodOptions 194 | 195 | # 196 | # We need to give API Gateway permission to invoke our Lambda function. 197 | # 198 | PermissionForAPIGatewayToInvokeLambda: 199 | Type: AWS::Lambda::Permission 200 | Properties: 201 | Action: lambda:invokeFunction 202 | FunctionName: !Ref LambdaFunction 203 | Principal: apigateway.amazonaws.com 204 | SourceArn: 205 | Fn::Join: 206 | - '' 207 | - - 'arn:aws:execute-api:' 208 | - Ref: AWS::Region 209 | - ':' 210 | - Ref: AWS::AccountId 211 | - ':' 212 | - Ref: RestApi 213 | - '/*/*' 214 | 215 | # 216 | # SSL Certificate needed by CloudFront. 217 | # 218 | SSL: 219 | Type: AWS::CertificateManager::Certificate 220 | Condition: UseCustomDomain 221 | Properties: 222 | DomainName: !Ref Domain 223 | DomainValidationOptions: 224 | - DomainName: !Ref Domain 225 | ValidationDomain: !Ref TLD 226 | 227 | # 228 | # Custom Domain Name 229 | # 230 | ApiDomainName: 231 | Type: AWS::ApiGateway::DomainName 232 | Condition: UseCustomDomain 233 | Properties: 234 | DomainName: !Ref Domain 235 | CertificateArn: !Ref SSL 236 | 237 | # 238 | # Wire custom domain to Api Gateway 239 | # 240 | BasePathMapping: 241 | Type: AWS::ApiGateway::BasePathMapping 242 | Condition: UseCustomDomain 243 | Properties: 244 | DomainName: !Ref ApiDomainName 245 | RestApiId: !Ref RestApi 246 | Stage: !Ref Stage 247 | 248 | # 249 | # Route53 DNS record set to map our domain to API Gateway 250 | # 251 | DomainDNS: 252 | Type: AWS::Route53::RecordSetGroup 253 | Condition: UseCustomDomain 254 | Properties: 255 | HostedZoneName: 256 | Fn::Join: 257 | - '' 258 | - - !Ref TLD 259 | - '.' 260 | RecordSets: 261 | - 262 | Name: !Ref Domain 263 | Type: 'A' 264 | AliasTarget: 265 | HostedZoneId: 'Z2FDTNDATAQYW2' # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid 266 | DNSName: !GetAtt ApiDomainName.DistributionDomainName 267 | 268 | Outputs: 269 | LambdaFunction: 270 | Description: Lambda Function 271 | Value: !Ref LambdaFunction 272 | Export: 273 | Name: !Sub '${AWS::StackName}-LambdaFunction' 274 | ApiGatewayUrl: 275 | Description: URL of your API endpoint 276 | Value: !Join 277 | - '' 278 | - - 'https://' 279 | - !Ref RestApi 280 | - '.execute-api.' 281 | - !Ref AWS::Region 282 | - '.amazonaws.com/' 283 | - !Ref Stage 284 | - '/' 285 | - !Ref Path 286 | CustomDomainUrl: 287 | Description: URL of your API endpoint 288 | Condition: UseCustomDomain 289 | Value: !Join 290 | - '' 291 | - - 'https://' 292 | - !Ref Domain 293 | - '/' 294 | - !Ref Path 295 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.handler = function(event, context, callback) { 4 | const response = { 5 | statusCode: 200, 6 | body: JSON.stringify({ 7 | message: `Hello CIM`, 8 | event: event 9 | }) 10 | }; 11 | 12 | callback(null, response); 13 | }; -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessApi/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-api", 3 | "version": "1.0.0", 4 | "description": "Serverless API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "winston": "^2.3.1" 11 | }, 12 | "devDependencies": { 13 | "aws-sdk": "^2.110.0", 14 | "mocha": "^3.5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class ServerlessWebApp { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'serverless-web-app', 20 | description: 'Static S3 website with SSL, CDN, and CI/CD.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = ServerlessWebApp; -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/README.md: -------------------------------------------------------------------------------- 1 | # Static S3 website with SSL, CDN, and CI/CD 2 | This template will create the following static website: 3 | - S3 bucket to store and serve static website 4 | - CloudFront to cache and serve website over SSL and using your custom domain 5 | - Certificate Manager to provide the SSL cert 6 | - Route53 to perform the DNS routing 7 | 8 | [![](architecture.png)](architecture.png) 9 | 10 | # Setup 11 | ## Prerequisites 12 | - Register your domain with Route53 13 | - Or point your existing domain to Route53 14 | - Configure 'admin@yourdomain.com' to receive the SSL verification email 15 | - You will have to confirm this email address. This is annoying and I've asked AWS to remove this step if the domain is used with Route53. 16 | 17 | ## Stack Up 18 | - Replace 'example.com' in _cim.yml with your domain name. 19 | - Run `cim stack-up` 20 | 21 | ## CI/CD 22 | CI/CD is available. When enabled all commits to GitHub will trigger CodePipeline to build, test, and deploy your site. 23 | 24 | ## dev stage 25 | You can also deploy a dev environment for testing. 26 | 27 | `cim stack-up --stage=dev` 28 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: static-website # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | # 8 | # Reference parent stacks fo included shared information like stack name. 9 | # 10 | # parents: 11 | # vpc: '../vpc' 12 | 13 | # 14 | # Define stack input parameters. 15 | # 16 | parameters: 17 | # The top level domain that you created in Route53. 18 | TLD: 'example.com' 19 | # The domain you wish to use for this static website. 20 | Domain: 'example.com' 21 | # A redirect you wish to use. 22 | Redirect: 'www.example.com' 23 | 24 | # CI/CD 25 | #GitHubOwner: 'thestackshack' 26 | #GitHubRepo: 'serverless-demo' 27 | #GitHubToken: '${kms.decrypt(AQICAHgiu9XuQb4FZRXrLn/77g1P99ZhS7/g3xOsvbvNpb+/qQH+sxP+if0SN0/QR0I3M9ehAAAAhzCBhAYJKoZIhvcNAQcGoHcwdQIBADBwBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNTJCu1YrFM64MUL4AIBEIBD8zDL0Oc+8rQwr/7fJq+NyPB7vKJ/lNqqcmBBN9QS8XDFAqB9Vh9fkCUSilXs3HG3NL6EdLYaR9Z5blo7p2/HTmJrnw==)}' 28 | #GitHubBranch: 'master' 29 | # 30 | # Define stack capabilities required. 31 | # 32 | capabilities: 33 | - 'CAPABILITY_IAM' 34 | 35 | # 36 | # Define global tags. 37 | # 38 | # tags: 39 | # app: 'example.com' 40 | 41 | stage: 42 | dev: 43 | stack: 44 | name: static-website-dev 45 | parameters: 46 | Domain: 'dev.example.com' 47 | Redirect: '' 48 | 49 | # CI/CD 50 | #GitHubBranch: 'develop' 51 | 52 | # 53 | # README: 54 | # - This template has an additional 'dev' stage. This can be hooked up to your 'develop' GIT branch. 55 | # This is nice for testing changes before pushing to production. 56 | # 57 | # - After you 'stack-up' you will need to verify your email address so that AWS can issue the SSL certificate for you domain. 58 | # More info here: http://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate.html 59 | # -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thestackshack/cim/e4badb8c24af2a38ba6a3719c8e67d7ac3d5b8d1/lib/plugins/aws/ServerlessWebApp/template/architecture.png -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | phases: 3 | install: 4 | commands: 5 | pre_build: 6 | commands: 7 | build: 8 | commands: 9 | - aws s3 sync www "s3://${BUCKET_NAME}" --acl bucket-owner-full-control --acl public-read --delete --cache-control "max-age=1" --exclude www/assets 10 | - aws s3 sync www/assets "s3://${BUCKET_NAME}/assets" --acl bucket-owner-full-control --acl public-read --delete --cache-control "max-age=31536000" 11 | post_build: 12 | commands: 13 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | Parameters: 7 | TLD: 8 | Type: String 9 | Description: TLD name needed by Route53 to perform DNS (example.com) 10 | Domain: 11 | Type: String 12 | Description: Domain name for your website (example.com) 13 | Redirect: 14 | Type: String 15 | Description: Redirect to Domain (www.example.com redirects to example.com) 16 | Default: '' 17 | GitHubOwner: 18 | Type: String 19 | Description: GitHub repository owner 20 | Default: '' 21 | GitHubRepo: 22 | Type: String 23 | Default: aws-codepipeline-nested-stack 24 | Description: GitHub repository name 25 | Default: '' 26 | GitHubToken: 27 | Type: String 28 | Description: GitHub repository OAuth token 29 | Default: '' 30 | GitHubBranch: 31 | Type: String 32 | Description: GitHub repository branch 33 | Default: '' 34 | 35 | Conditions: 36 | NeedsRedirect: !Not [!Equals [!Ref Redirect, '']] 37 | NeedsCICD: !And 38 | - !Not [!Equals [!Ref GitHubOwner, '']] 39 | - !Not [!Equals [!Ref GitHubRepo, '']] 40 | - !Not [!Equals [!Ref GitHubToken, '']] 41 | - !Not [!Equals [!Ref GitHubBranch, '']] 42 | 43 | 44 | # 45 | # CloudFormation resources 46 | # 47 | Resources: 48 | 49 | # 50 | # Website buckets 51 | # 52 | WebsiteBucket: 53 | Type: AWS::S3::Bucket 54 | Properties: 55 | BucketName: !Ref Domain 56 | AccessControl: PublicRead 57 | WebsiteConfiguration: 58 | IndexDocument: 'index.html' 59 | ErrorDocument: '404.html' 60 | 61 | RedirectBucket: 62 | Type: AWS::S3::Bucket 63 | Condition: NeedsRedirect 64 | Properties: 65 | BucketName: !Ref Redirect 66 | AccessControl: BucketOwnerFullControl 67 | WebsiteConfiguration: 68 | RedirectAllRequestsTo: 69 | HostName: !Ref Domain 70 | 71 | # 72 | # SSL Certificate needed by CloudFront. 73 | # 74 | SSL: 75 | Type: AWS::CertificateManager::Certificate 76 | Properties: 77 | DomainName: !Ref Domain 78 | DomainValidationOptions: 79 | - DomainName: !Ref Domain 80 | ValidationDomain: !Ref TLD 81 | 82 | # 83 | # CloudFront CDN 84 | # 85 | CDN: 86 | Type: AWS::CloudFront::Distribution 87 | Properties: 88 | DistributionConfig: 89 | Aliases: 90 | - !Ref Domain 91 | Enabled: true 92 | PriceClass: 'PriceClass_All' 93 | CacheBehaviors: 94 | - 95 | TargetOriginId: !Ref WebsiteBucket 96 | PathPattern: '*.js' 97 | ViewerProtocolPolicy: 'redirect-to-https' 98 | MinTTL: 0 99 | AllowedMethods: 100 | - 'HEAD' 101 | - 'GET' 102 | CachedMethods: 103 | - 'HEAD' 104 | - 'GET' 105 | ForwardedValues: 106 | QueryString: true 107 | Cookies: 108 | Forward: 'none' 109 | - 110 | TargetOriginId: !Ref WebsiteBucket 111 | PathPattern: '*.css' 112 | ViewerProtocolPolicy: 'redirect-to-https' 113 | MinTTL: 0 114 | AllowedMethods: 115 | - 'HEAD' 116 | - 'GET' 117 | CachedMethods: 118 | - 'HEAD' 119 | - 'GET' 120 | ForwardedValues: 121 | QueryString: true 122 | Cookies: 123 | Forward: 'none' 124 | DefaultCacheBehavior: 125 | TargetOriginId: !Ref WebsiteBucket 126 | ViewerProtocolPolicy: 'redirect-to-https' 127 | MinTTL: 0 128 | AllowedMethods: 129 | - 'HEAD' 130 | - 'GET' 131 | CachedMethods: 132 | - 'HEAD' 133 | - 'GET' 134 | ForwardedValues: 135 | QueryString: false 136 | Cookies: 137 | Forward: none 138 | Origins: 139 | - 140 | Id: !Ref WebsiteBucket 141 | DomainName: 142 | Fn::Join: 143 | - '' 144 | - - !Ref Domain 145 | - '.s3-website-' 146 | - !Ref AWS::Region 147 | - '.amazonaws.com' 148 | CustomOriginConfig: 149 | HTTPPort: 80 150 | HTTPSPort: 443 151 | OriginProtocolPolicy: 'http-only' 152 | Restrictions: 153 | GeoRestriction: 154 | RestrictionType: 'none' 155 | ViewerCertificate: 156 | SslSupportMethod: 'sni-only' 157 | MinimumProtocolVersion: 'TLSv1' 158 | AcmCertificateArn: !Ref SSL 159 | 160 | # 161 | # Route53 DNS record set to map our domain to our CDN 162 | # 163 | DomainDNS: 164 | Type: AWS::Route53::RecordSetGroup 165 | Properties: 166 | HostedZoneName: 167 | Fn::Join: 168 | - '' 169 | - - !Ref TLD 170 | - '.' 171 | RecordSets: 172 | - 173 | Name: !Ref Domain 174 | Type: 'A' 175 | AliasTarget: 176 | HostedZoneId: 'Z2FDTNDATAQYW2' # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid 177 | DNSName: !GetAtt CDN.DomainName 178 | 179 | # 180 | # Route53 DNS record set to map our redirect to our domain (www.example.com -> example.com) 181 | # 182 | RedirectDNS: 183 | Type: AWS::Route53::RecordSet 184 | Condition: NeedsRedirect 185 | Properties: 186 | HostedZoneName: 187 | Fn::Join: 188 | - '' 189 | - - !Ref TLD 190 | - '.' 191 | Name: !Ref Redirect 192 | Type: 'CNAME' 193 | TTL: 900 194 | ResourceRecords: 195 | - 196 | Fn::Join: 197 | - '' 198 | - - !Ref Domain 199 | - '.s3-website-' 200 | - !Ref AWS::Region 201 | - '.amazonaws.com' 202 | 203 | # 204 | # CI/CD 205 | # 206 | ArtifactsBucket: 207 | Type: AWS::S3::Bucket 208 | Condition: NeedsCICD 209 | Properties: 210 | AccessControl: Private 211 | 212 | # 213 | # CodeBuild Permissions 214 | # 215 | CodeBuildRole: 216 | Type: AWS::IAM::Role 217 | Condition: NeedsCICD 218 | Properties: 219 | AssumeRolePolicyDocument: 220 | Version: 2012-10-17 221 | Statement: 222 | - Effect: Allow 223 | Action: 224 | - sts:AssumeRole 225 | Principal: 226 | Service: 227 | - codebuild.amazonaws.com 228 | 229 | CodeBuildRolePolicy: 230 | Type: AWS::IAM::Policy 231 | Condition: NeedsCICD 232 | Properties: 233 | PolicyName: CodeBuildRolePolicy 234 | PolicyDocument: 235 | Version: 2012-10-17 236 | Statement: 237 | - Effect: Allow 238 | Action: 239 | - logs:CreateLogGroup 240 | - logs:CreateLogStream 241 | - logs:PutLogEvents 242 | Resource: '*' 243 | - Effect: Allow 244 | Action: 245 | - s3:* 246 | Resource: 247 | - !Sub 'arn:aws:s3:::${ArtifactsBucket}' 248 | - !Sub 'arn:aws:s3:::${ArtifactsBucket}/*' 249 | - !Sub 'arn:aws:s3:::${WebsiteBucket}' 250 | - !Sub 'arn:aws:s3:::${WebsiteBucket}/*' 251 | Roles: 252 | - !Ref CodeBuildRole 253 | 254 | # 255 | # CodeBuild 256 | # 257 | CodeBuildProject: 258 | Type: AWS::CodeBuild::Project 259 | Condition: NeedsCICD 260 | Properties: 261 | Artifacts: 262 | Type: CODEPIPELINE 263 | Environment: 264 | ComputeType: BUILD_GENERAL1_SMALL 265 | Image: aws/codebuild/eb-nodejs-6.10.0-amazonlinux-64:4.0.0 266 | Type: LINUX_CONTAINER 267 | EnvironmentVariables: 268 | - 269 | Name: 'BUCKET_NAME' 270 | Value: !Ref WebsiteBucket 271 | Name: 272 | Fn::Join: 273 | - '' 274 | - - Ref: AWS::StackName 275 | - '-code-build' 276 | ServiceRole: !GetAtt CodeBuildRole.Arn 277 | Source: 278 | Type: CODEPIPELINE 279 | BuildSpec: buildspec.yml 280 | TimeoutInMinutes: 5 # must be between 5 minutes and 8 hours 281 | 282 | # 283 | # CodePipeline Permissions 284 | # 285 | CodePipelineRole: 286 | Type: AWS::IAM::Role 287 | Condition: NeedsCICD 288 | Properties: 289 | AssumeRolePolicyDocument: 290 | Version: 2012-10-17 291 | Statement: 292 | - Effect: Allow 293 | Action: 294 | - sts:AssumeRole 295 | Principal: 296 | Service: 297 | - codepipeline.amazonaws.com 298 | ManagedPolicyArns: 299 | - arn:aws:iam::aws:policy/AdministratorAccess 300 | 301 | # 302 | # CodePipeline 303 | # 304 | CodePipeline: 305 | Type: AWS::CodePipeline::Pipeline 306 | Condition: NeedsCICD 307 | Properties: 308 | ArtifactStore: 309 | Type: S3 310 | Location: !Ref ArtifactsBucket 311 | RestartExecutionOnUpdate: true 312 | RoleArn: !GetAtt CodePipelineRole.Arn 313 | Stages: 314 | - Name: Source 315 | Actions: 316 | - Name: Source 317 | ActionTypeId: 318 | Category: Source 319 | Owner: ThirdParty 320 | Version: 1 321 | Provider: GitHub 322 | Configuration: 323 | Owner: !Ref GitHubOwner 324 | Repo: !Ref GitHubRepo 325 | Branch: !Ref GitHubBranch 326 | OAuthToken: !Ref GitHubToken 327 | OutputArtifacts: 328 | - Name: SourceOutput 329 | RunOrder: 1 330 | - Name: Build 331 | Actions: 332 | - Name: BuildTestDeploy 333 | ActionTypeId: 334 | Category: Build 335 | Owner: AWS 336 | Provider: CodeBuild 337 | Version: 1 338 | Configuration: 339 | ProjectName: !Ref CodeBuildProject 340 | InputArtifacts: 341 | - Name: SourceOutput 342 | OutputArtifacts: 343 | - Name: BuildOutput 344 | 345 | # 346 | # Outputs to be used by other CloudFormation templates if needed. 347 | # 348 | #Outputs: 349 | # LambdaFunction: 350 | # Description: Lambda Function 351 | # Value: !Ref LambdaFunction 352 | # Export: 353 | # Name: !Sub '${AWS::StackName}-LambdaFunction' 354 | # S3Bucket: 355 | # Description: S3 Bucket 356 | # Value: !Ref S3Bucket 357 | # Export: 358 | # Name: !Sub '${AWS::StackName}-S3Bucket' 359 | -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/www/assets/css/app.css: -------------------------------------------------------------------------------- 1 | img { 2 | border: 1px solid black; 3 | } -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/www/assets/images/img.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thestackshack/cim/e4badb8c24af2a38ba6a3719c8e67d7ac3d5b8d1/lib/plugins/aws/ServerlessWebApp/template/www/assets/images/img.jpeg -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/www/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // 2 | // Add JavaScript 3 | // -------------------------------------------------------------------------------- /lib/plugins/aws/ServerlessWebApp/template/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CIM.sh 4 | 5 | 6 | 7 | 8 |

CIM.sh

9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/plugins/aws/VPC/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | class VPC { 6 | constructor() { 7 | } 8 | 9 | commands() { 10 | return []; 11 | } 12 | 13 | hooks() { 14 | return null; 15 | } 16 | 17 | template() { 18 | return { 19 | name: 'vpc', 20 | description: 'VPC - Modular and scalable virtual networking foundation on the AWS Cloud.', 21 | path: path.resolve(__dirname, 'template') 22 | } 23 | } 24 | 25 | } 26 | module.exports = VPC; -------------------------------------------------------------------------------- /lib/plugins/aws/VPC/template/README.md: -------------------------------------------------------------------------------- 1 | # VPC 2 | Modular and scalable virtual networking foundation on the AWS Cloud. 3 | 4 | [![](quickstart-vpc-design-fullscreen.png)](quickstart-vpc-design-fullscreen.png) 5 | 6 | More information about this VPC template can be found here: https://aws.amazon.com/quickstart/architecture/vpc/ 7 | 8 | ## Setup 9 | 1. If you don't already have an AWS account, create one at http://aws.amazon.com by following the on-screen instructions. Part of the sign-up process involves receiving a phone call and entering a PIN using the phone keypad. 10 | 2. Open the AWS Console and select your Region. 11 | 3. Create a key pair in your preferred region. To do this, in the navigation pane of the Amazon EC2 console, choose Key Pairs, Create Key Pair, type a name, and then choose Create. 12 | 4. Populate the `_cim.yml` file with your key pair and availablity zones. 13 | 5. Launch your stack `cim stack-up`. -------------------------------------------------------------------------------- /lib/plugins/aws/VPC/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: vpc # Note: Update this with your stack name 4 | template: 5 | file: aws-vpc.template 6 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. Stacks are uploaded here prior to deployment.' 7 | 8 | # 9 | # Reference parent stacks fo included shared information like stack name. 10 | # 11 | # parents: 12 | # base: '../base' 13 | 14 | # 15 | # Define stack input parameters. 16 | # 17 | parameters: 18 | AvailabilityZones: 'us-east-1a, us-east-1b' # Replace these with your AZ's 19 | KeyPairName: '' 20 | NumberOfAZs: '2' # This number must match the number of AZ's above. 21 | 22 | # 23 | # Define stack capabilities required. 24 | # 25 | # capabilities: 26 | # - 'CAPABILITY_IAM' 27 | 28 | # 29 | # Define global tags. 30 | # 31 | # tags: 32 | # app: 'cim-stack' 33 | -------------------------------------------------------------------------------- /lib/plugins/aws/VPC/template/quickstart-vpc-design-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thestackshack/cim/e4badb8c24af2a38ba6a3719c8e67d7ac3d5b8d1/lib/plugins/aws/VPC/template/quickstart-vpc-design-fullscreen.png -------------------------------------------------------------------------------- /lib/plugins/lib/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const async = require('async'); 5 | const fs = require("fs-extra"); 6 | const path = require('path'); 7 | 8 | var functions = {}; 9 | 10 | functions.validate = function(cim, done) { 11 | const template = _.find(cim.templates, {name: cim.args.template}); 12 | if (!template) { 13 | done('Invalid \'template\', must be one of: '+JSON.stringify(cim.templates, null, 3), cim); 14 | } else if (fs.existsSync(path.resolve(cim.args.dir, '_cim.yml'))) { 15 | done('The following directory already contains a \'_cim.yml\' file: '+cim.args.dir, cim); 16 | } else { 17 | done(null, cim); 18 | } 19 | }; 20 | 21 | functions.create = function(cim, done) { 22 | const template = _.find(cim.templates, {name: cim.args.template}); 23 | fs.copy(template.path, cim.args.dir, function(err) { 24 | if (!err) { 25 | console.log(' ______ __ __ __'); 26 | console.log('/\\ ___\\ /\\ \\ /\\ "-./ \\'); 27 | console.log('\\ \\ \\____ \\ \\ \\ \\ \\ \\-./\\ \\ '); 28 | console.log(' \\ \\_____\\ \\ \\_\\ \\ \\_\\ \\ \\_\\'); 29 | console.log(' \\/_____/ \\/_/ \\/_/ \\/_/'); 30 | console.log(''); 31 | console.log(cim.args.template + ' has been created. Thanks for using CIM!'); 32 | } 33 | done(err); 34 | }); 35 | }; 36 | 37 | module.exports = { 38 | create(cim, done) { 39 | async.waterfall([ 40 | async.constant(cim), 41 | functions.validate, 42 | functions.create 43 | ], done); 44 | } 45 | }; -------------------------------------------------------------------------------- /lib/plugins/lib/templates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | templates(cim, done) { 7 | _.forEach(cim.templates, function(template) { 8 | console.log('Name: '+template.name); 9 | console.log('Description: '+template.description); 10 | console.log(''); 11 | }); 12 | done(); 13 | } 14 | }; -------------------------------------------------------------------------------- /lib/util/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | const CACHE_FILE = '.cim.json'; 8 | 9 | var functions = {}; 10 | 11 | functions.get_cache = function(done) { 12 | fs.readFile(path.resolve(os.homedir(), CACHE_FILE), function (err, data) { 13 | if (err) { 14 | done(null, {}); 15 | } else { 16 | done(null, JSON.parse(data)); 17 | } 18 | }); 19 | }; 20 | 21 | functions.store_cache = function(cache, done) { 22 | fs.writeFile(path.resolve(os.homedir(), CACHE_FILE), JSON.stringify(cache, null, 3), function(err) { 23 | if (err) console.log(JSON.stringify(err, null, 3)); 24 | done(); 25 | }); 26 | }; 27 | 28 | module.exports = functions; -------------------------------------------------------------------------------- /lib/util/configs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const async = require('async'); 3 | const _ = require('lodash'); 4 | const fs = require('fs'); 5 | const yaml = require('js-yaml'); 6 | const path = require('path'); 7 | const glob = require("glob"); 8 | const traverse = require("traverse"); 9 | 10 | var AWS = require('aws-sdk'); 11 | var kms; 12 | 13 | var functions = {}; 14 | 15 | var init = function(input) { 16 | if (!kms) { 17 | if (input.params && input.params.profile) { 18 | var credentials = new AWS.SharedIniFileCredentials({profile: input.params.profile}); 19 | AWS.config.credentials = credentials; 20 | } 21 | kms = new AWS.KMS(); 22 | } 23 | }; 24 | 25 | functions.find = function(cim, done) { 26 | // 27 | // Recursively find all the stacks to update. 28 | // 29 | glob(cim.args.recursive ? '**/_cim.yml' : '_cim.yml', { 30 | cwd: cim.args.dir, 31 | absolute: true 32 | }, done); 33 | }; 34 | 35 | functions.parse = function(files, done) { 36 | // 37 | // Parse all config files found. 38 | // 39 | async.map(files, function(file, mapNext) { 40 | // Get document, or throw exception on error 41 | try { 42 | var data = yaml.safeLoad(fs.readFileSync(file, 'utf8')); 43 | data._cim = { 44 | file: file 45 | }; 46 | mapNext(null, data); 47 | } catch (err) { 48 | mapNext(err, null); 49 | } 50 | }, done); 51 | }; 52 | 53 | functions.load_missing_parents = function(stacks, done) { 54 | if (arguments.length == 3) { 55 | done = arguments[2]; 56 | var seen = arguments[1]; 57 | } else { 58 | var seen = []; 59 | } 60 | async.each(stacks, function (stack, eachNext) { 61 | if (stack.stack.parents) { 62 | var parents = {}; 63 | async.each(_.keys(stack.stack.parents), function (key, eachParentNext) { 64 | 65 | var parent = stack.stack.parents[key]; 66 | 67 | if (!_.isObject(parent)) { 68 | // if (_.includes(seen, parent)) { 69 | // return eachParentNext('Circular reference'); 70 | // } 71 | seen.push(parent); 72 | // Load the parent that isn't included in this update. We still need the name. 73 | try { 74 | var parent_data = yaml.safeLoad(fs.readFileSync(parent, 'utf8')); 75 | 76 | // Resolve the full path of any inner parents. We need to recursively load them as well. 77 | var inner_parents = {}; 78 | _.forEach(_.keys(parent_data.stack.parents), function(inner_parent_key) { 79 | var inner_parent_path = parent_data.stack.parents[inner_parent_key]; 80 | var inner_parent_full_path = path.resolve(path.resolve(path.dirname(parent), inner_parent_path), '_cim.yml'); 81 | if (!fs.existsSync(inner_parent_full_path)) { 82 | return eachParentNext('Unable to resolve parent: '+inner_parent_full_path); 83 | } 84 | inner_parents[inner_parent_key] = inner_parent_full_path; 85 | }); 86 | parent_data.stack.parents = inner_parents; 87 | 88 | // Add the parent. 89 | parent_data._cim = { 90 | file: parent, 91 | skip: true 92 | }; 93 | parents[key] = parent_data; 94 | eachParentNext(null); 95 | } catch (err) { 96 | eachParentNext(err); 97 | } 98 | } else { 99 | // if (_.includes(seen, parent)) { 100 | // return eachParentNext('Circular reference'); 101 | // } 102 | seen.push(parent._cim.file); 103 | parents[key] = parent; 104 | eachParentNext(null); 105 | } 106 | }, function (err) { 107 | if (err) return eachNext(err); 108 | else { 109 | stack.stack.parents = parents; 110 | functions.load_missing_parents(_.values(stack.stack.parents), seen, eachNext); 111 | } 112 | }); 113 | } else { 114 | eachNext(null); 115 | } 116 | }, function (err) { 117 | done(err, stacks); 118 | }); 119 | }; 120 | 121 | functions.validate = function(stacks, done) { 122 | async.each(stacks, function(stack, eachNext) { 123 | async.waterfall([ 124 | function(next) { 125 | // 126 | // Validate the basic structure of the config. 127 | // 128 | if (_.isNil(stack.stack)) { 129 | next('Invalid configuration. Must have a \'stack\' defined.'); 130 | } else if (_.isNil(stack.stack.name)) { 131 | next('Invalid configuration. Must have a \'stack.name\' defined.'); 132 | } else if (_.isNil(stack.stack.template)) { 133 | next('Invalid configuration. Must have a \'stack.template\' defined.'); 134 | } else if (_.isNil(stack.stack.template.file) || _.isNil(stack._cim) || _.isNil(stack._cim.file)) { 135 | next('Invalid configuration. Must have a \'stack.template.file\' defined.'); 136 | } else if (_.isNil(stack.stack.template.bucket)) { 137 | next('Invalid configuration. Must have a \'stack.template.bucket\' defined.'); 138 | } else { 139 | next(null); 140 | } 141 | }, 142 | function(next) { 143 | // 144 | // Validate that the template is available. 145 | // 146 | var template = 'cloudformation.yml'; 147 | if (!_.isNil(stack.stack) && !_.isNil(stack.stack.template) && !_.isNil(stack.stack.template.file)) { 148 | template = stack.stack.template.file; 149 | } 150 | const template_path = path.resolve(path.dirname(stack._cim.file), template); 151 | if (!fs.existsSync(template_path)) { 152 | next('Invalid configuration. Missing CloudFormation template: '+template_path); 153 | } else { 154 | stack.stack.template.file = template_path; 155 | next(null) 156 | } 157 | }, 158 | function(next) { 159 | // 160 | // Validate that parent references exists. 161 | // 162 | if (stack.stack.parents) { 163 | var parents = {}; 164 | var err = null; 165 | _.forEach(_.keys(stack.stack.parents), function(key) { 166 | var parent_path = stack.stack.parents[key]; 167 | var parent_full_path = path.resolve(path.resolve(path.dirname(stack._cim.file), parent_path), '_cim.yml'); 168 | if (!fs.existsSync(parent_full_path)) { 169 | err = 'Invalid configuration. Missing parent configuration: '+parent_full_path; 170 | return; 171 | } else { 172 | var parent = _.find(stacks, { _cim: { file: parent_full_path } }); 173 | if (parent) parents[key] = parent; 174 | else parents[key] = parent_full_path; 175 | } 176 | }); 177 | if (err) { 178 | return next(err); 179 | } 180 | stack.stack.parents = parents; 181 | } 182 | next(null); 183 | } 184 | ], eachNext); 185 | }, function(err) { 186 | done(err, stacks); 187 | }); 188 | }; 189 | 190 | functions.do_resolve_stack_params = function(obj, stack, params) { 191 | var compiled = _.template(obj); 192 | try { 193 | stack.env = process.env; 194 | if (params.env) { 195 | _.forEach(_.split(params.env, /\s*,\s*/), function(envStr) { 196 | var env = _.split(envStr, /\s*=\s*/); 197 | stack.env[env[0]] = env[1]; 198 | }); 199 | } 200 | stack.opt = params; 201 | var output = compiled(stack); 202 | delete stack.env; 203 | delete stack.opt; 204 | return output; 205 | } catch (err) { 206 | delete stack.env; 207 | delete stack.opt; 208 | throw err; 209 | } 210 | }; 211 | 212 | functions.resolve_stack = function(obj, stack, input, done) { 213 | async.eachOf(obj, function(value, key, next) { 214 | if (!_.isNil(value) && _.isString(value) && _.includes(value, '${') && !_.includes(value, '${stack.outputs') && !_.includes(value, '${kms.decrypt')) { 215 | try { 216 | obj[key] = functions.do_resolve_stack_params(value, stack, input.params); 217 | next(); 218 | } catch (err) { 219 | next(err); 220 | } 221 | } else if (!_.isNil(value) && _.isString(value) && _.includes(value, '${kms.decrypt')) { 222 | var before = value.substr(0, value.indexOf('${kms.decrypt(')); 223 | var encryptedStr = value.substr(value.indexOf('${kms.decrypt(') + '${kms.decrypt('.length); 224 | var after = encryptedStr.substr(encryptedStr.indexOf(')}') + ')}'.length); 225 | encryptedStr = encryptedStr.substr(0, encryptedStr.indexOf(')}')); 226 | var params = { 227 | CiphertextBlob: Buffer.from(encryptedStr, 'base64') 228 | }; 229 | kms.decrypt(params, function(err, data) { 230 | if (err) { 231 | next(err); 232 | } 233 | else { 234 | obj[key] = before+data.Plaintext+after; 235 | next(); 236 | } 237 | }); 238 | } else if (!_.isNil(value) && _.isObject(value)) { 239 | functions.resolve_stack(value, stack, input, next); 240 | } else { 241 | next(); 242 | } 243 | }, done); 244 | }; 245 | 246 | functions.resolve_stacks = function(obj, input, done) { 247 | async.eachOf(obj, function(value, key, next) { 248 | if (_.isObject(value) && !_.isNil(value.stack)) { 249 | functions.resolve_stack(value, value, input, next); 250 | } else if (_.isObject(value)) { 251 | functions.resolve_stacks(value, input, next); 252 | } else { 253 | next(); 254 | } 255 | }, done); 256 | }; 257 | 258 | functions.resolve_params = function(input, done) { 259 | init(input); 260 | functions.resolve_stacks(input.stacks, input, function(err) { 261 | done(err, input.stacks); 262 | }); 263 | }; 264 | 265 | functions.merge_stage = function(input, done) { 266 | if (input.params.stage) { 267 | try { 268 | traverse(input.stacks).forEach(function (obj) { 269 | if (!_.isNil(obj.stack)) { 270 | this.after(function () { 271 | if (!_.isNil(obj.stage) && !_.isNil(obj.stage[input.params.stage]) && !_.isEmpty(obj.stage[input.params.stage])) { 272 | _.merge(obj, obj.stage[input.params.stage]); 273 | delete obj.stage; 274 | } 275 | }); 276 | } 277 | }); 278 | done(null, input); 279 | } catch (err) { 280 | return done(err, input); 281 | } 282 | } else { 283 | done(null, input); 284 | } 285 | }; 286 | 287 | functions.validate_circular_stacks = function(stacks, done) { 288 | var err = null; 289 | traverse(stacks).forEach(function (obj) { 290 | if (this.circular) { 291 | err = 'Circular stacks'; 292 | return; 293 | } 294 | }); 295 | return done(err, stacks); 296 | }; 297 | 298 | functions.load = function(cim, done) { 299 | async.waterfall([ 300 | async.constant(cim), 301 | functions.find, 302 | functions.parse, 303 | functions.validate, 304 | functions.validate_circular_stacks, 305 | functions.load_missing_parents, 306 | function(stacks, next) { 307 | next(null, { stacks: stacks, params: cim.args}); 308 | }, 309 | functions.merge_stage, 310 | functions.resolve_params, 311 | ], function(err, stacks) { 312 | cim.stacks = stacks; 313 | done(err, stacks); 314 | }); 315 | }; 316 | 317 | module.exports = functions; -------------------------------------------------------------------------------- /lib/util/load-3rd-party-plugins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const async = require('async'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const exec = require('child_process').exec; 8 | 9 | const cache_service = require('./cache'); 10 | 11 | var functions = {}; 12 | 13 | functions.npm_root_global = function(cim, done) { 14 | async.waterfall([ 15 | cache_service.get_cache, 16 | function(cache, next) { 17 | if (cache && cache.npm_root_global) { 18 | cim.npm_root_global = cache.npm_root_global; 19 | next(null, cim, cache); 20 | } else { 21 | exec('npm root -g', {}, function(err, stdout, stderr) { 22 | if (stdout) { 23 | cim.npm_root_global = _.trim(stdout); 24 | } 25 | next(err, cim, cache); 26 | }); 27 | } 28 | }, 29 | function(cim, cache, next) { 30 | if (!cache) { 31 | cache_service.store_cache({npm_root_global: cim.npm_root_global}, next); 32 | } else if (cache && !cache.npm_root_global) { 33 | cache.npm_root_global = cim.npm_root_global; 34 | cache_service.store_cache(cache, next); 35 | } else { 36 | next(); 37 | } 38 | } 39 | ], function(err) { 40 | done(err, cim); 41 | }); 42 | }; 43 | 44 | functions.search = function(cim, done) { 45 | fs.readdir(cim.npm_root_global, function(err, files) { 46 | if (err) return done(err, cim); 47 | var plugins = []; 48 | _.forEach(files, function(file) { 49 | if (_.startsWith(file, 'cim_') || _.startsWith(file, 'cim-')) { 50 | plugins.push(file); 51 | } 52 | }); 53 | cim.third_party_plugins = plugins; 54 | done(null, cim); 55 | }); 56 | }; 57 | 58 | functions.load = function(cim, done) { 59 | _.forEach(cim.third_party_plugins, function(plugin) { 60 | cim.plugins.push(new (require(path.resolve(cim.npm_root_global, plugin)))()); 61 | }); 62 | done(); 63 | }; 64 | 65 | module.exports = function(cim, done) { 66 | async.waterfall([ 67 | async.constant(cim), 68 | functions.npm_root_global, 69 | functions.search, 70 | functions.load 71 | ], done); 72 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cim", 3 | "version": "0.4.0", 4 | "description": "Cloud Infrastructure Manager", 5 | "main": "lib/cim.js", 6 | "bin": { 7 | "cim": "./bin/cim" 8 | }, 9 | "dependencies": { 10 | "async": "^2.1.4", 11 | "aws-sdk": "^2.7.7", 12 | "fs-extra": "^4.0.2", 13 | "glob": "^7.1.2", 14 | "js-yaml": "^3.9.1", 15 | "lodash": "^4.17.2", 16 | "moment": "^2.19.2", 17 | "traverse": "^0.6.6", 18 | "yargs": "^10.0.3" 19 | }, 20 | "scripts": { 21 | "test": "mocha --recursive" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://rgfindl@github.com/thestackshack/cim.git" 26 | }, 27 | "keywords": [ 28 | "CloudFormation", 29 | "Lambda", 30 | "Serverless", 31 | "Infrastructure", 32 | "as", 33 | "Code", 34 | "Stack", 35 | "Microservices", 36 | "ECS", 37 | "EC2", 38 | "Services" 39 | ], 40 | "author": "rgfindley@gmail.com", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/thestackshack/cim/issues" 44 | }, 45 | "homepage": "https://github.com/thestackshack/cim#readme", 46 | "devDependencies": { 47 | "mocha": "^3.5.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/plugins/Plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const _ = require('lodash'); 5 | 6 | const Plugin = require('../../lib/plugins/Plugin'); 7 | 8 | describe('Plugin', function() { 9 | it('commands', function (done) { 10 | let plugin = new Plugin(); 11 | console.log(JSON.stringify(plugin.commands(), null, 3)); 12 | done(); 13 | }); 14 | it('hooks', function (done) { 15 | let plugin = new Plugin(); 16 | console.log(JSON.stringify(plugin.hooks(), null, 3)); 17 | plugin.hooks()['before:templates'][0]('', done); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/plugins/aws/CloudFormation/CloudFormation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const _ = require('lodash'); 5 | 6 | const CloudFormation = require('../../../../lib/plugins/aws/CloudFormation/CloudFormation'); 7 | 8 | describe('CloudFormation', function() { 9 | it('commands', function (done) { 10 | let cloudFormation = new CloudFormation(); 11 | console.log(JSON.stringify(cloudFormation.commands(), null, 3)); 12 | done(); 13 | }); 14 | }); -------------------------------------------------------------------------------- /test/resources/3rd-party-plugin/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // Functions 6 | const test_fn = require('./lib/test'); 7 | 8 | class TestPlugin { 9 | constructor() { 10 | Object.assign( 11 | this, 12 | test_fn); 13 | } 14 | 15 | /** 16 | * Get all the commands available for this plugin. 17 | * 18 | * @returns {Array} 19 | */ 20 | commands() { 21 | return [ 22 | { 23 | command: 'test', 24 | description: 'Test', 25 | params: {}, 26 | run: this.test 27 | } 28 | ] 29 | } 30 | 31 | before_test(cim, done) { 32 | // Example hook. 33 | // Do something before the 'templates' command is executed. 34 | console.log('before_test'); 35 | done(); 36 | } 37 | 38 | after_test(cim, done) { 39 | // Example hook. 40 | // Do something after the 'templates' command is executed. 41 | console.log('after_test'); 42 | done(); 43 | } 44 | 45 | /** 46 | * Return all the before and after hooks this plugin exposes. 47 | * 48 | * @returns hooks 49 | */ 50 | hooks() { 51 | // Example hooks. 52 | return { 53 | 'before:templates': [ 54 | this.before_test 55 | ], 56 | 'after:templates': [ 57 | this.after_test 58 | ] 59 | }; 60 | } 61 | 62 | /** 63 | * Get the path to the template directory to copy during a 'create' command. 64 | * 65 | * @returns {null} 66 | */ 67 | template() { 68 | return { 69 | name: 'test', 70 | description: 'Test CloudFormation setup.', 71 | path: path.resolve(__dirname, 'template') 72 | } 73 | } 74 | 75 | } 76 | module.exports = TestPlugin; -------------------------------------------------------------------------------- /test/resources/3rd-party-plugin/lib/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | test(cim, done) { 7 | console.log('test'); 8 | done(); 9 | } 10 | }; -------------------------------------------------------------------------------- /test/resources/3rd-party-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3rd-party-plugin", 3 | "version": "1.0.0", 4 | "description": "3rd party plugin.", 5 | "main": "index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/resources/3rd-party-plugin/template/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: cim-stack # Note: Update this with your stack name 4 | template: 5 | file: cloudformation.yml 6 | # Stacks are uploaded here prior to deployment. The bucket is created if it doesn't exist. 7 | # The name can be the same for all your _cim.yml files. 8 | bucket: cim-stack-artifacts # Note: Update this with your bucket name. 9 | 10 | # 11 | # Reference parent stacks fo included shared information like stack name. 12 | # 13 | # parents: 14 | # vpc: '../vpc' 15 | 16 | # 17 | # Define stack input parameters. 18 | # 19 | # parameters: 20 | # VpcStackName: '${stack.parents.vpc.stack.name}' 21 | 22 | # 23 | # Define stack capabilities required. 24 | # 25 | # capabilities: 26 | # - 'CAPABILITY_IAM' 27 | 28 | # 29 | # Define global tags. 30 | # 31 | # tags: 32 | # app: 'cim-stack' 33 | -------------------------------------------------------------------------------- /test/resources/3rd-party-plugin/template/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | #Parameters: 7 | # VpcStackName: 8 | # Type: String 9 | # Description: The vpc stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Add your CloudFormation resources here... 18 | # 19 | 20 | # 21 | # S3 bucket 22 | # 23 | ResourcesS3Bucket: 24 | Type: 'AWS::S3::Bucket' 25 | Properties: 26 | AccessControl: 'Private' 27 | 28 | # 29 | # Outputs to be used by other CloudFormation templates if needed. 30 | # 31 | Outputs: 32 | ResourcesS3Bucket: 33 | Description: Resources S3 Bucket 34 | Value: !Ref ResourcesS3Bucket 35 | Export: 36 | Name: !Sub '${AWS::StackName}-ResourcesBucket' 37 | -------------------------------------------------------------------------------- /test/resources/base/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: 'serverless-demo' 4 | # prod: 5 | # name: 'serverless-demo-prod' 6 | # default: false 7 | # dev: 8 | # name: 'serverless-demo-dev' 9 | # default: true 10 | template: 11 | file: 'cloudformation.yml' 12 | bucket: 'serverless-demo-cf-templates' 13 | -------------------------------------------------------------------------------- /test/resources/base/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation resources 5 | # 6 | Resources: 7 | 8 | # 9 | # S3 bucket 10 | # 11 | S3Bucket: 12 | Type: 'AWS::S3::Bucket' 13 | Properties: 14 | BucketName: 15 | Fn::Join: 16 | - '' 17 | - - !Ref AWS::StackName 18 | - '-artifacts-bucket' 19 | AccessControl: 'Private' 20 | blah: do 21 | 22 | # 23 | # Outputs to be used by other CloudFormation templates if needed. 24 | # 25 | Outputs: 26 | S3Bucket: 27 | Description: Artifacts S3 Bucket 28 | Value: !Ref S3Bucket 29 | Export: 30 | Name: !Sub '${AWS::StackName}-ArtifactsBucket' -------------------------------------------------------------------------------- /test/resources/cim-test-plugin/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Functions 4 | const test_fn = require('./lib/test'); 5 | 6 | class TestPlugin { 7 | constructor() { 8 | Object.assign( 9 | this, 10 | test_fn); 11 | } 12 | 13 | /** 14 | * Get all the commands available for this plugin. 15 | * 16 | * @returns {Array} 17 | */ 18 | commands() { 19 | return [ 20 | { 21 | command: 'test', 22 | description: 'Test', 23 | params: {}, 24 | run: this.test 25 | } 26 | ] 27 | } 28 | 29 | before_test(cim, done) { 30 | // Example hook. 31 | // Do something before the 'templates' command is executed. 32 | console.log('before_test'); 33 | done(); 34 | } 35 | 36 | after_test(cim, done) { 37 | // Example hook. 38 | // Do something after the 'templates' command is executed. 39 | console.log('after_test'); 40 | done(); 41 | } 42 | 43 | hooks() { 44 | // Example hooks. 45 | return { 46 | 'before:test': [ 47 | this.before_test 48 | ], 49 | 'after:test': [ 50 | this.after_test 51 | ] 52 | }; 53 | } 54 | 55 | /** 56 | * Get the path to the template directory to copy during a 'create' command. 57 | * 58 | * @returns {null} 59 | */ 60 | template() { 61 | return null; 62 | } 63 | 64 | } 65 | module.exports = TestPlugin; -------------------------------------------------------------------------------- /test/resources/cim-test-plugin/lib/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | module.exports = { 5 | test(cim, done) { 6 | console.log('Hello Test'); 7 | done(); 8 | } 9 | }; -------------------------------------------------------------------------------- /test/resources/cim-test-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3rd-party-plugin", 3 | "version": "1.0.0", 4 | "description": "3rd party plugin.", 5 | "main": "index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/resources/missing_cf/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: 'serverless-demo' 4 | # prod: 5 | # name: 'serverless-demo-prod' 6 | # default: false 7 | # dev: 8 | # name: 'serverless-demo-dev' 9 | # default: true 10 | template: 11 | file: 'cloudformation.yml' 12 | bucket: 'serverless-demo-cf-templates' 13 | -------------------------------------------------------------------------------- /test/resources/missing_parent/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: 'serverless-demo' 4 | # prod: 5 | # name: 'serverless-demo-prod' 6 | # default: false 7 | # dev: 8 | # name: 'serverless-demo-dev' 9 | # default: true 10 | template: 11 | file: 'cloudformation.yml' 12 | bucket: 'serverless-demo-cf-templates' 13 | parents: 14 | - '../notfound' 15 | -------------------------------------------------------------------------------- /test/resources/missing_parent/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation resources 5 | # 6 | Resources: 7 | 8 | # 9 | # S3 bucket 10 | # 11 | S3Bucket: 12 | Type: 'AWS::S3::Bucket' 13 | Properties: 14 | BucketName: 15 | Fn::Join: 16 | - '' 17 | - - !Ref AWS::StackName 18 | - '-artifacts-bucket' 19 | AccessControl: 'Private' 20 | blah: do 21 | 22 | # 23 | # Outputs to be used by other CloudFormation templates if needed. 24 | # 25 | Outputs: 26 | S3Bucket: 27 | Description: Artifacts S3 Bucket 28 | Value: !Ref S3Bucket 29 | Export: 30 | Name: !Sub '${AWS::StackName}-ArtifactsBucket' -------------------------------------------------------------------------------- /test/resources/s3/_cim.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | stack: 3 | name: '${stack.parents.base.stack.name}-s3' 4 | template: 5 | file: 'cloudformation.yml' 6 | bucket: 'serverless-demo-cf-templates' 7 | parents: 8 | base: '../base' 9 | parameters: 10 | BaseStack: '${stack.parents.base.stack.name}' 11 | capabilities: 12 | - 'CAPABILITY_IAM' 13 | tags: 14 | app: 'serverless-demo' 15 | 16 | lambda: 17 | phases: 18 | pre_deploy: 19 | commands: 20 | # Install all npm packages including dev packages. 21 | - npm install 22 | 23 | # Run the tests 24 | # - npm test 25 | 26 | # Remove all the npm packages. 27 | - rm -Rf node_modules 28 | 29 | # Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages. 30 | - npm install --production 31 | 32 | # Zip the Lambda for upload to S3. 33 | - zip -r index.zip . 34 | deploy: 35 | functions: 36 | - 37 | function_name: ${stack.outputs.LambdaFunction} 38 | zip_file: index.zip 39 | post_deploy: 40 | commands: 41 | # Remove the zip file. 42 | - rm -Rf index.zip 43 | 44 | # Reinstall the dev npm packages. 45 | - npm install -------------------------------------------------------------------------------- /test/resources/s3/cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | # 4 | # CloudFormation input parameters 5 | # 6 | Parameters: 7 | BaseStack: 8 | Type: String 9 | Description: The base stack name, used to import output values from this stack. cross-stack resource sharing. 10 | 11 | # 12 | # CloudFormation resources 13 | # 14 | Resources: 15 | 16 | # 17 | # Role that our Lambda will assume to provide access to other AWS resources 18 | # 19 | IamRoleLambdaExecution: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | AssumeRolePolicyDocument: 23 | Version: '2012-10-17' 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: 28 | - lambda.amazonaws.com 29 | Action: 30 | - sts:AssumeRole 31 | Path: '/' 32 | 33 | # 34 | # Create a Policy and attach it to our Lambda Role. 35 | # 36 | IamPolicyLambdaExecution: 37 | Type: AWS::IAM::Policy 38 | Properties: 39 | PolicyName: IamPolicyLambdaExecution 40 | PolicyDocument: 41 | Version: '2012-10-17' 42 | Statement: 43 | - Effect: Allow 44 | Action: 45 | - logs:CreateLogGroup 46 | - logs:CreateLogStream 47 | - logs:PutLogEvents 48 | Resource: '*' 49 | - Effect: Allow 50 | Action: 51 | - s3:* 52 | Resource: '*' 53 | Roles: 54 | - Ref: IamRoleLambdaExecution 55 | 56 | # 57 | # Our Lambda function. 58 | # 59 | LambdaFunction: 60 | Type: AWS::Lambda::Function 61 | Properties: 62 | Handler: index.handler 63 | Timeout: 5 64 | Role: 65 | Fn::GetAtt: 66 | - IamRoleLambdaExecution 67 | - Arn 68 | Code: 69 | ZipFile: !Sub | 70 | 'use strict'; 71 | 72 | module.exports.handler = (event, context, callback) => { 73 | const response = { 74 | statusCode: 200, 75 | headers: { 76 | 'Access-Control-Allow-Origin': '*', // Required for CORS support to work 77 | }, 78 | body: JSON.stringify({ 79 | message: 'Lambda Stub', 80 | input: event, 81 | }), 82 | }; 83 | 84 | callback(null, response); 85 | }; 86 | Runtime: nodejs6.10 87 | 88 | # 89 | # S3 bucket 90 | # 91 | S3Bucket: 92 | Type: 'AWS::S3::Bucket' 93 | Properties: 94 | BucketName: 95 | Fn::Join: 96 | - '' 97 | - - !Ref AWS::StackName 98 | - '-s3' 99 | AccessControl: 'Private' 100 | NotificationConfiguration: 101 | LambdaConfigurations: 102 | - 103 | Function: !GetAtt LambdaFunction.Arn 104 | Event: 's3:ObjectCreated:*' 105 | Filter: 106 | S3Key: 107 | Rules: 108 | - 109 | Name: suffix 110 | Value: .jpg 111 | - 112 | Name: suffix 113 | Value: .jpeg 114 | 115 | # 116 | # S3 permission to invoke Lambda 117 | # 118 | PermissionForS3ToInvokeLambda: 119 | Type: 'AWS::Lambda::Permission' 120 | Properties: 121 | FunctionName: !Ref LambdaFunction 122 | Action: 'lambda:InvokeFunction' 123 | Principal: 's3.amazonaws.com' 124 | SourceAccount: !Ref AWS::AccountId 125 | 126 | # 127 | # Outputs to be used by other CloudFormation templates if needed. 128 | # 129 | Outputs: 130 | LambdaFunction: 131 | Description: Lambda Function 132 | Value: !Ref LambdaFunction 133 | Export: 134 | Name: !Sub '${AWS::StackName}-LambdaFunction' 135 | S3Bucket: 136 | Description: S3 Bucket 137 | Value: !Ref S3Bucket 138 | Export: 139 | Name: !Sub '${AWS::StackName}-S3Bucket' -------------------------------------------------------------------------------- /test/resources/s3/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | exports.handler = function(event, context) { 6 | winston.info(JSON.stringify(event)); 7 | context.succeed('Complete 2'); 8 | }; -------------------------------------------------------------------------------- /test/resources/s3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-demo-app.s3", 3 | "version": "1.0.0", 4 | "description": "Demo the CloudFormation templates for all the Lambda event sources.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "keywords": [ 10 | "CloudFormation", 11 | "Lambda", 12 | "AWS" 13 | ], 14 | "author": "rgfindley@gmail.com", 15 | "license": "ISC", 16 | "dependencies": { 17 | "winston": "^2.3.1" 18 | }, 19 | "devDependencies": { 20 | "aws-sdk": "^2.110.0", 21 | "mocha": "^3.5.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/util/cloudformation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const _ = require('lodash'); 5 | const async = require('async'); 6 | const cloudformation = require('../../lib/util/cloudformation'); 7 | 8 | describe('cloudformation', function() { 9 | it('is_ready', function(done) { 10 | var stack = { 11 | stack: { 12 | parents: { 13 | base: { 14 | _cim: { 15 | status: 'complete' 16 | } 17 | } 18 | } 19 | } 20 | }; 21 | assert.ok(cloudformation.is_ready(stack)); 22 | var stack = { 23 | stack: { 24 | parents: { 25 | base: { 26 | _cim: { 27 | status: 'failed' 28 | } 29 | } 30 | } 31 | } 32 | }; 33 | assert.ok(!cloudformation.is_ready(stack)); 34 | var stack = { 35 | stack: { 36 | parents: { 37 | base: { 38 | _cim: { 39 | } 40 | } 41 | } 42 | } 43 | }; 44 | assert.ok(!cloudformation.is_ready(stack)); 45 | var stack = { 46 | stack: { 47 | parents: { 48 | } 49 | } 50 | }; 51 | assert.ok(cloudformation.is_ready(stack)); 52 | done(); 53 | }); 54 | }); -------------------------------------------------------------------------------- /test/util/configs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const _ = require('lodash'); 5 | const async = require('async'); 6 | const configs = require('../../lib/util/configs'); 7 | const path = require('path'); 8 | 9 | describe('configs', function() { 10 | it('find', function(done) { 11 | configs.find( 12 | { 13 | args: { 14 | recursive: true, 15 | dir: __dirname+'/../resources/' 16 | } 17 | }, 18 | function(err, files) { 19 | if (err) return done(err); 20 | //console.log(JSON.stringify(files, null, 3)); 21 | assert.ok(_.includes(files, path.resolve(__dirname, '../resources/base/_cim.yml'))); 22 | assert.ok(_.size(files) >= 2); 23 | done(); 24 | }); 25 | }); 26 | it('parse', function(done) { 27 | configs.parse( 28 | [ 29 | __dirname+'/../resources/s3/_cim.yml' 30 | ], 31 | function(err, data) { 32 | if (err) return done(err); 33 | //console.log(JSON.stringify(data, null, 3)); 34 | assert.ok(data[0].version); 35 | done(); 36 | }); 37 | }); 38 | it('validate - stack', function(done) { 39 | configs.validate( 40 | [ 41 | {} 42 | ], 43 | function(err, data) { 44 | if (!err) return done('Should be invalid.'); 45 | //console.log(JSON.stringify(data, null, 3)); 46 | assert.ok(_.includes(err, 'Must have a \'stack\'')); 47 | done(); 48 | }); 49 | }); 50 | it('validate - name', function(done) { 51 | configs.validate( 52 | [ 53 | { 54 | stack: { 55 | 56 | } 57 | } 58 | ], 59 | function(err, data) { 60 | if (!err) return done('Should be invalid.'); 61 | //console.log(JSON.stringify(data, null, 3)); 62 | assert.ok(_.includes(err, 'Must have a \'stack.name\'')); 63 | done(); 64 | }); 65 | }); 66 | it('validate - template', function(done) { 67 | configs.validate( 68 | [ 69 | { 70 | stack: { 71 | name: 'test' 72 | } 73 | } 74 | ], 75 | function(err, data) { 76 | if (!err) return done('Should be invalid.'); 77 | //console.log(JSON.stringify(data, null, 3)); 78 | assert.ok(_.includes(err, 'Must have a \'stack.template\'')); 79 | done(); 80 | }); 81 | }); 82 | it('validate - template.file', function(done) { 83 | configs.validate( 84 | [ 85 | { 86 | stack: { 87 | name: 'test', 88 | template: { 89 | 90 | } 91 | } 92 | } 93 | ], 94 | function(err, data) { 95 | if (!err) return done('Should be invalid.'); 96 | //console.log(JSON.stringify(data, null, 3)); 97 | assert.ok(_.includes(err, 'Must have a \'stack.template.file\'')); 98 | done(); 99 | }); 100 | }); 101 | it('validate - template.bucket', function(done) { 102 | configs.validate( 103 | [ 104 | { 105 | stack: { 106 | name: 'test', 107 | template: { 108 | file: 'cf.yml' 109 | } 110 | }, 111 | _cim: { 112 | file: 'cf.yml' 113 | } 114 | } 115 | ], 116 | function(err, data) { 117 | if (!err) return done('Should be invalid.'); 118 | //console.log(JSON.stringify(data, null, 3)); 119 | assert.ok(_.includes(err, 'Must have a \'stack.template.bucket\'')); 120 | done(); 121 | }); 122 | }); 123 | it('validate - missing template', function(done) { 124 | configs.validate( 125 | [ 126 | { 127 | stack: { 128 | name: 'test', 129 | template: { 130 | file: 'notfound.yml', 131 | bucket: 'bucket' 132 | } 133 | }, 134 | _cim: { 135 | file: 'cf.yml' 136 | } 137 | } 138 | ], 139 | function(err, data) { 140 | if (!err) return done('Should be invalid'); 141 | //console.log(JSON.stringify(data, null, 3)); 142 | assert.ok(_.includes(err, 'Missing CloudFormation template')); 143 | done(); 144 | }); 145 | }); 146 | it('validate - missing template file', function(done) { 147 | async.waterfall([ 148 | async.constant( 149 | { 150 | args: { 151 | dir: __dirname + '/../resources/missing_cf' 152 | } 153 | }), 154 | configs.find, 155 | configs.parse, 156 | configs.validate, 157 | ], function(err, stacks) { 158 | if (!err) return done('Should be invalid.'); 159 | assert.ok(_.includes(err, 'Missing CloudFormation template')); 160 | done(); 161 | }); 162 | }); 163 | it('validate - missing parent file', function(done) { 164 | async.waterfall([ 165 | async.constant( 166 | { 167 | args: { 168 | dir: __dirname+'/../resources/missing_parent' 169 | } 170 | }), 171 | configs.find, 172 | configs.parse, 173 | configs.validate, 174 | ], function(err, stacks) { 175 | if (!err) return done('Should be invalid.'); 176 | assert.ok(_.includes(err, 'Missing parent')); 177 | done(); 178 | }); 179 | }); 180 | it('validate - valid', function(done) { 181 | async.waterfall([ 182 | async.constant( 183 | { 184 | args: { 185 | dir: __dirname + '/../resources/s3' 186 | } 187 | }), 188 | configs.find, 189 | configs.parse, 190 | configs.validate, 191 | ], function(err, stacks) { 192 | if (err) return done(err); 193 | done(); 194 | }); 195 | }); 196 | it('resolve_params', function(done) { 197 | var stacks = {stacks: [ 198 | { 199 | stack: 200 | { 201 | foo: 'bar', 202 | param: '${stack.foo}' 203 | } 204 | } 205 | ]}; 206 | configs.resolve_params(stacks, function(err, stacks) { 207 | if (err) return done(err); 208 | //console.log(JSON.stringify(stacks, null, 3)); 209 | assert.equal(stacks[0].stack.param, 'bar'); 210 | done(); 211 | }); 212 | }); 213 | it('resolve_params - env', function(done) { 214 | var stacks = {stacks: [ 215 | { 216 | stack: 217 | { 218 | foo: 'bar', 219 | param: '${stack.foo}', 220 | env: '${env.test}' 221 | } 222 | } 223 | ]}; 224 | process.env.test = 'yes'; 225 | configs.resolve_params(stacks, function(err, stacks) { 226 | if (err) return done(err); 227 | //console.log(JSON.stringify(stacks, null, 3)); 228 | assert.equal(stacks[0].stack.param, 'bar'); 229 | assert.equal(stacks[0].stack.env, 'yes'); 230 | done(); 231 | }); 232 | }); 233 | it('resolve_params - missing', function(done) { 234 | var stacks = [ 235 | { 236 | stack: 237 | { 238 | foo: 'bar', 239 | param: '${stack.missing}' 240 | } 241 | } 242 | ]; 243 | configs.resolve_params(stacks, function(err, stacks) { 244 | if (!err) return done(err); 245 | done(); 246 | }); 247 | }); 248 | it('validate_circular_stacks - valid', function(done) { 249 | var stacks = [ 250 | { 251 | stack: 252 | { 253 | foo: 'bar', 254 | param: '${stack.foo}' 255 | } 256 | } 257 | ]; 258 | configs.validate_circular_stacks(stacks, function(err, stacks) { 259 | if (err) return done(err); 260 | done(); 261 | }); 262 | }); 263 | it('validate_circular_stacks - invalid', function(done) { 264 | var stacks = [ 265 | { 266 | stack: 267 | { 268 | foo: 'bar', 269 | param: '${stack.foo}' 270 | } 271 | } 272 | ]; 273 | var obj = { 274 | stacks: stacks 275 | }; 276 | stacks.obj = obj; 277 | configs.validate_circular_stacks(stacks, function(err, stacks) { 278 | if (!err) return done(err); 279 | done(); 280 | }); 281 | }); 282 | }); --------------------------------------------------------------------------------