Hands-on AWS CloudFormation - Part 2. Into to Intrinsic functions
Samira Yusifova
Posted on December 18, 2020
This is Part 2 of Hands-on AWS CloudFormation series, you can find Part 1 here.
This article will walk you through the basic Intrinsic functions of AWS CloudFormation, which are simply built-in functions that can help you manage your stacks.
Fn::Ref
What if you need to use dynamically generated values, the values that are required but are only available at run time? For example, let’s say that you need to create a new VPC and a new Security group in the same stack; and you need to pass VPC's ID to Security group. Here is how you can do that:
Resources:
mySecurityGroup: # Logical ID
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: My description here
VpcId:
Ref: myVPC # ref to VPC that will be created by CloudFormation
# etc.
myVPC: # Logical ID
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
# etc.
Another way to use Ref is with Parameters. Let’s say that you need to create a new EC2 instance and you need to pass EC2 key pairs and instance’s tag during a stack creation.
Parameters:
paramTagValue: # declare paramTagValue as a parameter
Description: Value of tag for EC2 instance
Type: String
paramKeyName: # declare paramKeyName as a parameter
Description: Name of an existing EC2 KeyPair to enable SSH access into the server
Type: 'AWS::EC2::KeyPair::KeyName'
Resources:
myEc2Instance: # Logical ID
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: t2.micro
ImageId: ami-5b41123e
Tags:
- Key: CloudFormationLab
Value: !Ref paramTagValue # ref to Parameters
KeyName: !Ref paramKeyName # ref to Parameters
Note, that Parameters allow you to pass user inputs at the time of stack creation or update (it will be discussed in the next parts of the series).
The third way to use Ref is as a pseudo parameter. Pseudo parameters are parameters that are predefined by AWS CloudFormation. For example, what if you need to get a region where your resources are being created, e.g. us-west-1? In that case use AWS::Region:
Tags:
- Key: CloudFormationLab
Value: !Ref AWS::Region # use pseudo parameter to get a region
Other pseudo parameters:
- AWS::AccountId (returns AWS account ID of the account in which the stack is being created)
- AWS::StackId (returns arn name of the stack)
- AWS::StackName (returns friendly name of the stack)
- AWS::NoValue (acts like the null value)
See the whole list by vising AWS's Pseudo parameters reference documentation.
Fn::Select
Select allows you to select one item from a list. Assume that you have a list of CIDR blocks and you need to create three subnets by passing the first CIDR block to the first subnet, the second CIDR block to the second subnet, so on. Here is how you can do that:
Parameters:
paramDbSubnetIpBlocks:
Description: 'Comma-delimited list of three CIDR blocks'
Type: CommaDelimitedList
Default: '10.0.48.0/24, 10.0.112.0/24, 10.0.176.0/24'
Resources:
myFirstSubnet: # Logical ID
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref myVPC
CidrBlock: !Select [ 0, !Ref paramDbSubnetIpBlocks ] # output is 10.0.48.0/24
# etc.
mySecondSubnet: # Logical ID
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref myVPC
CidrBlock: !Select [ 1, !Ref paramDbSubnetIpBlocks ] # output is 10.0.112.0/24
# etc.
myVPC:
Type: 'AWS::EC2::VPC'
Properties:
# etc
Fn::Join
With Join you can take a set of values and append it into a single value, separated by the specified delimiter. Here is an example:
Tags:
- Key: CloudFormationLab
Value: !Join [ ' ', [ 'SecurityGroup', 'for', !Ref AWS::Region ] ]
# Output is “SecurityGroup for us-west-1”
Fn::Split
Split is the opposite of Join. It splits a string into a list of string values. Commonly used with Select. Assume that the parameter's value is a comma-delimited string instead of list. You can still easily convert this string into a list.
Parameters:
paramDbSubnetIpBlocks:
Description: 'Comma-delimited string of three CIDR blocks'
Type: CommaDelimitedList
Default: '10.0.48.0/24, 10.0.112.0/24, 10.0.176.0/24'
Resources:
myFirstSubnet: # Logical ID
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [ 0, !Split [',', !Ref paramDbSubnetIpBlocks]] # output is 10.0.48.0/24
# etc.
mySecondSubnet: # Logical ID
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: !Select [ 1, !Split [',', !Ref paramDbSubnetIpBlocks]] # output is 10.0.112.0/24
# etc.
Fn::GetAtt
What if you need to get an Availability Zone of your EC2 instance specified in your template? Or maybe public DNS? Well, you can use GetAtt which returns the value of an attribute from a resource in the template.
Resources:
myEC2Instance: # Logical ID
Type: 'AWS::EC2::Instance'
# etc.
MountPoint:
# etc.
myInstanceVolume: # Logical ID
Type: 'AWS::EC2::Volume'
Properties:
Size: 100
AvailabilityZone: !GetAtt myEC2Instance.AvailabilityZone # use GetAtt to get AZ of your EC2 instance
Outputs:
PublicDNS:
Description: EC2 instance public DNS name
Value: !GetAtt myEC2Instance.PublicDnsName # use GetAtt to get public DNS of your EC2 instance
Note, that Outputs allow you to declare output values that you can return in response to view on console or import into other stacks (it will be discussed in the next parts of the series).
But how would you know which attributes each resource has? You need to navigate to AWS CloudFormation's documentation and search for
"AWS resource and property types reference". Find a target resource from the list, e.g. EC2, then search by resource types, e.g. AWS::EC2::Instance. Scroll down and look for "Return values" section. Under "Fn::GetAtt" you can find the list of all available attributes for selected resource type:
Fn::GetAZs
You don't want to hard-code a full list of AZs for a specified region, do you? In that case use GetAZs. It returns a list of AZs for a target region in alphabetical order.
!GetAZs us-east-1 # output is [ 'us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d' ]
You can use GetAZs with Ref and Select:
Resources:
myInstanceVolume: # Logical ID
Type: 'AWS::EC2::Volume'
Properties:
Size: 100
AvailabilityZone: !Select [0, !GetAZs !Ref 'AWS::Region']
# !Ref 'AWS::Region' returns a region where your volume is being created, e.g. us-east-1
# !GetAZs 'us-east-1' returns the list of AZs for 'us-east-1' region, which are ['us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d']
# !Select ['us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d'] returns the first value from the list, which is 'us-east-1a'
Fn::FindInMap
Let's start with Mappings. Why would you need it? Let’s say that you want to provision a new EC2 instance and you want to specify image ID (AMI) based on a region where your instance is being created. E.g. for North Virginia ('us-east-1') region you need to use 'ami-1853ac65' image id and for Singapore ('ap-southeast-1') - 'ami-e2adf99e'. In this case you can use Mappings which allows you to create simple "key:value" dictionaries for use in your resource declarations.
Mappings: # map image ids with regions
mapRegion:
us-east-1:
AMI: ami-1853ac65
us-west-1:
AMI: ami-bf5540df
eu-west-1:
AMI: ami-3bfab942
ap-southeast-1:
AMI: ami-e2adf99e
ap-southeast-2:
AMI: ami-43874721
Now, in order to call your map and get a value based on a key, you need to use FindInMap function:
Mappings: # map image ids with regions
mapRegion:
us-east-1:
AMI: ami-1853ac65
us-west-1:
AMI: ami-bf5540df
# etc.
Resources:
myEc2Instance: # Logical ID
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: t2.micro
ImageId: !FindInMap # define imageId based on region
- mapRegion # map's name
- !Ref 'AWS::Region' # top level key which is a region where your instance is being created
- AMI # second level key - e.g. for 'us-east-1' the value for ImageId is 'ami-1853ac65'
Other intrinsic functions
Now, that was a whole lot to cover but if you read it, kudos! So far we have covered the most crucial intrinsic functions. The rest might be discussed later, here is a brief list:
- Fn::Base64 - returns the Base64 representation of the input string (details)
- Fn::Cidr - returns an array of CIDR address blocks (details)
- Fn::ImportValue - returns the value of an output exported by another stack (details)
- Fn::Sub - substitutes variables in an input string with values that you specify (details)
- Fn::Transform - specifies a macro to perform custom processing on part of a stack template (details)
Conclusion
Intrinsic (i.e. build-in) functions are extremely useful and efficient if you'd like to start writing your templates. There is one more group of intrinsic functions left, named Condition functions, which I’m going to cover in Part 3 of Hands-on AWS CloudFormation series. Once we are done with functions we can start using them in custom templates.
Posted on December 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.