# Using AWS CloudFormation Macros is also Plain and Simple

on
Sep 07, 2018
in

Recently I wrote a blog post on creating a custom resource for AWS CloudFormation to generate a random string. With the launch of Macro support, we can even do more. I build a similar function: a random string generator. Using Macros, the function will generate a new string every time you execute a CloudFormation deployment.
Btw, the Macro support is at the moment of publishing just GA for a few hours. I wanted to play around with this new feature and share my experience quickly. Probably lots of use cases will appear soon. Hopefully with this blog post you have your first Macro experiment done in minutes.

## Deploy a Macro Lambda

First we have to deploy a Lambda function. Of course with the right policies and permissions, and new with Macro you have to add a AWS::CloudFormation::Transform resource.

AWSTemplateFormatVersion: 2010-09-09
Resources:
TransformFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import traceback
import string
import random

def random_string(size=6):
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(size))

def handler(event, context):
response = {
"requestId": event["requestId"],
"status": "success"
}
params = event.get("params", {})
operation = params.get("Operation", "upper")
number = int(params.get("Number", 6))
no_param_string_funcs = ["upper", "lower"]

rs = random_string(number)
if operation == "upper":
response["fragment"] = rs.upper()
elif operation == "lower":
response["fragment"] = rs.lower()
else:
response["status"] = "failure"

return response

Handler: index.handler
Runtime: python3.6
Role: !GetAtt TransformExecutionRole.Arn
TransformExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: ['logs:*']
Resource: 'arn:aws:logs:*:*:*'
TransformFunctionPermissions:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt TransformFunction.Arn
Principal: "cloudformation.amazonaws.com"
Transform:
Type: AWS::CloudFormation::Transform
Properties:
Name: !Sub "${AWS::AccountId}::RandomString" content: "Generates a random string" RoutingTable: '*': 0_1 Versions: - VersionName: 0_1 content: "Version 0_1 of the RandomString Generator" FunctionName: !GetAtt TransformFunction.Arn ExecutionPolicy: Version: 2012-10-17 Id: AllowOtherAccountPolicy Statement: - Sid: AllowExecution Effect: Allow Principal: AWS: !Sub '${AWS::AccountId}'
Action: "cloudformation:CreateChangeSet"
Resource: !Sub "arn:*:cloudformation:${AWS::Region}:${AWS::AccountId}:transform/RandomString"


Deploy the stack:

aws cloudformation deploy \
--stack-name cfn-macro-randomstring \
--template-file template.yaml \
--capabilities CAPABILITY_IAM


## Use the Macro

Now we can test our CloudFormation Macro by creating and deploying the following template. Like I said, it will generate a new random string every time a deployment happens.

Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: "join-test"
Value:
'Fn::Join':
- ''
- - "mybucket-"
- 'Fn::Transform':
Name: "RandomString"
Parameters:
Operation: "lower"
Number: "8"

aws cloudformation deploy \
--stack-name test-the-macro \
--template-file test.yaml


The Macro feature is plain and easy when you really want something to happen everytime you deploy the template. It could be handy when you are expirimenting and want a new resource, property value or attribute, everytime you change something in your stack. And of course you can think of many more use cases.
I tried to use the shorthand notation !Sub together with !Transform { Name : RandomString } without any parameters, but this is not supported yet. Although the documentation page says it does. Might be an easy bug and fixed quickly.
I’m also a big fan of cfn-lint. I automated this in my own (opinionated) cfn deployment cli. However, I now had to disable this check because cfn-lint is not up-to-date yet. And I think it will become a lot harder to enforce policies with all the powers of this Macro feature.
What I do like about the new Macro feature is that the lambda function is very small and simple. You just have to return a few values, instead of invoking a special CloudFormation endpoint with a lot of values, like Custom Resources require.
If you want to build your own Macro, also check out these examples.