Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
roger-hujin opened this issue Apr 21, 2022 · 4 comments · Fixed by #30392 · 4 remaining pull requests
Closed

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

roger-hujin opened this issue Apr 21, 2022 · 4 comments · Fixed by #30392 · 4 remaining pull requests
Labels
@aws-cdk/aws-cloudfront Related to Amazon CloudFront bug This issue is a bug. effort/small Small work item – less than a day of effort p1

Comments

@roger-hujin
Copy link

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

@roger-hujin roger-hujin added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Apr 21, 2022
@github-actions github-actions bot added the @aws-cdk/aws-cloudfront Related to Amazon CloudFront label Apr 21, 2022
@peterwoodworth
Copy link
Contributor

This is an interesting bug, thank you very much for the deep dive and thorough explanation of what's happening here. It's much appreciated 🙂

In case you're unaware, you can override the properties generated by the CDK with escape hatches to ensure your function name stays consistent until we get this fixed up.

@peterwoodworth peterwoodworth added p1 effort/small Small work item – less than a day of effort and removed needs-triage This issue or PR still needs to be triaged. labels Apr 21, 2022
@roger-hujin
Copy link
Author

Thank you very much @peterwoodworth. I am aware of the escape hatches. Currently I can workaround the issue by specifying functionName in the props so that the auto-generated name will not be used. The shortcoming of this workaround is that the name I specified will not be regional specific because CloudFront function is not in the regional-scoped namespace. I guess that is the reason why the region name was added as the prefix in the first place in the construct, so with this workaround, it means if I deploy the same stack in another region again, it will fail due to this CloudFront function already existed. However, it is not a concern for the project I am working on at the moment.

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

Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

1 similar comment
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

mazyu36 pushed a commit to mazyu36/aws-cdk that referenced this issue Jun 22, 2024
### Issue # (if applicable)

Closes aws#20017 as well as aws#15523 and aws#28629 

### Reason for this change

Due to the way function names are generated using token strings with either single- or double-digit numbers, longer function names can be truncated differently, leading to inconsistency in generated CloudFormation templates.

### Description of changes

To ensure backwards compatibility, if names are longer than 64 characters and use region tokens, if the token uses a single-digit region number, it takes the first **31** characters + the last 32 characters; if the token uses a double-digit region number or otherwise, it takes the first **32** characters + the last 32 characters. This ensures it will always take the same first chunk of the actual function's name.

### Description of how you validated changes

A new unit test was added to verify the consistency of function names in the template.

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment