In the previous post, we discussed Pseudo parameters and Intrinsic functions such as Ref, GetAtt, GetAZs, FindInMap, Select, Join, and Split. There is one more subset of intrinsic functions left. Let’s quickly walk through Condition functions and then create a template and a stack using most of the intrinsic functions we’ve already learned.
Conditionally provisioned resources
Assume that you are working in two different environments - 'dev' and 'prod'. You need to provision a new EC2 resource. You also want to create and attach a new EBS volume but only for prod env because you want to save extra money by using reduced capabilities of 'dev'. In that case you might want to use the intrinsic function Condition. It returns the evaluated result of the specified condition. For e.g.:
Parameters:
paramEnvironmentType: # ask a user to define whether it is 'dev' or 'prod'
Description: Environment type
Default: dev # by default it is 'dev' environment
Type: String
AllowedValues: [dev, prod]
ConstraintDescription: Must specify 'dev' or 'prod'
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
Resources:
myVolume: # create a new EBS volume only if environment is 'prod'
Type: 'AWS::EC2::Volume'
Condition: isProd # conditionally create EBS volume (only if environment is 'prod')
Properties:
Size: 100
# etc.
As you can see, in "Parameters" section we declared a new "paramEnvironmentType" input parameter which value - either 'dev' or 'prod' - should be defined by a user at the time of stack creation.
Next, in "Conditions" section we declared a new "isProd" boolean variable which checks whether a user provisions resources for 'dev' or 'prod' based on "paramEnvironmentType" value.
And finally, in "Resources" section we used Condition function to refer to "isProd" condition. Thus, AWS CLoudFormation will check the condition and create EBS volume only if environment is 'prod'.
Intrinsic functions for conditions
You can use five intrinsic functions to conditionally create stack resources.
Fn::Equals
Compares two values and returns true if the two values are equal, or returns false if they aren't.
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
Fn::And
Returns true if all the specified conditions are true, or returns false if any one of the conditions is false. E.g., let’s create a “isCreateS3Bucket” condition that evaluates to true only if both month is 'January' and environment is 'prod':
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
isCreateS3Bucket: !And
- !Equals [!Ref paramMonth, 'January'] # if 'January' then TRUE
- !Condition isProd # if 'prod' then TRUE
# isCreateS3Bucket condition is TRUE only if both month is 'January' and environment is 'prod'
Note, don’t exceed the limit of 10 conditions per Fn::And.
Fn::Or
Returns true if any one of the specified conditions is true, or returns false if all of the conditions are false. E.g., let’s create a “isCreateS3Bucket” condition that evaluates to true if either month is 'January' or environment is 'prod', or both month is 'January' and environment is 'prod':
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
isCreateS3Bucket: !Or
- !Equals [!Ref paramMonth, 'January'] # if 'January' then TRUE
- !Condition isProd # if 'prod' then TRUE
# isCreateS3Bucket condition is TRUE if either month is 'January' or environment is 'prod'
Note, don’t exceed the limit of 10 conditions per Fn::Or.
Fn::Not
Returns true if false and vice versa. E.g., let’s create a “isNonProd” condition that evaluates to false if the environment is 'prod' and true otherwise.
Conditions:
isNonProd: !Not [!Ref paramEnvironmentType, prod] # if 'prod' then FALSE, otherwise TRUE
Fn::If
Returns one value if the specified condition is true and another value if the specified condition is false. E.g., if environment is 'prod' then let’s use “t2.large” as EC2 instance type, otherwise use “t2.micro”.
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
Resources:
myEC2Instance: # create a new EC2 instance
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: !If [isProd, t2.large, t2.micro] ] # if 'prod', then use t2.micro, otherwise use t1.micro
# etc.
Let's get our hands dirty!
Time to apply all our knowledge about Intrinsic functions. We are going to create a simple CloudFormation template and provision a new EC2 instance. Also let's add conditionally provisioned EBS volume which must be created and attached to instance only if environment is 'prod'.
Step 1. Create a template
Create a new "2-IntrinsicFunctions.yaml" file in your favorite code editor and add the following code:
## =================== DESCRIPTION ===================
Description: >-
AWS CloudFormation sample template.
Create a new EC2 instance and if environment is 'prod' then attach a new EBS volume to it
## =================== PARAMETERS ===================
Parameters:
paramEnvironmentType: # ask a user to define whether it is 'dev', 'qa' or 'prod' environment
Description: Environment type
Default: dev # by default it is 'dev' environment
Type: String
AllowedValues: [dev, qa, prod]
ConstraintDescription: Must specify 'dev', 'qa' or 'prod'
paramTagValues: #ask a user to specify some values for tags
Description: 'Comma-delimited list of tag values'
Type: CommaDelimitedList
Default: 'JonSnow, DaenerysTargaryen, AryaStark'
## =================== MAPPINGS ===================
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
## =================== CONDITIONS ===================
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
isDev: !Equals [!Ref paramEnvironmentType, dev] # if 'dev' then TRUE, otherwise FALSE
## =================== RESOURCES ===================
Resources:
myEC2Instance: # create a new EC2 instance
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !FindInMap # define imageId based on region
- mapRegion # map's name
- !Ref 'AWS::Region' # top level key which is a region where a new instance is being created
- AMI # second level key - e.g. for 'us-east-1' the value for ImageId is 'ami-0ff8a91507f77f867'
InstanceType: !If [isProd, t2.micro, !If [isDev, t2.nano, t1.micro] ] # if 'prod', then t2.micro, (else) if 'dev' then t2.nano otherwise (if 'qa') then t1.micro
Tags:
- Key: CloudFormationLab
Value: !Join [' ', ['EC2 Instance for', !Ref AWS::Region] ]
myVolume: # create a new EBS volume only if environment is 'prod'
Type: 'AWS::EC2::Volume'
Condition: isProd # conditionally create EBS volume (only if environment is 'prod')
Properties:
Size: 100 # 100 GiB
AvailabilityZone: !GetAtt myEC2Instance.AvailabilityZone # get AZ of a new EC2 instance
Tags:
- Key: CloudFormationLab
Value: !Select [ 0, !Ref paramTagValues ] # output is 'JonSnow'
myMountPoint: # attach an Amazon EBS volume to an EC2 instance only i environment is 'prod'
Type: 'AWS::EC2::VolumeAttachment'
Condition: isProd # conditionally attach EBS volume (only if environment is 'prod')
Properties:
InstanceId: !Ref myEC2Instance # ref to a new EC2 instance
VolumeId: !Ref myVolume # ref to a new EBS volume
Device: /dev/sdh
During stack creation we are going to specify environment type. For either 'dev' or 'qa' a new EC2 instance only should be provisioned ignoring an EBS volume and a Mount Point. The instance type also depends on environment type: t2.nano for 'dev' and t1.micro for 'qa'.
Once we re-select the environment type switching to 'prod', an instance type should be changed to t2.micro and a new EBS volume should be created and attached to the instance.
Step 2. Create a stack
Log in to AWS Console, search for CloudFormation service. Click on "Create stack" and upload your yaml file:
You can view your template in AWS CloudFormation Designer, which is a graphic tool for creating, viewing, and modifying templates:
Give a name to your stack and specify parameters values. First, let's go with 'dev' environment type:
Click next, add tags if you want, review your stack and hit on "Create stack". Wait couple of seconds and click on refresh button to get the updated list of events. Voila!
Step 3. New resources for 'dev'
Click on "Resources" tab to see the list of newly created resources:
As you can see, a new EC2 instance with Logical ID of 'myEC2Intance' has been created. Once you click on its physical ID you will be navigated to Amazon EC2:
Note, as we have specified 'dev' environment type, AWS CloudFormation used t2.nano as an instance type.
InstanceType: !If [isProd, t2.micro, !If [isDev, t2.nano, t1.micro] ] # if 'prod', then t2.micro, (else) if 'dev' then t2.nano otherwise (if 'qa') then t1.micro
We can also check "Tags" tab to verify that Fn::Join intrinsic function concatenated a string with the region.
Tags:
- Key: CloudFormationLab
Value: !Join [' ', ['EC2 Instance for', !Ref AWS::Region] ]
Step 4. Change parameters and update the stack
Now, let's change environment type from 'dev' to 'prod' and re-run the stack. For that, click on Stack actions dropdown and select Create change set for current stack:
Keep the current template:
Next, re-select 'prod' as a new value of "paramEnvironmentType" parameter:
Then hit Create change set. Look at the list of changes:
Change sets allow you to preview how proposed changes to a stack might impact your running resources. In our case, AWS CloudFormation is going to add two more resources (EBS volume and Mount Point) and also update EC2 instance type from t2.nano to t2.micro.
Go ahead and execute the changes:
Step 5. New resources for 'prod'
Click on "Resources" tab to see the list of created/updated resources:
Let's examine EC2 instance. Click on its physical ID:
Note three changes:
Instance type has been changed from t2.nano for 'dev' to t2.micro for 'prod'.
A new EBS volume has been created.
The EBS volume has been attached to the instance.
Now, click on Volume ID:
Notice that EBS volume's size is 100 GiB as we specified in the template. Also the volume was provisioned in the same AZ as that of the instance.
myVolume: # create a new EBS volume only if environment is 'prod'
Type: 'AWS::EC2::Volume'
Condition: isProd # conditionally create EBS volume (only if environment is 'prod')
Properties:
Size: 100 # 100 GiB
AvailabilityZone: !GetAtt myEC2Instance.AvailabilityZone # get AZ of a new EC2 instance
We can also check "Tags" tab to verify that Fn::Select intrinsic function picked the first element of "paramTagValues" list.
Parameters:
paramTagValues: #ask a user to specify some values for tags
Description: 'Comma-delimited list of tag values'
Type: CommaDelimitedList
Default: 'JonSnow, DaenerysTargaryen, AryaStark'
#etc.
<span class="na">Tags</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Key</span><span class="pi">:</span> <span class="s">CloudFormationLab</span>
<span class="na">Value</span><span class="pi">:</span> <span class="kt">!Select</span> <span class="pi">[</span> <span class="nv">0</span><span class="pi">,</span> <span class="kt">!Ref</span> <span class="nv">paramTagValues</span> <span class="pi">]</span> <span class="c1"># output is 'JonSnow'</span>
Step 6. Cleanup
Don't forget to clean all provisioned resources once you are done with the stack. Otherwise you might be charged for running resources.
By deleting a stack, all its resources will be deleted as well.
Summary
In the template above we tried to implement the most of the intrinsic functions such as GetAtt, Ref, FindInMap, Join, Select, Equals, etc. and showed how to specify conditionally provisioned resources.
I hope this post was easy to follow and helped you to better understand the implementation of intrinsic functions in AWS CloudFormation.
Top comments (0)