/cloudformation-deploy

Node.js deployment to AWS via CloudFormation template.

Primary LanguageJavaScriptMIT LicenseMIT

CloudFormation Deploy

This package provides a simple interface for the deployment of a CloudFormation stack. It implements the following deployment pattern, avoiding the use of stack updates in favor of stack creation and deletion:

  • Request stack creation.
  • Wait on the stack status to show show success.
  • Update existing resources to point to the new stack.
  • Delete any previous instances of the stack once the switchover is done.

This is well suited to deploying webserver stacks that include an Auto Scaling Group and Elastic Load Balancer. The update step between successful creation of a new stack and starting the deletion of an old stack might consist of updating a Route 53 CNAME record to point to the new stack and then allowing time for propagation.

Installation

Obtain CloudFormation Deploy via NPM:

npm install cloudformation-deploy

Examples

See the examples directory for ready to run examples.

Deploying a CloudFormation Stack

1) Configure AWS Credentials

For deployment to work, suitable credentials for the destination AWS account must be present. The credentials must at a minimum allow interaction with CloudFormation stacks and granting all suitable permissions to stack resources via IAM roles.

To make credentials available, either create a credentials file in the standard location, set environment variables to hold the key, secret key and region, or run on an EC2 instance with an IAM role that has suitable permissions. These options are described in the AWS SDK documentation.

For example, if using environment variables:

export AWS_ACCESS_KEY_ID=<key>
export AWS_SECRET_ACCESS_KEY=<secret key>
export AWS_REGION=us-east-1

Alternatively, CloudFormation Deploy accepts an optional configuration object that is passed to the AWS SDK CloudFormation client instance if present. See the configuration details below. This is not recommended: it is bad practice to specify access keys in code or configuration for code. You should always add them to the environment in one of the ways noted above.

2) Create the CloudFormation Template

Generate or load the CloudFormation template. This module can accept the template as either a JSON string, an object, or a URL to a template uploaded to S3. Use the latter method for larger templates, as it has a larger maximum size limit.

3) Run the Deployment

Run the following code.

cloudFormationDeploy = require('cloudformation-deploy');

// Pull in the CloudFormation template from a JSON file or object.
//var template = fs.readFileSync('example.json', { encoding: 'utf8' });
//var template = { ... };
// Or specify a URL.
var template = 'http://s3.amazonaws.com/bucket/example.json';

var config = {
  // --------------------
  // Required properties.
  // --------------------

  // The baseName and deployId are combined to form a unique name.
  //
  // Tags are automatically added based on these values and the version, and are
  // used when deleting old stacks from earlier versions and prior deployments.
  //
  // The deployId property is along the lines of BUILD_NUMBER in Jenkins or
  // a similar values generated by another task framework. Using a Unix
  // timestamp is an acceptable fallback if nothing else is available.
  baseName: 'example-stack',
  deployId: '15',
  version: '0.1.0',

  // --------------------
  // Optional properties.
  // --------------------

  // If defined, this property is passed to the AWS SDK client. It is not
  // recommended to use this approach, see above for comments on configuring
  // AWS access via the environment.
  // clientOptions : {
  //   accessKeyId: 'akid',
  //   secretAccessKey: 'secret',
  //   region: 'us-east-1'
  // },

  // Timeout in minutes for the process of stack creation.
  createStackTimeoutInMinutes: 10,

  // Specify additional tags to apply to the stack.
  tags: {
    name: 'value'
  },

  // Pass in any parameters required by the template.
  parameters: {
    name: 'value'
  },

  // Seconds to wait between each check on the progress of stack creation or
  // deletion.
  progressCheckIntervalInSeconds: 10,

  // A function invoked whenever a CloudFormation event is created during
  // stack creation or deletion.
  onEventFn: function (event) {
    console.log(event);
  },

  // A function invoked after the CloudFormation stack is successfully created
  // but before any prior stack is deleted. This allows for a clean switchover
  // of resources to use the new stack.
  //
  // The provided stackDescription object is the standard output from the
  // describeStacks API for the created stack.
  postCreationFn: function (stackDescription, callback) {

    // Take action here, such as updating a Route 53 DNS entry and waiting for
    // propagation to complete.

    callback();
  },

  // What to do with prior instances of this stack, which is defined as any
  // stack deployed with the same baseName.
  priorInstance: cloudFormationDeploy.priorInstance.DELETE,

  // What to do with this deployed stack should it fail to complete
  // successfully.
  onFailure: cloudFormationDeploy.onFailure.DELETE
};

cloudFormationDeploy.deploy(config, template, function (error, results) {
  if (error) {
    console.error(error);
  }

  // Whether or not there is an error, the results object is returned. It will
  // usually have additional useful information on why the stack deployment
  // failed. On success it will include the stack description, outputs
  // defined in the CloudFormation template, and events.
  console.log(results);
});

Deployment Configuration

The config object passed to cloudFormationDeploy.deploy supports the following required and optional properties.

Required Properties

baseName - string - Combined with deployId to generat the stack name.

version - string - The version of the application deployed to the stack.

deployId - string - A distinguishing string for this stack baseName, such as a build number, to ensure this stack name is unique even for multiple deployments of the same version.

Optional Properties

createStackTimeoutInMinutes - number - A timeout in minutes for stack creation or deletion. Defaults to 10.

tags - object - The tags to apply to the stack in addition to those created automatically based on the baseName. Tag values must be strings.

parameters - object - Values to apply to the parameters in the CloudFormation template. Parameter values must be strings.

progressCheckIntervalInSeconds - number - Number of seconds to wait between each check on the progress of stack creation or deletion. Defaults to 10.

onEventFn - function - A function invoked whenever a new event is created during stack creation or deletion.

function (event) {
  console.log(event);
}

postCreationFunction - function - A function invoked after a stack is successfully created but before any prior instance of the stack with the same baseName is destroyed. This allows resources to be updated to use the new stack.

function (stackDescription, callback) {

  // Take action here, such as updating a Route 53 DNS entry and waiting for
  // propagation to complete.

  callback();
}

priorInstance - string - One of the allowed values describing what to do with previously deployed stacks with the same baseName. Defaults to cloudFormationDeploy.priorInstance.DELETE and the allowed values are:

  • cloudFormationDeploy.priorInstance.DELETE
  • cloudFormationDeploy.priorInstance.DO_NOTHING

onFailure - string - One of the allowed values describing what to do with the deployed stack on failure. Defaults to cloudFormationDeploy.onFailure.DELETE and the allowed values are:

  • cloudFormationDeploy.onFailure.DELETE
  • cloudFormationDeploy.onFailure.DO_NOTHING

Failure Cases

All failure cases will result in cloudFormationDeploy.deploy calling back with an error. The state of the stacks depends on where in the process that error occurred, however.

  1. If the stack creation fails, then:
  • The postCreationFn function is not invoked.
  • The failed stack is deleted if priorInstance is cloudFormationDeploy.onFailure.DELETE.
  • Any prior stacks are left untouched.
  1. If the stack creation succeeds, but subsequent calls to obtain the stack details fail, then:
  • The postCreationFn function is not invoked.
  • The newly created stack is not deleted.
  • Any prior stacks are left untouched.
  1. If the postCreationFn calls back with an error due to failure, then:
  • The newly created stack is not deleted.
  • Any prior stacks are left untouched.
  1. If the deletion of prior stacks fails, then:
  • The newly created stack is not deleted.
  • Any prior stacks are left in whatever state the deletion failure leaves them in.