AWS News Blog
Extending AWS CloudFormation with AWS Lambda Powered Macros
|
Today I’m really excited to show you a powerful new feature of AWS CloudFormation called Macros. CloudFormation Macros allow developers to extend the native syntax of CloudFormation templates by calling out to AWS Lambda powered transformations. This is the same technology that powers the popular Serverless Application Model functionality but the transforms run in your own accounts, on your own lambda functions, and they’re completely customizable. CloudFormation, if you’re new to AWS, is an absolutely essential tool for modeling and defining your infrastructure as code (YAML or JSON). It is a core building block for all of AWS and many of our services depend on it.
There are two major steps for using macros. First, we need to define a macro, which of course, we do with a CloudFormation template. Second, to use the created macro in our template we need to add it as a transform for the entire template or call it directly. Throughout this post, I use the term macro and transform somewhat interchangeably. Ready to see how this works?
Creating a CloudFormation Macro
Creating a macro has two components: a definition and an implementation. To create the definition of a macro we create a CloudFormation resource of a type AWS::CloudFormation::Macro
, that outlines which Lambda function to use and what the macro should be called.
Type: "AWS::CloudFormation::Macro"
Properties:
Description: String
FunctionName: String
LogGroupName: String
LogRoleARN: String
Name: String
The Name
of the macro must be unique throughout the region and the Lambda function referenced by FunctionName
must be in the same region the macro is being created in. When you execute the macro template, it will make that macro available for other templates to use. The implementation of the macro is fulfilled by a Lambda function. Macros can be in their own templates or grouped with others, but you won’t be able to use a macro in the same template you’re registering it in. The Lambda function receives a JSON payload that looks like something like this:
{
"region": "us-east-1",
"accountId": "$ACCOUNT_ID",
"fragment": { ... },
"transformId": "$TRANSFORM_ID",
"params": { ... },
"requestId": "$REQUEST_ID",
"templateParameterValues": { ... }
}
The fragment
portion of the payload contains either the entire template or the relevant fragments of the template – depending on how the transform is invoked from the calling template. The fragment will always be in JSON, even if the template is in YAML.
The Lambda function is expected to return a simple JSON response:
{
"requestId": "$REQUEST_ID",
"status": "success",
"fragment": { ... }
}
The requestId
needs to be the same as the one received in the input payload, and if status
contains any value other than success (case-insensitive) then the changeset will fail to create. Now, fragment
must contain the valid CloudFormation JSON of the transformed template. Even if your function performed no action it would still need to return the fragment for it to be included in the final template.
Using CloudFormation Macros
To use the macro we simply call out to Fn::Transform
with the required parameters. If we want to have a macro parse the whole template we can include it in our list of transforms in the template the same way we would with SAM: Transform: [Echo]
. When we go to execute this template the transforms will be collected into a changeset, by calling out to each macro’s specified function and returning the final template.
Let’s imagine we have a dummy Lambda function called EchoFunction, it just logs the data passed into it and returns the fragments unchanged. We define the macro as a normal CloudFormation resource, like this:
EchoMacro:
Type: "AWS::CloudFormation::Macro"
Properties:
FunctionName: arn:aws:lambda:us-east-1:1234567:function:EchoFunction
Name: EchoMacro
The code for the lambda function could be as simple as this:
def lambda_handler(event, context):
print(event)
return {
"requestId": event['requestId'],
"status": "success",
"fragment": event["fragment"]
}
Then, after deploying this function and executing the macro template, we can invoke the macro in a transform at the top level of any other template like this:
AWSTemplateFormatVersion: 2010-09-09
Transform: [EchoMacro, AWS::Serverless-2016-10-31]
Resources:
FancyTable:
Type: AWS::Serverless::SimpleTable
The CloudFormation service creates a changeset for the template by first calling the Echo macro we defined and then the AWS::Serverless transform. It will execute the macros listed in the transform in the order they’re listed.
We could also invoke the macro using the Fn::Transform
intrinsic function which allows us to pass in additional parameters. For example:
AWSTemplateFormatVersion: 2010-09-09
Resources:
MyS3Bucket:
Type: 'AWS::S3::Bucket'
Fn::Transform:
Name: EchoMacro
Parameters:
Key: Value
The inline transform will have access to all of its sibling nodes and all of its children nodes. Transforms are processed from deepest to shallowest which means top-level transforms are executed last. Since I know most of you are going to ask: no you cannot include macros within macros – but nice try.
When you go to execute the CloudFormation template it would simply ask you to create a changeset and you could preview the output before deploying.
Example Macros
We’re launching a number of reference macros to help developers get started and I expect many people will publish others. These four are the winners from a little internal hackathon we had prior to releasing this feature:
Name | Description | Author |
---|---|---|
PyPlate | Allows you to inline Python in your templates | Jay McConnell – Partner SA |
ShortHand | Defines a short-hand syntax for common cloudformation resources | Steve Engledow – Solutions Builder |
StackMetrics | Adds cloudwatch metrics to stacks | Steve Engledow and Jason Gregson – Global SA |
String Functions | Adds common string functions to your templates | Jay McConnell – Partner SA |
Here are a few ideas I thought of that might be fun for someone to implement:
- Automatic R53 domain registration + AWS Certificate Manager (ACM) certificate provisioning
- Automatic S3 static website or Amazon CloudFront distribution with a custom domain
- Extending CloudFormation mappings to read from a DynamoDB table
- Automatic IPv6 setup for Amazon Virtual Private Cloud (Amazon VPC)
- Automatic webhook subscription for Slack, Twitter, Messenger integrations
If you end up building something cool I’m more than happy to tweet it out!
Available Now
CloudFormation Macros are available today, in all AWS regions that have AWS Lambda. There is no additional CloudFormation charge for Macros meaning you are only billed normal AWS Lambda function charges. The documentation has more information that may be helpful.
This is one of my favorite new features for CloudFormation and I’m excited to see some of the amazing things our customers will build with it. The real power here is that you can extend your existing infrastructure as code with code. The possibilities enabled by this new functionality are virtually unlimited.
– Randall