Conditions - !If function

Everything you need to know about the !If function

Introduction

We are going to explore:

  • Inner workings of the !If function

  • How they work in conjunction with the !Equals function

  • How they work with the pseudo parameter !NoValue

With !If functions, a value is returned if a certain condition evaluates to true while another value is returned when the condition evaluates to false.

If a condition evaluates to false, any Resource that depends on that condition will not be created in the stack.

If the condition evaluates to true, any resource that depends on that condition will be created in the stack.

Template Sample

Let's explore the template below and examine how Conditions are utilized in the template

AWSTemplateFormatVersion: 2010-09-09
Description: Creating an ec2 instance

Mappings:          # Allows cloudformation to use only these AMIs in the following regions. Always update the latest AMI IDs
  MapEc2Region:
    us-east-2:
      HVM64: ami-0c0d141edc4f470cc
    us-west-2:
      HVM64: ami-093467ec28ae4fe03

  MapEnvironmentType:     # Allows cloudformation to only use these instance type depending on the Env type 
    Prod: 
      InstanceType: t3.small
    Dev:
      InstanceType: t3.micro


Parameters:

  ParamEnvironmentType:             
    Description: Select the Environment Type from the list
    Type: String
    Default: Dev
    AllowedValues:
      - Prod
      - Dev

  ParamKeyName:
    Description: Select the key name from the list
    Type: AWS::EC2::KeyPair::KeyName

  ParamAZName:
    Description: Select the Avaiability Zone name from the list
    Type: AWS::EC2::AvailabilityZone::Name


Conditions:
  ConditionForProdEIP: !Equals [!Ref ParamEnvironmentType, Prod]
  ConditionForProdSG: !Equals [!Ref ParamEnvironmentType, Prod]
  ConditionForDevSG: !Equals [!Ref ParamEnvironmentType, Dev]


Resources:
  ProdSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: ConditionForProdSG
    Properties:
      GroupDescription: Allow HTTP traffic 
      GroupName: Prod SecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0

  DevSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: ConditionForDevSG
    Properties:
      GroupDescription: Allow HTTP and SSH traffic 
      GroupName: Dev SecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0

        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 0.0.0.0/0



  MyEIP:
    Type: AWS::EC2::EIP
    Condition: ConditionForProdEIP
    Properties:
      InstanceId: !Ref EC2Instance


  EC2Instance: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap 
        - MapEc2Region
        - !Ref AWS::Region
        - HVM64
      KeyName: !Ref ParamKeyName
      AvailabilityZone: !Ref ParamAZName
      InstanceType: !FindInMap
        - MapEnvironmentType
        - !Ref ParamEnvironmentType
        - InstanceType
      Tags:
        - Key: Name
          Value: !Ref ParamEnvironmentType
      UserData:
        Fn::Base64: |
          #!/bin/bash -xe
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo '<html><h1>Hello From Your Restart Web Server!</h1></html>' > /var/www/html/index.html
      SecurityGroups: 
        - !If [ConditionForProdSG, !Ref ProdSecurityGroup, !Ref AWS::NoValue]

With Conditions, you can deploy both / either the Dev and / or Prod environments using the same template. You can achieve this with the help of Parameters, Mappings and Resources.

Let's explore how Parameters and the other sections assist in deploying different environments.

Step 1

Parameters

Let's extract the Parameter section of our template

 ParamEnvironmentType:             
    Description: Select the Environment Type from the list
    Type: String
    Default: Dev
    AllowedValues:
      - Prod
      - Dev

The ParamEnvironmentType parameter tells CloudFormation which environment to be created during the stack creation process.

This is where you choose either Dev or Prod

Step 2

Conditions

Conditions:
  ConditionForProdEIP: !Equals [!Ref ParamEnvironmentType, Prod]
  ConditionForProdSG: !Equals [!Ref ParamEnvironmentType, Prod]
  ConditionForDevSG: !Equals [!Ref ParamEnvironmentType, Dev]

Conditions evaluate the selected environment type (by using !Ref ParamEnvironmentType) and decides which resources are going to be created.

  • ConditionForDevSG evaluate to true if the environment type selected is Dev.

  • ConditionForProdSG and ConditionForProdEIP evaluate to true if the environment type selected is Prod.

Resource Creation

Notice that Condition: ConditionForProdSG is now added to the following Resources

  • ProdSecurityGroup

  • MyEIP

We first evaluate that Resource and confirm whether the condition associated with it evaluates to true or false.

  • If the condition ConditionForProdSG is true, the ProdSecurityGroup Resource will be created. If the condition is false, the Resource is not created. Same goes forMyEIPResource.

  • If the condition ConditionForDevSG is true, the DevSecurityGroup Resource will be created

Resources:
  ProdSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: ConditionForProdSG
    Properties:
      GroupDescription: Allow HTTP traffic 
      GroupName: Prod SecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0

  DevSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: ConditionForDevSG
    Properties:
      GroupDescription: Allow HTTP and SSH traffic 
      GroupName: Dev SecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0

        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 0.0.0.0/0



  MyEIP:
    Type: AWS::EC2::EIP
    Condition: ConditionForProdEIP
    Properties:
      InstanceId: !Ref EC2Instance


  EC2Instance: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap 
        - MapEc2Region
        - !Ref AWS::Region
        - HVM64
      KeyName: !Ref ParamKeyName
      AvailabilityZone: !Ref ParamAZName
      InstanceType: !FindInMap
        - MapEnvironmentType
        - !Ref ParamEnvironmentType
        - InstanceType
      Tags:
        - Key: Name
          Value: !Ref ParamEnvironmentType
      UserData:
        Fn::Base64: |
          #!/bin/bash -xe
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo '<html><h1>Hello From Your Restart Web Server!</h1></html>' > /var/www/html/index.html
      SecurityGroups: 
        - !If [ConditionForProdSG, !Ref ProdSecurityGroup, !Ref AWS::NoValue]

How the !If function works with Conditions

If we intend to deploy a Dev environment, we expect to attach a Dev security group to the instance and vice versa.

Since the EC2 Instance does not rely on any condition evaluating to true or false, it will be created regardless.

But what is more important to note is the security group that is going to be attached to the instance.

 EC2Instance: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap 
        - MapEc2Region
        - !Ref AWS::Region
        - HVM64
      KeyName: !Ref ParamKeyName
      AvailabilityZone: !Ref ParamAZName
      InstanceType: !FindInMap
        - MapEnvironmentType
        - !Ref ParamEnvironmentType
        - InstanceType
      Tags:
        - Key: Name
          Value: !Ref ParamEnvironmentType
      UserData:
        Fn::Base64: |
          #!/bin/bash -xe
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo '<html><h1>Hello From Your Restart Web Server!</h1></html>' > /var/www/html/index.html
      SecurityGroups: 
        - !If [ConditionForProdSG, !Ref ProdSecurityGroup, !Ref AWS::NoValue]

By using the !If function on the security group property, we are explicitly telling CloudFormation

  • To use the !If intrinsic function to check if ConditionForProdSG is true.

  • If ConditionForProdSG is true (meaning the environment is Prod), it references ProdSecurityGroup thus it will be attached to the instance.

  • If ConditionForProdSG is false (meaning the environment is Dev), then no security group will be attached to the instance. This is due to the pseudo parameter AWS::NoValue.

    Note. You can adjust the template using another!If functionto reference the correct security group when the environment selected isDev

How AWS::NoValue works with Conditions

SecurityGroups: 
  - !If [ConditionForProdSG, !Ref ProdSecurityGroup, !Ref AWS::NoValue]

We have concluded that the pseudo parameter AWS::NoValue ensures a Resource is not created if ConditionForProdSG evaluates to false .

When you choose the Dev environment, a Dev security group will be created but because of the pseudo parameter AWS::NoValue, no security group will attached to the instance.

Summary

You can update the template to include Resources such as databases and load balancers with similar conditional logic.

This technique follows architectural best practices by separating environment-specific configurations thus resulting in a more cleaner, readable and flexible template.