AWS CloudFormation Templates – AWS CloudFormation Tutorial – Part 2

0
4557
AWS CloudFormation Templates
Image: AWS

In this session will discuss more about AWS CloudFormation Templates, here will be working with template basics and objects in detail. Will Cover below mentioned options:

  • Parameters
  • Mappings
  • Conditions
  • Pseudo Parameters

Next Post:

  • Resources and resources parameters
  • References
  • Intrinsic Functions
  • Outputs

Parameters

Parameters option enables us to pass values into our template at stack creation time. So in this way, we can create customized stacks for each deployment. For example, think of that we have a template that creates a web server farm and we use it frequently for our customers.  Because the load and cost can vary for every customer, every time we may use different EC2 instance types. So, we have to use a parameter that gets the EC2 instance type as a value.

As you see below, our paratemer for an EC2 instance type. “InstanceType” is the logical name of the parameter.

"Parameters" : {
   "InstanceType" : {
      "Type" : "String",
      "Default" : "t1.micro",
      "AllowedValues" : ["t1.micro", "m1.small", "m1.large"],
      "Description" : "Enter t1.micro, m1.small, or m1.large. Default is t1.micro."
    }
}

A parameter can have a default value and we can override it if we pass a value. Again in  our “InstanceType” parameter , we specify a “Default” value as “t1.micro”. And also in “AllowedValues” property we limit the values to only “t1.micro” , “m1.small” and “m1.large”.

There are three types can be used to declare parameters.

String:  Below there is a parameter with a type string. We can define the following constraints for string: MinLength, MaxLength, Default, AllowedValues, and AllowedPattern

"Parameters" : {
    "InstanceType" : {
      "Type" : "String",
      "Default" : "t1.micro",
      "AllowedValues" : ["t1.micro", "m1.small", "m1.large"],
      "Description" : "Enter t1.micro, m1.small, or m1.large. Default is t1.micro."
    }
}

Another parameter with a type string with the logical name “Pwd”.

"Parameters" : {
    "Pwd": {
      "NoEcho": "true",
      "Description" : "The user password",
      "Type": "String",
      "MinLength": "7",
      "MaxLength": "15",
      "AllowedPattern" : "[a-zA-Z0-9]*"
    }
}

Number: Number parameter can be integer or float and parameter values must  be surrounded by quotes( in templates it is used as string ). We can define the following constraints for number: MinValue, MaxValue, Default, and AllowedValues.

"Parameters" : {
    "SSHPort": {
      "Default": "22",
      "Description" : "SSH port to be used",
      "Type": "Number",
      "MinValue": "1150",
      "MaxValue": "65535"
    }

CommaDelimitedList: This is an array of literal strings separated by commas.

"Parameters" : {
  "SubnetIpBlocks": {
    "Description": "Subnet IP blocks for my VPC",
    "Type": "CommaDelimitedList",
      "Default": "192.168.0.0/24, 192.168.1.0/24, 192.168.2.0/24"
  }
}

Remember that for string and number types, we can define constraints (rules) to validate the value of the parameter when we pass while creating our stack.
Let’s explain all parameters properties:

Type:  String, Number or CommaDelimitedlist

Default: Default value for the parameter

NoEcho: If we select this value as TRUE , when we describe our stack with cli , the value will be seen as asteriks (*****). Should be used for passwords,keys etc.

AllowedValues: Allowed values as an array for the parameter as string constraint

AllowedPattern: Allowed pattern as regex for the parameter  as string constraint

MaxLength:  Allowed largest number of characters for the parameter as string  constraint

MinLength:  Allowed smallest number of characters for the parameter as string  constraint

Max Value: Allowed largest numeric value  for the parameter as number constraint

MinValue: Allowed smallest numeric value for the parameter  as number constraint

Description: Description of the parameter

ConstraintDescription: When we use a different value than the allowed values , this description will be displayed as error message.

Now we can see all of them in our sample.

"Parameters" : {
   "InstanceType" : {
      "Type" : "String",
      "Default" : "t1.micro",
      "AllowedValues" : ["t1.micro", "m1.small", "m1.large"],
      "Description" : "Enter t1.micro, m1.small, or m1.large. Default is t1.micro."
      "ConstraintDescription: "must be a valid EC2 instance type"
    },
   "Pwd": {
      "NoEcho": "true",
      "Description" : "The user password",
      "Type": "String",
      "MinLength": "7",
      "MaxLength": "15",
      "AllowedPattern" : "[a-zA-Z0-9]*"
    },
   "SubnetIpBlocks": {
    "Description": "Subnet IP blocks for my VPC",
    "Type": "CommaDelimitedList",
      "Default": "192.168.0.0/24, 192.168.1.0/24, 192.168.2.0/24"
    },
   "WebPort": {
      "Default": "8080",
      "Description" : "Web port to be used",
      "Type": "Number",
      "MinValue": "8080",
      "MaxValue": "8090"
    }
 }

Mappings

We use mapping for conditional parameter values. It matches a key to a value. As seen below, the mappings with logical name “RegionMap” matches the regions names to an AMI instance. If we use the “eu-west-1” region and use this map, the “AMI” key will have the value of ” ami-7fd4e10b”.

"Mappings" : {
  "RegionMap" : {
    "us-east-1" : {
      "AMI" : "ami-76f0061f"
    },
    "us-west-1" : {
      "AMI" : "ami-655a0a20"
    },
    "eu-west-1" : {
      "AMI" : "ami-7fd4e10b"
    },
    "ap-southeast-1" : {
      "AMI" : "ami-72621c20"
    }
  }
}

Another example: If we select “eu-west-1” region and “64” key, the value will be “ami-60f28c32”

 "Mappings" : {
     "RegionMap" : {
       "us-east-1" : { "32" : "ami-6411e20d", "64" : "ami-7a11e213"},
       "us-west-1" : { "32" : "ami-c9c7978c", "64" : "ami-cfc7978a"},
       "eu-west-1" : { "32" : "ami-37c2f643", "64" : "ami-31c2f645"},
       "ap-southeast-1" : { "32" : "ami-66f28c34", "64" : "ami-60f28c32"},
       "ap-northeast-1" : { "32" : "ami-9c03a89d", "64" : "ami-a003a8a1"}
     }
   }

Will explain later but to be clear, let’s examine the resource and the usage of mappings:

 "Resources" : {
     "myEC2Instance" : {
        "Type" : "AWS::EC2::Instance",
        "Properties" : {
           "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "32"]},
           "InstanceType" : "m1.small"
        }
     }
 }

As you see we create an EC2 instance as a resource and “ImageId” property will be selected by using our mapping “RegionMap” and as “32” key. So if the region is “eu-west-“” the “ImageId” will have the value of “ami-6411e20d”. It is clear now how to use the mappings.

Conditions

Conditions are used to define values for specific conditions. For example, you may get a value as a parameter and decide what to do then. Let’s see an example:

As you see, we create an resource of EC2 and get a parameter of “EnvType”. If the value is “prod” ( “Condition” : “Prod” ) , “ProdInstance” resource will be created as “InstanceType” m3.xlarge and will be created from the “AMI” in our “RegionMap” mapping.

"Parameters" : {
    "EnvType" : {
      "Description" : "Environment type.",
      "Default" : "test",
      "Type" : "String",
      "AllowedValues" : ["prod", "dev", "test"],
      "ConstraintDescription" : "must specify prod, dev, or test."
    }
  },
"Conditions" : {
    "Prod" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "prod"]},
    "Dev" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "dev"]}
    "Test" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "test"]}
  },
"Resources:" {
 "ProdInstance" : {
  "Type" : "AWS::EC2::Instance",
  "Condition" : "Prod",
  "Properties" : {
    "InstanceType" : "m3.xlarge",
    "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]}
  }
}

Another usage of conditions with IF function.

"Parameters" : {
    "EnvType" : {
      "Description" : "Environment type.",
      "Default" : "test",
      "Type" : "String",
      "AllowedValues" : ["prod", "dev", "test"],
      "ConstraintDescription" : "must specify prod, dev, or test."
    }
  },

We reference to “EnvType” parameters, if it’s value is “prod” , “Prod” will be “True”.

  "Conditions" : {
    "Prod" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "prod"]},
    "Dev" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "dev"]}
    "Test" : {"Fn::Equals" : [{"Ref" : "EnvType"}, "test"]}
  },

Our resource is an EC2 instance that will choose the “Image Id” from our mappings “RegionMap”. Also “InstanceType” will be selected as the condition with IF function. If the condition “Prod” is “True”, “InstanceType” will be “m3.xlarge”.

  "Resources" : {
    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
        "InstanceType" : {
           "Fn::If" : [
             "Prod", "m3.xlarge",
          {"Fn::If" : [
            "Dev", "m1.large",
          {"Fn::If" : [
            "Test","m1.small"
          ]}
        ]}
       ]}
      }
    },

There are condition functions we can use in our templates. to learn more about them, please refer to this link.

Pseudo Parameters

Pseudo Parameters are parameters that are predefined by AWS and we can use them to get values like the availability zones, our account ID etc.

Below you can find the Pseudo parameter list:

AWS::AccountId
Returns the AWS account ID of the account in which the stack is being created

AWS::NotificationARNs
Returns the list of notification Amazon Resource Names (ARNs) for the current stack.

AWS::NoValue
Used to remove the propertty of an resource. AS seen below , an RDS instnace is defined as a resource. In its properties there is an if condition says that if “UseDbSnapshot” returns “True” , use the “DBSnapshotname” as “DBSnapshotIdentifier” property . And if not, remove the “DBSnapshotIdentifier” property , it will have “NoValue”.

"MyDB" : {
  "Type" : "AWS::RDS::DBInstance",
  "Properties" : {
    "AllocatedStorage" : "5",
    "DBInstanceClass" : "db.m1.small",
    "Engine" : "MySQL",
    "EngineVersion" : "5.5",
    "MasterUsername" : { "Ref" : "DBUser" },
    "MasterUserPassword" : { "Ref" : "DBPassword" },
    "DBParameterGroupName" : { "Ref" : "MyRDSParamGroup" },
    "DBSnapshotIdentifier" : {
      "Fn::If" : [
        "UseDBSnapshot",
        {"Ref" : "DBSnapshotName"},
        {"Ref" : "AWS::NoValue"}
      ]
    }
  }
}

AWS::Region
Returns a string representing the AWS Region in which the encompassing resource is being created. For example, “ImageId” will use a mapping and match it using the region that the resource will be created.

"Mappings" : {
    "RegionMap" : {
      "us-east-1" : {
          "AMI" : "ami-76f0061f"
      },
      "us-west-1" : {
          "AMI" : "ami-655a0a20"
      },
      "eu-west-1" : {
          "AMI" : "ami-6411e20d"
      },
      "ap-southeast-1" : {
          "AMI" : "ami-72621c20"
      },
      "ap-northeast-1" : {
          "AMI" : "ami-8e08a38f"
      }
    }
  },
"Resources" : {
    "Ec2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]}
      }
    },

Here our resource “ImageId” will get a value from “RegionMap” and there it will use ” AWS:Region” pseudo parameter and get the value for “AMI” key.

Basically: Go to “Mappings”. In “RegionMap”, find the key “AWS:region.AMI” and get it’s value. For example, if our region is “eu-west-1” , “AWS:Region” will have the “eu-west-1” value and finally the “AMI” will have the “ami-6411e20d” value.

“ImageId”: “Mappings”.”Regionmap”.”eu-west-1″.”AMI”=”ami-6411e20d”

AWS::StackId
Returns the ID of the stack as specified with the aws cloudformation create-stack command.

AWS::StackName
Returns the name of the stack as specified with the aws cloudformation create-stack command.

Read More: aws-cloudformation-tutorial-part-3

NO COMMENTS