Skip to content

(cloudfront): (cloudfront function generated name is not deterministic and will change between synthesis) #20017

@roger-hujin

Description

@roger-hujin

Describe the bug

When using cloudfront.Function without specifying the function name, the construct cloudfront.Function will generate a name automatically. However this generated function name will change from time to time between synthesis due to the name truncated to a fixed length with a region token prefix in it. This change of name caused deployment failure when deploying onto an existing stack.

For example, with below code in a stack named CdkIssueWithALongNameStack

    new cloudfront.Function(this, "MyCloudFrontFunction", {
      code: cloudfront.FunctionCode.fromInline(""),
    });

Running cdk synth multiple times, It can be observed that during some runs, in the synthesised CloudFormation template, we can find below. Please note the function name is CdkIssueWtackMyCloudFrontFunction6157953B.

  "MyCloudFrontFunction18D9B9FD": {
   "Type": "AWS::CloudFront::Function",
   "Properties": {
    "Name": {
     "Fn::Join": [
      "",
      [
       {
        "Ref": "AWS::Region"
       },
       "CdkIssueWtackMyCloudFrontFunction6157953B"
      ]
     ]
    },

However, some other runs, the name will change to CdkIssueWitackMyCloudFrontFunction6157953B. Note the additional i after CdkIssueW.

This name change of the cloudfront function will cause failure of deployment on an existing stack as CDK will report error like:

Resource of type ‘AWS::CloudFront::Function’ with identifier ‘CdkIssueWtackMyCloudFrontFunction6157953B’ was not found.

Expected Behavior

The automatically generated function name should remain the same during multiple cdk synth.

Current Behavior

The name changes due to the following reason:

The following code snippet is how cloudfront.Function generate its function name if the name is not specified in the props.

  private generateName(): string {
    const name = Stack.of(this).region + Names.uniqueId(this);
    if (name.length > 64) {
      return name.substring(0, 32) + name.substring(name.length - 32);
    }
    return name;
  }
}

private generateName(): string {

it added Stack.of(this).region in front of the unique id of this construct and then took the first 32 chars and then last 32 chars if the string is longer than 64 chars, and concatenated these two parts together to form the name of the function. However, the region added in front of the name is still a token at this time, so the name string became:

${Token[AWS.Region.7]}CdkIssueWithALongNameStackMyCloudFrontFunction6157953B

This string is then sent through name.substring(0, 32) + name.substring(name.length - 32); and resulted in two parts:

${Token[AWS.Region.7]}CdkIssueWi and tackMyCloudFrontFunction6157953B and then they are concatenated together to make the name ${Token[AWS.Region.7]}CdkIssueWitackMyCloudFrontFunction6157953B, which eventually became the name in the CloudFormation template.

However, the region token string could be different every time cdk synth runs, sometimes, the region token will have a two digits value like 11 instead of 7, then the name above will be like:

${Token[AWS.Region.11]}CdkIssueWithALongNameStackMyCloudFrontFunction6157953B, which run through name.substring(0, 32) + name.substring(name.length - 32); and resulted in two parts:

${Token[AWS.Region.11]}CdkIssueW and tackMyCloudFrontFunction6157953B and then they are concatenated together make the name ${Token[AWS.Region.11]}CdkIssueWtackMyCloudFrontFunction6157953B. This name is different from the name when the number in region token is one digit, and missed that i.

Since the number in the region token is unpredictable so after a few rounds of cdk synth command, the name will change between those two results.

Reproduction Steps

  1. Create a cdk sample app with command cdk init sample-app --language=typescript.
  2. Add below into lib/<app name>-stack.ts
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";

...
// in the constructor, add below at the end

    new cloudfront.Function(this, "MyCloudFrontFunction", {
      code: cloudfront.FunctionCode.fromInline(""),
    });
  1. add below into bin/<app name>.ts. Make sure the stack name is long enough
#!/usr/bin/env node
import { Construct } from "constructs";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import { Aspects } from "aws-cdk-lib";

class TestAspect implements cdk.IAspect {
  public visit(construct: Construct) {
    if (construct instanceof cloudfront.CfnFunction) {
      console.log(construct.name);
    }
  }
}

const app = new cdk.App();
Aspects.of(app).add(new TestAspect());
new CdkissueStack(app, "CdkIssueWithALongNameStack");
  1. build with npm run build and run cdk synth with npx cdk synth -q, the following content will be printed out during the cdk synth
$ npx cdk synth -q
${Token[AWS.Region.10]}CdkIssueWtackMyCloudFrontFunction6157953B
  1. when seeing this, check the generated CloudFormation Template in cdk.out and search for AWS::CloudFront::Function resource and there should be something like below:
  "MyCloudFrontFunction18D9B9FD": {
   "Type": "AWS::CloudFront::Function",
   "Properties": {
    "Name": {
     "Fn::Join": [
      "",
      [
       {
        "Ref": "AWS::Region"
       },
       "CdkIssueWtackMyCloudFrontFunction6157953B"
      ]
     ]
    },

Write down the name

  1. run npx cdk synth -q multiple times until you can see the number in the region token is one digit
$ npx cdk synth -q
${Token[AWS.Region.8]}CdkIssueWitackMyCloudFrontFunction6157953B

then check the generated CloudFormation template in cdk.out, and search for AWS::CloudFront::Function resource and you can see the name of the resource became CdkIssueWitackMyCloudFrontFunction6157953B with an additional i.

Possible Solution

The quoted code snippet below are from cloudfront.Function construct, instead of adding the region token into the name string first, it could perform the name truncating and concatenation without adding the region token in front of it, and then add the region as prefix after to make the new name. So the concatenated name will be stable and won't change due to the different length of the region token (the digits of the number in it)

Below is one possible fix and it might need further validation:

  private generateName(): string {
    // currently the longest region name is ap-southeast-2 which is 14 chars
    const maxLengthOfRegionName = 14;
    // CloudFront Function has maximum 64 chars limit on function name
    const maxLengthOfName = 64 -  maxLengthOfRegionName;
    const longName = Names.uniqueId(this);
    if (longName.length > maxLengthOfName)) {
      const partLength = Math.floor(maxLengthOfName/2)
      return Stack.of(this).region  + longName.substring(0, partLength) + longName.substring(name.length - partLength);
    }
    return Stack.of(this).region  + longName;
  }

Current code in cloudfront.Function:

  private generateName(): string {
    const name = Stack.of(this).region + Names.uniqueId(this);
    if (name.length > 64) {
      return name.substring(0, 32) + name.substring(name.length - 32);
    }
    return name;
  }
}

private generateName(): string {

Additional Information/Context

No response

CDK CLI Version

2.20.0 (build 738ef49)

Framework Version

No response

Node.js Version

v14.18.2

OS

macOS Monterey 12.2.1

Language

Typescript

Language Version

Typescript(3.9.7)

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    @aws-cdk/aws-cloudfrontRelated to Amazon CloudFrontbugThis issue is a bug.effort/smallSmall work item – less than a day of effortp1

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions