CIM takes the pain out of Infrastructure as Code and CloudFormation!
CIM is a simple command line utility that bootstraps your CloudFormation CRUD operations, making them easier to execute, repeatable, and less error-prone. CIM separates out the stack template (YAML file) from the stack configuration (CLI options) so both can be stored safely in your project and executed again-and-again for stack updates.Â
CIM is not a CloudFormation abstraction.
So what’s the problem? Why did you build CIM? The problem I was having with the AWS CloudFormation cli was remembering the exact cli options used in previous executions. Plus I wanted support for things like nested stacks, variable resolution, environments, encryption, Lambda deployments, etc…
- CIM makes it easy to create, update, and delete stacks
- CIM allows you to create nested stacks
- CIM helps organize stack input parameters
- CIM provides templates to help you get started
- CIM has support for Lambda functions
- CIM has an extensible Plugin framework
Easily create your stack, build and deploy your code, and view your logs.
Table of contents:
# Install CIM
npm install cim -g
# See the available templates
cim templates
# Create your first stack using the lambda template
mkdir app
cd app
cim create --template=lambda-node
# Deploy your stack
cim stack-up
# Deploy your code
cim lambda-deploy
# View logs
cim lambda-logs --function=<function-name> --tail=true
# Delete you stack
cim stack-delete
Use npm to install CIM globally.
npm install cim -g
- create
- templates
- stack-up
- stack-show
- stack-delete
- lambda-deploy
- lambda-publish
- lambda-versions
- lambda-prune
- lambda-logs
- help
Create a new CIM package based on a give template.
At minimum a CIM package contains the following files:
mkdir app
cd app
cim create --template=<template>
Where <template>
is equal to one of the templates from cim templates
.
For examples see the templates.
--template
: The name of a template.- ex. --template=lambda-node
--dir
: (optional) The directory where you wish the new package to be.- ex. --dir=/app
View all the available templates. Templates are used in the create
command.
cim templates
Create or update your stack. Sends a create or update command to CloudFormation using the properties defined in your _cim.yml.
cim stack-up {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--recursive
: (optional) Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is 'false'.- ex. --recursive=true
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Show all the details about your CloudFormation stack. Helper method to see the status of your stack.
cim stack-show {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--recursive
: (optional) Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is 'false'.- ex. --recursive=true
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Delete your stack. Sends a delete command to CloudFormation using the properties defined in your _cim.yml.
cim stack-delete {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--recursive
: (optional) Recursively search for nested stacks to create or update. Any nested directory with a valid _cim.yml file. Default is 'false'.- ex. --recursive=true
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Deploy your Lambda functions.
If alias
and lambda-version
are used then alias
is simply updated to point to the specified lambda-version
. Read more about Versions and Aliases here.
If alias
and lambda-version
are omitted then a new version of the code is uploaded and the '$LATEST' alias is updated to point to this new version.
cim lambda-deploy {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--recursive
: (optional) Recursively search for nested stacks to deploy. Any nested directory with a valid _cim.yml file. Default is 'false'.- ex. --recursive=true
--function
: (optional) Restrict to a single Lambda function by its name.- ex. --function=function1
--alias
: (optional) Deploys thelambda-version
to thisalias
below.- ex. --alias=PROD
--lambda-version
: (optional) Deploys thislambda-version
to thealias
above.- ex. --lambda-version=2
--prune
: (optional) Deletes all unused versions. Defaults to 'false'.- ex. --prune=true
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
AWS recommends using Lambda Versions and Aliases in production. This is not required though
Here is an example CloudFormation template that uses a Lambda version and alias. The alias is then used by the S3 trigger.
#
# Our Lambda function.
#
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Timeout: 5
Role:
Fn::GetAtt:
- IamRoleLambdaExecution
- Arn
Code:
ZipFile: !Sub |
'use strict';
exports.handler = function(event, context) {
console.log(JSON.stringify(event));
context.succeed('Hello CIM!');
};
Runtime: nodejs6.10
#
# Version 1 of our function.
#
LambdaFunctionVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref LambdaFunction
#
# 'PROD' Alias -> Version 1
#
LambdaFunctionAlias:
Type: AWS::Lambda::Alias
Properties:
FunctionName: !Ref LambdaFunction
FunctionVersion: !GetAtt LambdaFunctionVersion.Version
Name: 'PROD'
#
# S3 bucket
#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Join:
- ''
- - !Ref AWS::StackName
- '-s3'
AccessControl: 'Private'
NotificationConfiguration:
LambdaConfigurations:
-
Function: !Ref LambdaFunctionAlias
Event: 's3:ObjectCreated:*'
Filter:
S3Key:
Rules:
-
Name: suffix
Value: .jpg
Here is the _cim.yml lambda
section that uses the Alias:
lambda:
functions:
-
function: ${stack.outputs.LambdaFunction}
aliases:
PROD: ${stack.outputs.LambdaFunctionAlias}
zip_file: index.zip
deploy:
phases:
pre_deploy:
commands:
# Install all npm packages including dev packages.
- npm install
# Run the tests
# - npm test
# Remove all the npm packages.
- rm -Rf node_modules
# Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages.
- npm install --production
# Zip the Lambda for upload to S3.
- zip -r index.zip .
post_deploy:
commands:
# Remove the zip file.
- rm -Rf index.zip
# Reinstall the dev npm packages.
- npm install
We can now deploy additional versions to our Lambda without affecting the PROD
alias. Once we have tested our code and are ready to go live we simply update the PROD
alias to point to the new version.
Don't forget to prune
your unused versions so you don't run out of space.
If you decide to use versions and aliases your deployment becomes two steps.
Deployment with versions and aliases:
cim lambda-publish
cim lambda-deploy --alias=<alias> --lambda-version=<version>
Deployment without versions and aliases:
cim lambda-deploy
Publish a new lambda-version
of this function.
cim lambda-publish {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--function
: (optional) Restrict to a single Lambda function by its name.- ex. --function=function1
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Show all the lambda function versions and associated aliases.
cim lambda-versions {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--function
: (optional) Restrict to a single Lambda function by its name.- ex. --function=function1
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Delete one or more unused version
's of this function.
cim lambda-prune {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--function
: (optional) Restrict to a single Lambda function by its name.- ex. --function=function1
--lambda-version
: (required) Deletes thislambda-version
. Set to 'all' to delete all unused versions.- ex. --lambda-version=2 or --lambda-version=all
--stage
: (optional) Create or update the stack(s) using the give stage.- ex. --stage=prod
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
Show the CloudWatch Logs for a single Lambda function.
cim lambda-logs {OPTIONS}
--dir
: (optional) The directory to run this command in. Defaults to the current directory.- ex. --dir=/app
--function
: Restrict to a single Lambda function by its name.- ex. --function=function1
--tail
: (optional) Tail the logs. Default is false.- ex. --tail=true
--startTime
: (optional) Start fetching logs from this time. Time in minutes. Ex. '30s' minutes ago. Default is '30s'.- ex. --startTime=30s
--internal
: (optional) Interval, in milliseconds, between calls to CloudWatch Logs when using 'tail'. Default is 5000.- ex. --internal=5000
--filterPattern
: (optional) CloudWatch Logs filter pattern.- ex. --filterPattern=ERROR
--profile
: (optional) Your AWS credentials profile.- ex. --profile=prod_aws_account
View all the available commands.
cim help
View a single command.
cim <command> --help
The _cim.yml file is where details about your stack are stored. Lets take a look at base template.
version: 0.1
stack:
name: 'base'
template:
file: 'cloudformation.yml'
bucket: 'base-templates'
The stack name
will be used when creating your CloudFormation stack. Shen you view all your CloudFormation stacks through the AWS console, this will be the name.
A lot of the AWS resources created within this stack will also have this name as a prefix within their name.
The template
defines the local CloudFormation file
to use for this stack. When calling CloudFormation the CloudFormation file needs to be on S3. The S3 bucket
is where CIM stores the CloudFormation file prior to calling CloudFormation.
In most cases you will have multiple stacks, and these stacks will be nested. For example, lets say you have a base
stack for your VPC and other shared resources, and then a stack for your api application. Your project structure might look like:
/iac
/base
- _cim.yml
- cloudformation.yml
/api-app
- _cim.yml
- cloudformation.yml
Through the use of the parents
field you can reference and reuse items in a parent stack. In our api app example we reuse the parent stack name and template bucket.
version: 0.1
stack:
name: '${stack.parents.base.stack.name}-api'
template:
file: 'cloudformation.yml'
bucket: '${stack.parents.base.stack.template.bucket}'
parents:
base: '../base'
You can reference multiple parents
by key.
The parameters
field is used to define the input parameter values sent to CloudFormation during a stack-up command.
Continuing our example above lets say we also want to pass in the base stack name as an input parameter for cross stack parameter referencing.
version: 0.1
stack:
name: '${stack.parents.base.stack.name}-api'
template:
file: 'cloudformation.yml'
bucket: '${stack.parents.base.stack.template.bucket}'
parents:
base: '../base'
parameters:
BaseStackName: '${stack.parents.base.stack.name}'
If you have IAM resources, you can specify either capability. If you have IAM resources with custom names, you must specify CAPABILITY_NAMED_IAM. If you don't specify this parameter, this action returns an InsufficientCapabilities error.
Valid Values: CAPABILITY_IAM | CAPABILITY_NAMED_IAM Continuing our example above lets say we also want to pass in the base stack name as an input parameter for cross stack parameter referencing.
version: 0.1
stack:
name: '${stack.parents.base.stack.name}-api'
template:
file: 'cloudformation.yml'
bucket: '${stack.parents.base.stack.template.bucket}'
parents:
base: '../base'
parameters:
BaseStackName: '${stack.parents.base.stack.name}'
capabilities:
- 'CAPABILITY_IAM'
Tags are used to not only tag your CloudFormation stack but to also tag all resources created by the stack given that those resources support tags.
version: 0.1
stack:
name: '${stack.parents.base.stack.name}-api'
template:
file: 'cloudformation.yml'
bucket: '${stack.parents.base.stack.template.bucket}'
parents:
base: '../base'
parameters:
BaseStackName: '${stack.parents.base.stack.name}'
capabilities:
- 'CAPABILITY_IAM'
tags:
app: 'api-app'
owner: 'John Doe'
CIM supports both the Policy
and PolicyDuringUpdate
CloudFormation params.
version: 0.1
stack:
name: 'test'
template:
file: 'cloudformation.yml'
bucket: 'test-bucket'
policy:
file: 'policy.json'
bucket: 'test-bucket'
policyDuringUpdate:
file: 'policyDuringUpdat'
bucket: 'test-bucket'
For more information about Policy
and PolicyDuringUpdate
see here: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html#protect-stack-resources-modifying
All AWS SDK CloudFormation createStack and updateStack params are supported. If you add them to your _cim.yml config file they will be used when creating and updating your stack.
- Capabilities
- RollbackConfiguration
- DisableRollback
- TimeoutInMinutes
- NotificationARNs
- ResourceTypes
- RoleARN
- OnFailure
- EnableTerminationProtection
The stage
object is used to override any part of the configuration file when that --stage
is used as a command line option. For example if we have the following dev stage:
version: 0.1
stack:
name: 'base-prod'
template:
file: 'cloudformation.yml'
bucket: 'base-templates'
parameters:
param1: 'prod-param'
stage:
dev:
stack:
name: 'base-dev'
parameters:
param1: 'dev-param'
Now when we use the --stage=dev
command line option, our stack name will be 'base-dev' and our param1 will be 'dev-param'. Any field can be overridden.
You can reference any cli option and use it in a variable.
For example, we use the profile
cli option in this command below: cim stack-up --profile=bluefin
version: 0.1
stack:
name: 'base-prod'
template:
file: 'cloudformation.yml'
bucket: 'base-templates'
parameters:
param1: '${opt.profile}'
You can reference any environment var in the config file and use it in a variable.
version: 0.1
stack:
name: 'base-prod'
template:
file: 'cloudformation.yml'
bucket: 'base-templates'
parameters:
param1: '${env.param1}'
You can include any kms encrypted string (base64 encoded) and CIM will decrypt prior to running the commend.
version: 0.1
stack:
name: 'base-prod'
template:
file: 'cloudformation.yml'
bucket: 'base-templates'
parameters:
param1: '${kms.decrypt(<kms encrypted and bas64 encoded string)}'
If your stack includes one or more lambda's you can add the lambda
section to your _cim.yml to enable Lambda support (lambda-deploy, lambda-logs).
In this example we have two Lambda functions. The function
will be an output param from our stack. The function
is used by the lambda-deploy and lambda-logs commands to specify a single function.
The deploy
section which is broken up into two parts is used by the lambda-deploy command.
pre_deploy
Install any dependencies, run the tests, and package the Lambda zip for deployment.post_deploy
Tear down any leftover artifacts from thepre_deploy
phase.
The Lambda zip that is packaged in the pre_deploy
phase must match the zip_file
under each function. When a function is deployed it uses the zip_file
as the deployment artifact.
lambda:
functions:
-
function: ${stack.outputs.LambdaFunction}
zip_file: index.zip
-
function: ${stack.outputs.LambdaFunctionSecond}
zip_file: index.zip
deploy:
phases:
pre_deploy:
commands:
# Install all npm packages including dev packages.
- npm install
# Run the tests
# - npm test
# Remove all the npm packages.
- rm -Rf node_modules
# Only install the non-dev npm packages. We don't want to bloat our Lambda with dev packages.
- npm install --production
# Zip the Lambda for upload to S3.
- zip -r index.zip .
post_deploy:
commands:
# Remove the zip file.
- rm -Rf index.zip
# Reinstall the dev npm packages.
- npm install
Lambda versions and aliases are also supported. If your cloudformation template includes an alias, you can include it in your _cim.yml
file like so:
lambda:
functions:
-
function: ${stack.outputs.LambdaFunction}
aliases:
PROD: ${stack.outputs.LambdaFunctionProdAlias}
zip_file: index.zip
Use the lambda-publish and lambda-unpublish commands to upload and delete new versions.
The lambda-deploy command can be used to deploy new versions, by updating the alias to point to the specified version.
The templates are used when creating a new package.
cim create --template=<template>
Name | Description |
---|---|
cloudformation | Basic setup. |
serverless-web-app | Static S3 website with SSL, CDN, and CI/CD. |
serverless-api | API Gateway proxying calls to a Lambda backend. Optional custom domain. |
lambda-node | A single Lambda function. |
lambda-node-s3 | A single Lambda function with an S3 event trigger. |
lambda-node-dynamodb | A single Lambda function with a DynamoDB stream event trigger. |
lambda-node-kinesis | A single Lambda function with a Kinesis stream event trigger. |
lambda-node-sns | A single Lambda function with an SNS event trigger. |
lambda-node-cloudwatch-cron | A single Lambda function with a scheduled CloudWatch cron event trigger. |
lambda-node-cloudwatch-logs | A single Lambda function with a CloudWatch Logs event trigger. |
vpc | VPC - Modular and scalable virtual networking foundation on the AWS Cloud. |
ecr | ECR - AWS Docker Container Registry. |
ecs | ECS - AWS EC2 Docker Container Service. |
ecs-service | Example ECS Service. |
ecs-service-ci | Example ECS Service with continuous integration. |
Do you want to create additional CIM commands? Or do you want to create before
and after
hooks for any CIM command? Or do you just want to create a new template?
There are two ways to contribute to CIM:
- Add a new Plugin and create a PR.
- Create your own 3rd party CIM plugin. Here is an example. Install these plugins globally. CIM searches the global npm directory for packages starting with
cim-
orcim_
.
- Add cloudformation change set. createChangeSet, executeChangeSet.
- Add multiple CloudFormation scripts per package? Maybe...
- Add genaric logs command to support all log groups, not just Lambda's.