Using built-in CloudFormation variables to generate unique resource names

Using AWS::StackId & other pseudo predefined cloudformation variables to generate unique resource names within a CloudFormation Template

Table Of Contents

Today I Explained

When working within AWS, eventually you will encounter a service that needs unique names, and typically you learn this by an error message that sounds similar to:

An error occurred: ServiceLogs - /aws/lambda/myservicesloggroup already exists.

As the message is hinting at, the resource you are attempting to create must have a unique name/identifier, and you are attempting to create it with a name/identifier that is already in-use. This usually leads to the idea of just having a random suffix (or prefix) within your resource name to avoid the name clash, or phrased in another way:

  • How can I add a random string in a CloudFormation template?
  • Is there a way to create some kind of random value in a CloudFormation template?
  • How to add random suffix values to resource names in CloudFormation?
  • Is there a way to randomly parameterize CloudFormation resource names?
  • How to set semi-random names for resoruces using CloudFormation?

One of the built-in solutions within CloudFormation to this problem is the pseudo predefined cloudformation variables, specifically the AWS::StackId parameter, as it allows us to source a Uuid like 201c12d0-0721-11ee-b056-02bec084ce62 from the ARN: arn:aws:cloudformation:ca-central-1:123456789012:stack/aws-cfn-unique-resource-names/201c12d0-0721-11ee-b056-02bec084ce62.

Using something like the AWS::StackId, it then becomes possible to create a name like Example-02bec084ce62 using something like the following:

  MyResource:
    Type: AWS::MyService::MyResource
    Properties:
      Name:
        !Join [
          "-",
          [
            "Example",
            !Select [4, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]]],
          ],
        ]

Or to summarize the above in a programming language, we are doing something like this:

stackid = "arn:aws:cloudformation:ca-central-1:123456789012:stack/aws-cfn-unique-resource-names/201c12d0-0721-11ee-b056-02bec084ce62"

uuid = stackid.split("/")[2]          # Split on `/` to isolate the Uuid, which is the 3rd element in the array
random = uuid.split("-")[-1]          # Split the uuid into partitions, grab the last element
name = "-".join(["Example", random])  # Combine the elements into a "-" delimited string

This isn’t the only option for generating a “pseudo” unique name using the pseudo predefined cloudformation variables. Depending on the level of uniqueness you need, it may make sense to leverage some of the other available variables. You can see an example below of these values:

Name Value
AWSAccountID 123456789012
AWSAccountRegion ca-central-1
StackId arn:aws:cloudformation:ca-central-1:123456789012:stack/aws-cfn-unique-resource-names/201c12d0-0721-11ee-b056-02bec084ce62
StackName aws-cfn-unique-resource-names
Uuid 201c12d0-0721-11ee-b056-02bec084ce62
UuidPartition1 201c12d0
UuidPartition2 0721
UuidPartition3 11ee
UuidPartition4 b056
UuidPartition5 02bec084ce62
GeneratedName Example-02bec084ce62

Notes on Uniqueness

  • Sometimes you only need regional uniqueness, that is to say, your name is unique within an AWS Region (e.g. us-east-1). In which case, the name of the CloudFormation stack is already guaranteed to be unique within your AWS Region (AWS::StackName).
  • The combination of the AWS Account ID, AWS Region & StackName can often yield a likely globally unique resource name for resources like S3 Buckets. Although at this length, you’ll need to be wary of the length limits for S3 buckets (63 characters).

Finally, if you think the uniqueness offered by the predefined pseudo variables isn’t appropriate, you can either consider passing in a unique identifier to your CloudFormation stacks as a parameter:

Parameters:
  Identifier:
    Description: A random identifier 
    Type: String

or evaluate the use of AWS CloudFormation template macros which behind-the-scenes allow you to invoke Lambda functions that would be responsible for creating your unique values at runtime. This can be accomplished using roughly something like this:

Resources:
  TransformFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.9
      Handler: index.handler
      MemorySize: 128
      Timeout: 3
      InlineCode: |
        import random
        def handler(event, context):
            response = {
                'requestId': event['requestId'],
                'status': 'success'
            }
            try:
                param_min = int(event['params']['min'])
                param_max = int(event['params']['max'])
                response['fragment'] = random.randint(param_min, param_max - 1)
            except Exception as e:
                response['status'] = 'failure'
                response['errorMessage'] = str(e)
            return response        

  Transform:
    Type: AWS::CloudFormation::Macro
    Properties:
      Name: RandomInt
      Description: Provides a random integer within the range requested.
      FunctionName: !GetAtt TransformFunction.Arn

With the usage for creating a uniquely named bucket soemthing like this:

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      Tags:
        - Key: RandomInt
          Value:
            'Fn::Transform':
              - Name: RandomInt
                Parameters:
                  min: 100000000
                  max: 200000000

An AWS S3 bucket with a tag named RandomInt, which has a random value of 177238673