DEV Community

Cover image for Hands-on AWS CloudFormation - Part 3. Intrinsic functions in Action
Samira Yusifova
Samira Yusifova

Posted on

Hands-on AWS CloudFormation - Part 3. Intrinsic functions in Action

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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:

Alt Text

You can view your template in AWS CloudFormation Designer, which is a graphic tool for creating, viewing, and modifying templates:

Alt Text

Give a name to your stack and specify parameters values. First, let's go with 'dev' environment type:

Alt Text

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!

Alt Text

Step 3. New resources for 'dev'

Click on "Resources" tab to see the list of newly created resources:

Alt Text

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:

Alt Text

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  
Enter fullscreen mode Exit fullscreen mode

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] ] 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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:

Alt Text

Keep the current template:

Alt Text

Next, re-select 'prod' as a new value of "paramEnvironmentType" parameter:

Alt Text

Then hit Create change set. Look at the list of changes:

Alt Text

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:

Alt Text

Step 5. New resources for 'prod'

Click on "Resources" tab to see the list of created/updated resources:

Alt Text

Let's examine EC2 instance. Click on its physical ID:

Alt Text

Note three changes:

  1. Instance type has been changed from t2.nano for 'dev' to t2.micro for 'prod'.

  2. A new EBS volume has been created.

  3. The EBS volume has been attached to the instance.

Now, click on Volume ID:

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

We can also check "Tags" tab to verify that Fn::Select intrinsic function picked the first element of "paramTagValues" list.

Alt Text

 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.

      Tags:
        - Key: CloudFormationLab
          Value: !Select [ 0, !Ref paramTagValues ] # output is 'JonSnow'
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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)