Using AWS CloudFormation Macros and Custom Resources with the Serverless Framework
Serverless Framework released 1.4.11 few days ago, which adds additional Capability when Transform is detected, it means we can now use AWS CloudFormation Macros to Perform Custom Processing from Serverless Framework.
In this post, I show an example of using CloudFormation custom resources and macros to add default article to sample application after each deployment.
CloudFormation macros and Custom resources
CloudFormation macros are like pre-processors of your CloudFormation templates. After you submit your CloudFormation template, macros are called to transform portions of your template before CloudFormation actually starts provisioning resources.
Custom resources greatly expand what you can do with CloudFormation as you can run custom logic as part of your CloudFormation deployment.
Use case
I run my CloudFormation template that setups up An DynamoDb table and lambda functions. CloudFormation template then adds default welcome article to empty DynamoDb table after each deployment.
Prerequisites
The following must be done before following this guide:
- Setup an AWS account
- Install the AWS CLI
- Configure the AWS CLI with user credentials
- Install or update the Serverless framework (≥1.4.11) to latest version
$ npm install -g serverless
CloudFormation custom resources
Let’s see how to use custom resources, to use a CloudFormation custom resource, you’ll need:
- Define custom resource.
- Make custom resource logic available by deploying to an AWS Lambda function.
- References Lambda function to use the custom resource in your yml template.
Here’s an example of a custom resource and lambda function:
As shown above, You must provide a ServiceToken property. The ServiceToken is an ARN of either an AWS Lambda function.
Problem
If we deploy above template, The Custom Resource are invoked on first deployment. it won’t trigger again. To fix this issue, We have to add a property with dynamic value to the custom resource.
In my example, I use CloudFormation macro which get stack’s last updated date time, and then injects the date time value into the custom resource’s property.
Setup CloudFormation Macro
Create a new Serverless framework project and add following config to serverless.yml
Note the new Cloudformation resource AWS::Cloudformation::Macro
, which makes the Lambda function accessible from other CloudFormation templates.
Add Lambda function
Now, let’s update our handler.js, create lambda function to get CloudFormation stack’s LastUpdatedTime:
For above macro, we want to use stack name from provided CloudFormation Parameters in our CloudFormation template. The parameters will be provided on our Lambda event object under the Parameters key, and our CloudFormation template will be available in the fragment key.
Deploy CloudFormation Macro
The macro can be deployed like this:
$ sls deploy
Usage
The newly created macro can now be used, back to serverless.yml
template of custom resource, add StackLastUpdatedTime Property to use our newly generated macro, forcing custom resource to recreate on each deployment using a unique parameter StackLastUpdatedTime:
Next, let’s create a lambda function that is triggered on each deployment and adds welcome article to DynamoDB table if it is empty.
Note that when the custom resource create request is successful, a response must be sent to the S3 bucket is similar to the following format:
{
StackId:
"arn:aws:cloudformation:ap-southeast-2:687416365397:stack/CustomResourceCMSDefaultPost-dev/67c30500-6741-11e9-9b67-0a8814e42c77",
RequestId: "5270edad-8d64-453a-8683-da2db12f7381",
Status: "SUCCESS",
PhysicalResourceId: "2019/04/25/[$LATEST]8c7b0002a0ba45c29ea6685b99e28977",
LogicalResourceId: "DefaultPost",
Data: {
LastUpdatedTime: "2019-04-25T12:07:16.747Z"
}
};
Deploy the service and Try it out!
$ sls deploy
Now run following commend to check whether default article has been added to DynamoDb table.
$ aws dynamodb scan --table-name devposts
Result
I hope you have found this article useful, You can find complete project in my GitHub repo.