AWS Cloud Operations Blog

Introducing new language extensions in AWS CloudFormation

AWS CloudFormation, an Infrastructure as Code (IaC) service that lets you model, provision, and manage AWS and third-party resources, recently released a new language transform that enhances the core CloudFormation language. For our first release, these enhancements are new intrinsic functions for JSON string conversion (Fn::ToJsonString), length (Fn::Length), and support for intrinsic functions and pseudo-parameter references in update and deletion policies.

These new language extensions are the result of open discussions with the larger CloudFormation community via our Request For Comments (RFC) proposals for new language features at our Language Discussion Github repository. We want to collaborate with the community to better align features and incorporate early feedback into the development cycle to meet the community’s needs. We invite you to participate in new RFCs to help shape the future of the CloudFormation language.

In this post, I’ll dive deeper into what these new extensions mean for your authoring experience, as well as give a few examples of how to use them.

Prerequisites

To use these new language features, you must add AWS::LanguageExtensions to the transform section of your template.

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

If you have a list of transforms, then we recommend having AWS managed transforms at the end, and AWS::LanguageExtensions must be listed before AWS::Serverless.

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 
  - 'AWS::LanguageExtensions'
  - 'AWS::Serverless-2016-10-31'

This transform will cover all of the existing and future language extensions.

Fn::ToJsonString

Sometimes, resources require a fully-composed JSON string as an input.

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Resources:
  Dashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
        DashboardBody: "{\"start\":\"-PT6H\",\"periodOverride\":\"inherit\",\"widgets\":[{\"type\":\"text\",\"x\":0,\"y\":7,\"width\":3,\"height\":3,\"properties\":{\"markdown\":\"Hello world\"}}]}"

Although this does work and is currently fully-supported by CloudFormation, it’s not easy to read at-a-glance.

Fn::ToJsonString lets us significantly simplify this example and re-introduce readability:

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Parameters:
  MarkdownText: 
  Type: String
  Default: "# Hello world"

Resources:
  Dashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardBody: 
        Fn::ToJsonString:
          start: "-PT6H"
          periodOverride: inherit
          widgets:
            - type: text
              x: 0
              y: 7
              width: 3
              height: 3
              properties:
                markdown: !Ref MarkdownText

Fn::ToJsonString accepts either an array or an object, as well as intrinsic functions, and converts it into an escaped JSON string. The only intrinsic function not supported is !Ref AWS::NotificationARNs.

You can see more examples and the actual RFC at the CloudFormation GitHub RFC Repo

Fn::Length

The Fn::Length intrinsic function returns the length of a given list. Consider the following example:

Conditions:
  IsLengthThree: !Equals
    - 3
    - Fn::Length: [1, 2, 3]

In this scenario, Fn::Length will consider the immediately following list and return the value of three (the number of items in the list). This means that the !Equals function will evaluate to true. Finally, the condition IsLengthThree will evaluate to true in this template.

The Length intrinsic function also supports parameters of the type List<Number> and CommaDelimitedList, and it can be combined with other intrinsic functions, such as !Select or !Split.

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Parameters: 
  CIDRblocks: 
    Description: "Comma-delimited list of CIDR blocks"
    Type: CommaDelimitedList
    Default: "10.0.48.0/24, 10.0.112.0/24, 10.0.176.0/24"
Conditions:
  HasThreeSubnets: !Equals
    - Fn::Length: !Ref CIDRblocks
    - 3
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      #...removed for brevity...
      CidrBlock: !If
        - HasThreeSubnets
        - !Select 
          - 2
          - !Ref CIDRblocks
        - !Select
          - 0
          - !Ref CIDRblocks

This block of code selects either the last subnet or the first subnet in the parameter CIDRblocks. Before, you couldn’t bounds-check your lists using !Select, but with Fn::Length you can! Note that we didn’t have to !Split our CommaDelimitedList, the Fn::Length function did that for us.

There are some minor limitations to Fn::Length. It can currently only compute values that are known when Transforms are run, not at resource provisioning time. For example, this means that getting the length of a split Amazon Resource Names (ARNs) won’t work.

See more examples and the actual RFC at the CloudFormation GitHub RFC Repo.

Intrinsic support for DeletionPolicy and UpdateReplacePolicy

One of the previous limitations of Deletion and UpdateReplace policies was that they couldn’t dynamically resolve intrinsic functions or parameter references. This limitation is now removed with language extensions! You can resolve values from the Parameters, Mappings, and Conditions sections of your CloudFormation templates.

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Parameters:
  DeletionPolicyParameter:
    Type: String
    AllowedValues: ["Delete", "Retain", "Snapshot"]
    Default: "Delete"
Resources:
  DynamoTable:
    Type: AWS::DynamoDB::Table
    Properties:
        # ..removed for brevity..
    DeletionPolicy: !Ref DeletionPolicyParameter

A common use case that we’ve heard is having DeletionPolicy: Retain only in production. It’s now trivial to have this using Parameters and Mappings:

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Parameters:
  Environment:
    Type: String
    AllowedValues: ["Dev", "Test", "Production"]
    Default: "Dev"

Mappings:
  EnvironmentMappings
    DeletionPolicy:
      Dev: Delete
        Test: Delete
        Production: Retain

Resources:
  AuroraDB:
    Type: AWS::RDS::DBCluster
    Properties:
        # ..removed for brevity..
    DeletionPolicy: !FindInMap
                  - EnvironmentMappings
                  - DeletionPolicy
                  - !Ref Environment

Here is the same example, represented with Conditions instead:

---
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::LanguageExtensions'

Parameters:
  Environment:
    Type: String
    AllowedValues: ["Dev", "Test", "Production"]
    Default: "Dev"

Conditions:
  IsProd: !Equals
    - !Ref Environment
    - "Production"

Resources:
  AuroraDB:
    Type: AWS::RDS::DBCluster
    Properties:
        # ..removed for brevity..
    DeletionPolicy: !If
        - IsProd
        - "Retain"
        - "Delete"

Find all of our examples and the actual RFC at the CloudFormation GitHub RFC Repo.

Conclusion

In this post, we walked through the new CloudFormation language extensions transform, how to enable them in your templates, and how to engage in future language extensions via our open language discussion repository. We have more language extensions planned, and your feedback can help shape the future of the CloudFormation language. Leave us your feedback at our Language Discussion Github repository. We look forward to hearing from you!

About the Author:

Dan Blanco

Dan is a senior AWS Developer Advocate based in Atlanta for the AWS IaC team. When he’s not advocating for IaC tools, you can either find him in the kitchen whipping up something delicious or in the skies flying. Find him on twitter (@TheDanBlanco) or in the AWS Developers #cloudformation Slack channel.