-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Description
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
- Create a cdk sample app with command
cdk init sample-app --language=typescript
. - 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(""),
});
- 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");
- build with
npm run build
and run cdk synth withnpx cdk synth -q
, the following content will be printed out during thecdk synth
$ npx cdk synth -q
${Token[AWS.Region.10]}CdkIssueWtackMyCloudFrontFunction6157953B
- when seeing this, check the generated CloudFormation Template in
cdk.out
and search forAWS::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
- 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