Apart from big things like API and pipeline definitions macro also handles many minor things that simplifies common code duplication parts in many CloudFormation templates. This page presents list of changes that macro applies.
However, main rule when working on macro transformations is to keep it transparent. For all of these cases native CloudFormation notations are supported and only in case when simplified notation is detected the transformation is applied. In principle, it is possible to include macro in any template, and it should just work transparently.
Keep in ming that despite all examples are presented in YAML notation you can use them also in JSON - macro is applied to template structure not its document. In some places the conventions needed to be picked to ensure unambiguity and predictability. These conventions differ from native CloudFormation conventions intentionally to avoid collisions and predictability is highly wanted mainly to avoid unattended template changes which would result in physical resources changes.
Note: Macro handlers work as a pre-processors, they do not have any execution-time information - they operate on plain template structure so there are certain limitations to it. Macro is just a template transformation which modifies it before executing by CloudFormation.
Primarily, there are some simplifications that reduces verbosity of the template source code mapping some simple properties into their full representations.
Cache
as plain value (string or Fn::
call) it will be expanded into S3
type with Location
taken as the value:
CachedProject:
Type: "AWS::CodeBuild::Project"
Properties:
ServiceRole: !ImportValue "root:v1:codebuild:role:name"
Environment:
Image: "maven:3.6.2-jdk-11"
# will be expanded into { "Type": "S3", "Location": !Sub "${CacheBucketName}/integrations" }
Cache: !Sub "${CacheBucketName}/integrations"
Artifacts
section will be automatically set to S3
if there is a Location
property:
PipelineProject:
Type: "AWS::CodeBuild::Project"
Properties:
ServiceRole: !ImportValue "root:v1:codebuild:role:name"
Environment:
Image: "maven:3.6.2-jdk-11"
Artifacts:
Location: !ImportValue "root:v1:codepipeline:artifacts-bucket:name"
Path: !Ref "ProjectName"
Name: "checkout.zip"
Packaging: "ZIP"
# this is not needed
# Type: "S3"
EnvironmentVariables
can be specified as a mapping:
PipelineProject:
Type: "AWS::CodeBuild::Project"
Properties:
Environment:
EnvironmentVariables:
ServiceUrl: "https://example.com"
ServiceVersion: "v1"
AttributeDefinitions
schema
are added automatically with type S
:
SomeTable:
Type: "AWS::DynamoDB::Table"
DeletionPolicy: "Retain"
Properties:
AttributeDefinitions:
-
AttributeName: "version"
AttributeType: "N"
# clientId and apiKey will be added automatically with Type: "S"
KeySchema:
-
AttributeName: "clientId"
KeyType: "HASH"
-
AttributeName: "version"
KeyType: "RANGE"
GlobalSecondaryIndexes:
-
IndexName: "keys"
KeySchema:
-
AttributeName: "clientId"
KeyType: "HASH"
-
AttributeName: "apiKey"
KeyType: "RANGE"
Policies
in AWS::IAM::Group
, AWS::IAM::Role
and AWS::IAM::User
can be written in more compat way as
a mapping from policy name to policy document and policy document gets wrapped with Statement
and Version
envelope:
ApiLambdaRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
-
Action: "sts:AssumeRole"
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
- "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess"
Policies:
AllowUsingSomeTable:
-
Action:
- "dynamodb:Query"
Effect: "Allow"
Resource:
- !Sub "${SomeTable.Arn}/index/keys"
PolicyDocument
in AWS::IAM::ManagedPolicy
and AWS::IAM::Policy
and AssumeRolePolicyDocument
in
AWS::IAM::Role
are expanded same way (as a single policy statement).AWS::Connect::ContactFlow
resource Content
can be defined as regular template structure and it will be
serialized to JSON (intrinsic function calls will be maintained).2019-10-30
by default (no need for manual definition).
ConnectFlow:
Type: "AWS::Connect::ContactFlow"
Properties:
InstanceArn: !GetAtt "ConnectInstance.Arn"
Name: "Default Flow"
Type: "CONTACT_FLOW"
Content:
StartAction: "start"
Actions:
-
Identifier: "start"
Type: "UpdateContactTargetQueue"
Parameters:
QueueId: !Ref "OrderQueueId"
Transitions:
NextAction: "order_number"
Errors:
-
ErrorType: "NoMatchingError"
NextAction: "end"
AWS::SecretsManager::Secret
initial value can be specified using SecretContent
property, that will be
serialized as SecretString
JSON (intrinsic function calls will be maintained).
MySecret:
Type: "AWS::SecretsManager::Secret"
Properties:
SecretContent:
OAuthUrl: !Ref "TokenEndpoint"
# these will be entered in console
ClientId: ""
ClientSecret: ""
For AWS::Lambda::Function
, AWS::Serverless::Function
and AWS::CodeBuild::Project
resources you can specify new
property - LogsRetentionInDays
. When it is detected, corresponding AWS::Logs::LogGroup
resource is automatically
defined with name that follows convention to provision log group that will be used by the function or build project.
Note: Log group can not exist at the moment of template execution, otherwise CloudFormation will fail to provision it. If you already have such log group, you need to manually delete it, or import existing log group into the stack.
Fn::ImportValue
within Fn::Sub
Fn::Sub
was a
big simplification on its own when it was introduced - before that you had to handle each string concatenation with
spaghetti Fn::Join
call with empty separator. The simplest case is when you can put all your placeholders into string
parameters for further interpolation - eg.
!Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApiId}/${ApiStage}/${Table.Arn}"
. If you use
something else than simple references or attributes you unfortunately need to switch to more verbose structure:
SomeProperty:
"Fn::Sub":
- "Your ${Message}"
-
Message: !ImportValue "RootBucketName"
This is very common in case of Fn::ImportValue
inserted into Fn::Sub
. With the macro you can interpolate import
values directly into pattern string to simplify the notation just the same way as replacements for Ref
and Fn::Sub
calls - all placeholders beginning with Import:
prefix are replaced with placeholders backed by Fn::ImportValue
:
SomeProperty: !Sub "Your ${Import:RootBucketName}"
Additionally, some vales are specified as defaults - these are the cases when the values are required by CloudFormation anyway, so if you use the following setup presented here, you can omit it and it will be applied implicitly.
Source
or Artifacts
property of the project it will be set to CODEPIPELINE
type.
# this effectively defines project for CodePipeline
PipelineProject:
Type: "AWS::CodeBuild::Project"
Properties:
ServiceRole: !ImportValue "root:v1:codebuild:role:name"
Environment:
Image: "maven:3.6.2-jdk-11"
LINUX_CONTAINER
and compute type to BUILD_GENERAL1_SMALL
.ShardsCount
for Kinesis data stream and you don't explicitly set StreamModeDetails
property, mode will be set to ON_DEMAND
.