https://github.com/odedniv/safe-cfn-custom-resource
The safest implementation for CloudFormation custom resources in NodeJS.
https://github.com/odedniv/safe-cfn-custom-resource
Last synced: 5 months ago
JSON representation
The safest implementation for CloudFormation custom resources in NodeJS.
- Host: GitHub
- URL: https://github.com/odedniv/safe-cfn-custom-resource
- Owner: odedniv
- License: mit
- Created: 2018-05-26T09:10:40.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2018-09-26T22:09:30.000Z (over 7 years ago)
- Last Synced: 2025-10-17T14:51:19.572Z (8 months ago)
- Language: JavaScript
- Size: 14.6 KB
- Stars: 6
- Watchers: 1
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
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](https://www.npmjs.com/package/cfn-custom-resource),
this is basically a wrapper around it.
## Install
```bash
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:
```javascript
// 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
},
};
});
```
* See the [official documentation for custom resource request event structure](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html).
* If you throw an error with an `id` attribute, it will be used as the physical resource id send with the failure message to CloudFormation.
* If you are using VPN, make sure you allow the function to connect to CloudFormation's endpoint.
* **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](https://webpack.js.org/) 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.
2. 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.resource.create(event, context);
handler.resource.update(event, context);
handler.resource.delete(event, context);
```
Feel free to return additional internal functions you want to test.
3. 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