blogs

Using AWS CloudFormation macros

Posted 10 June 2022

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 

Leave a Reply

Your email address will not be published. Required fields are marked *