Append to a Static List in a YAML CloudFormation Template

When writing CloudFormation stack templates, I sometimes need to create a list combining things defined at runtime and static values.

Imagine you have a template that contains a mapping, which enumerates IAM roles by environment. You want to grant permission to these roles as well as one or more Lambda execution roles. Can you create a list comprised of the static values defined in your map with references to roles created as part of your stack?

The FindInMap intrinsic function returns a set when the mapped value is a list, such as in our example. The Join function creates a string composed of the elements in the set separated by a given value.

You may perform a join on a set returned from the FindInMap function, returning a string composed of the elements in the set delimited by comma. You can then join the comma delimited string with a list of values. This second list can include references to resources created in the template.

!Join
  - ","
    - - !Join [",", !FindInMap ["MyMap", "foo", "thing"]]
    - !Ref "Thinger"

The following shows a CloudFormation stack template using this technique juxtaposition to an instance of the provisioned resource..

AWS CloudFormation Append Value to List
You’re seeing a role definition in a CloudFormation stack template shown juxtaposition to an instance of the resource provisioned. The role’s definition includes a list of ARNs. The ARNs are a combination of a static list provided by a mapping, and an execution role for a Lambda. The provisioned role reflects the complete list.

Notice the provisioned resource is a superset of the two lists. The following is the complete template:

Description: Sample Stack
Parameters:
  Thinger:
    Type: "String"
    Default: "arn:aws:s3:::f2c9"
Mappings:
  MyMap:
    foo:
      thing:
        - "arn:aws:s3:::0b50"
        - "arn:aws:s3:::e256"
        - "arn:aws:s3:::4159"
      thang:
        - "arn:aws:s3:::8199"
        - "arn:aws:s3:::d9f1"
        - "arn:aws:s3:::bc2b"
    bar:
      thing:
        - "arn:aws:s3:::bd69"
        - "arn:aws:s3:::eb00"
        - "arn:aws:s3:::0f55"
      thang:
        - "arn:aws:s3:::5ebc"
        - "arn:aws:s3:::4ccb"
        - "arn:aws:s3:::85c2"
Resources:
  Something:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: ExecuteSubmitFilePolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Split
                  - ","
                  - !Join
                    - ","
                    - - !Join [",", !FindInMap ["MyMap", "foo", "thing"]]
                      - !Ref "Thinger"
Outputs:
  UnifiedList:
    Value: !Join
      - ","
      - - !Join [",", !FindInMap ["MyMap", "foo", "thing"]]
        - !Ref "Thinger"

The utility of this technique is debatable. That said, it’s a useful pattern for joining two sets in a CloudFormation stack template.

Generating a Uniquifier for Your Resources in CloudFormation

I don’t generally name CloudFormation resources explicitly. However, once in a while, I want to explicitly name a resource, and I want whatever this resource name is to be unique across stacks. This lets me deploy multiple instances of the stack without worrying about naming collision. Oh, and I don’t want the unique portion of the name to change each time I update the stack. This is important. If I use this technique on an S3 bucket (for example), I’d get a new bucket with each stack update, and I don’t want that.

One quick-and-dirty way to accomplish this is to leverage the stack id(entifier). Consider the CloudFormation template:

---
Outputs:
  MyBucket:
    Value: !Select [6, !Split [ "-", !Ref "AWS::StackId" ]]
    Export:
      Name: "MyBucket"
Resources:
  MyBucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: !Sub
        - "mybucket-${Uniquifier}"
        - Uniquifier: !Select [6, !Split [ "-", !Ref "AWS::StackId" ]]

I’m building the template with something like the following (run in bash):

aws cloudformation deploy \
--stack-name "KewlStackAdamGaveMe" \
--template-file "<full-path-to-template-file>" \
--capabilities CAPABILITY_IAM

You’ll end up with a stack and S3 bucket that looks like the following:

Deployed Cloudformation Stack

I’m using the last the last 12-characters of the stack id, but you can use the whole thing if you’d like to. Keep in mind the naming rules for S3 buckets. Either way, you get the gist of how I’m creating a unique name that stays unique across stack updates.