safe-cfn-custom-resource

The safest implementation for CloudFormation custom resources in NodeJS.

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
safe-cfn-custom-resource
611.0.34 years ago5 years agoMinified + gzip package size for safe-cfn-custom-resource in KB

Readme

safe-cfn-custom-resource
CloudFormation is awesome! Custom resources are awesome! But... \ Tired of having your CloudFormation stack for 1 whole hour because of a bug during development? Want to use async/await?
This package lets you have the safest implementation of your custom resource, if you use it properly your stack will never get stuck.
Special thanks to zippadd/cfn-custom-resource, this is basically a wrapper around it.

Install

npm install --save safe-cfn-custom-resource

Requirements

  • Lambda Runtime: NodeJS 8.10+ (the handler will return a promise)
  • Lambda Timeout: 4+ seconds (the function will timeout 3 seconds before Lambda's timeout)

Note: 4 seconds timeout means you have less than 1 second to finish your work, you should probably set it to more.

Usage

Your main JavaScript module should look like this:
// Yes, even your initialization can be async!
module.exports.handler = require('safe-cfn-custom-resource')(/*async*/ () => {
  // Don't do ANYTHING outside this function!
  // All your requires go here.

  // event & context are the original ones the handler was invoked with.
  return {
    /*async*/ create(event, context) {
      return {
        // required
        id: "the physical resource id (Ref)",
        // optional
        // can also supply a non-object which would be retrieved using !GetAtt: [ResourceLogicalId, Data]
        data: { Key: "value", For: "GetAtt" }
      };
    },
    /*async*/ update(event, context) {
      return {
        // optional, original physical resource id will be used if not supplied
        id: "the physical resource id (Ref)",
        // optional
        // can also supply a non-object which would be retrieved using !GetAtt: [ResourceLogicalId, Data]
        data: { Key: "value", For: "GetAtt" }
      };
    },
    /*async*/ delete(event, context) {
      // doesn't need to return anything
    },
  };
});


  • Tip 1: Remember that if the update requires re-creation, return the new physical resource id and don't delete the old one,
CloudFormation will automatically call this function again for deletion of the old physical resource id.
  • Tip 2: Use Webpack to save some artifact size when using dependencies (like this one).

Debugging

Setting an environment variable called DEBUG on your Lambda function will give you debug logging in CloudWatch. Note that your lambda needs to be able to access the function's CloudWatch log stream.

Unit testing your resource

In order to unit test your implementation there are 3 options:
  1. You can write your logic in a separate resource.js file,
and `require` & `return` it in the `index.js` that is used as the Lambda's handler:

```javascript
# resource.js
module.exports = {
  /*async*/ create(event, context) {
    /* see above */
  }
  /*async*/ update(event, context) {
    /* see above */
  }
  /*async*/ delete(event, context) {
    /* see above */
  }
};

# index.js
module.exports.handler = require('safe-cfn-custom-resource')(/*async*/ () => {
  // Require it INSIDE the safety callback!
  return require('./resource');
});
```

And now you can `require` your implementation separetely.
  1. If you are a minimalist there is a second option. safe-cfn-custom-resource
exposes whatever you return inside the callback via a `resource` attribute:

```javascript
const { handler } = require('./index');
let event = { /* CloudFormation event */ };
let context = { /* Lambda context */ };
handler.create(event, context);
handler.update(event, context);
handler.delete(event, context);
```

Feel free to return additional internal functions you want to test.
  1. You can avoid testing internal functions (e.g your create/update/delete),
and only test the exported handler:

```javascript
const { handler } = require('./index');
let context = { /* Lambda context */ };
handler({ /* CloudFormation Create event */ }, context);
handler({ /* CloudFormation Update event */ }, context);
handler({ /* CloudFormation Delete event */ }, context);
```

License

MIT