Macros enable you to perform custom processing on templates, simple actions like find-and-replace operations to extensive transformations of entire templates.
CloudFormation let process template with include additional template with S3 URL or serverless transformation with lambda. These helps increase the flexibility and performance of automation
Macros can have two types of transformations
- AWS::Include transform enables you to insert boilerplate template snippets into your templates.
- AWS::Serverless transform takes an entire template written in the AWS Serverless Application Model (AWS SAM) syntax and transforms and expands it into a compliant AWS CloudFormation template
We are here going to look at how to transform CloudFormation template with AWS::Serverless transformation with simple example
CloudFormation macro work with three component which are lambda function, Macro function and CloudFormation stack to create and transform stack
To create macro, you need create following components
- Lambda function for process template, Lambda function accept template snippet or entire template and user passed parameters then processed and return processed snippet or entire template via response
- Then with resource type AWS::CloudFormation::Macro create macro function which enables to call lambda function with in the CloudFormation. Resource add Lambda function ARN to invoke this function
After we created both elements, we able to use in CloudFormation template to transform template or snippets
To process template section or snippets use Fn::Transform function to reference relative to the content you want to transform snippets or sections. If you want to transform whole template use Transform section
When invoke macro lambda function by macro it will pass following params to process template
{ "region" : "us-east-1", "accountId" : "$ACCOUNT_ID", "fragment" : { ... }, "transformId" : "$TRANSFORM_ID", "params" : { ... }, "requestId" : "$REQUEST_ID", "templateParameterValues" : { ... } }
Here fragment When use Transform section it contains entire template except Transform section. When use Fn::Transform intrinsic function it contain template snippets related to function including all siblings except Fn::Transform function
TemplateParameterValues contain custom params which user passed
After we processed template need to return following params
{ "requestId" : "$REQUEST_ID", "status" : "$STATUS", "fragment" : { ... } }
When return fragment contains processed template snippets or entire template. status should be success CloudFormation will consider as failure if any other values for status and also, we should add requestId from params when invoke lambda function
We know basic ideas about CloudFormation macro now focus on template and functions
You can get all codes from here https://github.com/misyaath/awscfmacrolatestami/tree/master/cfmacro
Example shows how to transform ImageId when create a stack; get latest snapshot of AMI to launch new instance
First need to create Lambda function to add macro. Below template.yaml shows create lambda function with SAM (Serverless Application Model)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > AWS CloudFormation Macro Function for get Latest AMI ID Globals: Function: Timeout: 3 Resources: CFMacroFunction: Type: AWS::Serverless::Function Properties: CodeUri: cfmacro/ Handler: app.lambdaHandler Runtime: nodejs14.x Policies: - Statement: - Sid: DescribeImagesPolicies Effect: Allow Action: - ec2:DescribeImages Resource: "*" Tags: Name: CFMACROFUNCTION Outputs: CFMacroFUnctionARN: Description: "CF Macro Lambda Function ARN" Value: !GetAtt CFMacroFunction.Arn
Then create a Lambda function with node js to handle macro request and return processed template snippets or entire template to CloudFormation
Below shows main function to get Latest ImageId. see complete codes here https://github.com/misyaath/awscfmacrolatestami/tree/master/cfmacro
import aws from 'aws-sdk' import {AmiParams} from "./src/AmiParams.js"; import {GetAmi} from "./src/GetAmi.js"; let response; /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html * @param {Object} context * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ export const lambdaHandler = async (event, context) => { let latestAmi; try { let EC2 = new aws.EC2({"region": "us-east-1"}); // See these codes here https://github.com/misyaath/awscfmacrolatestami/tree/master/cfmacro/src let params = new AmiParams(); params.dryRun = false; params.owners = "self"; response = await EC2.describeImages(params.setParams()).promise(); if ("Images" in response) { latestAmi = new GetAmi(response.Images).byCreationDate().getLatest().ImageId; event.fragment.ImageId = latestAmi; } } catch (err) { console.log(err); return false; } //return response with processed snippets return { "requestId": event.requestId, "status": "success", "fragment": event.fragment }; };
Now have created lambda function then build and deploy it with sam cli
sam build sam build --guided
Then need to create macro function with CloudFormation template. Replace with <<Lambda Function ARN>> in with lambda function ARN
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "", "Resources": { "awsCFMacro": { "Type": "AWS::CloudFormation::Macro", "Properties": { "Name": "AMIIdMacroFunction", "Description": "Get latest AMI id with macro", "FunctionName": "<<Lambda Function ARN>>" } } } }
Create Macro Function Before you create Stacks to use macro
After created macro need create CloudFormation template to create stack for new instance
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "", "Resources": { "cfMacroInstance": { "Type": "AWS::EC2::Instance", "Properties": { "Fn::Transform": { "Name": "AMIIdMacroFunction" }, "InstanceType": "t2.micro", "NetworkInterfaces": [ { "AssociatePublicIpAddress": "true", "DeviceIndex": "0", "GroupSet": [ { "Fn::ImportValue": "cfMacroSG" } ], "SubnetId": { "Fn::ImportValue": "cfMacroSubnet" } } ], "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "Ebs": { "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": "10" } } ] } } } }
You can see other related CloudFormation template here https://github.com/misyaath/awscfmacrolatestami/tree/master/cf
"Fn::Transform": { "Name": "AMIIdMacroFunction" },
Can see in template we are using Fn::Transform intrinsic function to process template snippets to process with macro. inside the Fn::Transform intrinsic function added Name attribute to identify macro function
Now you can create or update stack with AWS cli or AWS Console. it run macro function and launch instance. Its basic idea of how macro function works how to add basic macro function in CloudFormation template.
You can see complete user guide of CloudFormation macro https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-macros.html
Get Images from AWS SDK references are here https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html