Hands-on AWS CloudFormation - Part 2. Into to Intrinsic functions
Tags: aws, cloud, cloudformation
Published: 2020-12-18 05:09:26

This is Part 2 of **Hands-on AWS CloudFormation** series, you can find Part 1 [here](https://dev.to/tiamatt/hands-on-aws-cloudformation-part-1-it-all-starts-here-5153).

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:

```YAML
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. 

```YAML
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**:

```YAML
      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] (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html) 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:

```YAML
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:

```YAML
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.
```YAML
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.

```YAML
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"](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). Find a target resource from the list, e.g. *EC2*, then search by resource types, e.g. [*AWS::EC2::Instance*](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html). Scroll down and look for "Return values" section. Under "Fn::GetAtt" you can find the list of all available attributes for selected resource type:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kdljsn22woktb03kgj77.png)

##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.
```YAML
!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*: 

```YAML
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.

```YAML
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:

```YAML
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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c5y9h9yrupwir5t7x4l3.jpg)

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)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-base64.html)
- **Fn::Cidr** - returns an array of CIDR address blocks [(details)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-cidr.html)
- **Fn::ImportValue** - returns the value of an output exported by another stack [(details)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html)
- **Fn::Sub** - substitutes variables in an input string with values that you specify [(details)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)
- **Fn::Transform** - specifies a macro to perform custom processing on part of a stack template [(details)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html)

#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.