Skip to content

Commit 8b20c11

Browse files
authored
feat(s3-deployment): support Fn::Select in renderData() (#27237)
Closes #25504 The reason for this change is to support more complex Cloudformation references used within `Source.data` in `aws-s3-deployment`. The objects today only support `Ref` or `Fn::GetAtt` Cfn references, which is limiting when it comes to attempting to manipulate Cfn references at deploy-time, such as via `Fn::Split` or `Fn::Select`. Many AWS CDK functions return tokens that must be evaluated using these complex Cfn functions (see [ApplicationTargetGroup's firstLoadBalancerFullName attribute](https://github.com/aws/aws-cdk/blob/3edd2400bc0c8a86366a29d3a7eef1ef4fa5e016/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts#L438)), but they are incompatible with `renderData`! This is a blocking issue for CDK projects which rely on generating S3 objects using `BucketDeployment`, wherein the rendered data is generated from native functions which utilize Cfn functions under-the-hood to dynamically construct values at deploy-time. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent bed9b8d commit 8b20c11

File tree

13 files changed

+145
-36
lines changed

13 files changed

+145
-36
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/TestBucketDeploymentContent.assets.json

Lines changed: 19 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/TestBucketDeploymentContent.template.json

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"S3Bucket": {
2121
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
2222
},
23-
"S3Key": "68b22621fff135f9e3f225bad7ff80fdf2f45c3d9910af601206a0d9b279933a.zip"
23+
"S3Key": "e2277687077a2abf9ae1af1cc9565e6715e2ebb62f79ec53aa75a1af9298f642.zip"
2424
},
2525
"Description": "/opt/awscli/aws"
2626
}
@@ -44,6 +44,9 @@
4444
{
4545
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
4646
},
47+
{
48+
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
49+
},
4750
{
4851
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
4952
}
@@ -52,7 +55,8 @@
5255
"d09271be89b6cb0398f793b40c1531fd9b076aa92ba80b5e436914b1808fe18d.zip",
5356
"0f14dedeaf4386031c978375cbda0f65d7b52b29452cabb8873eb8f0d0fa936b.zip",
5457
"27eff729291aea0a2b33592996b9a764c233dc3387bd9cfd58c6f064073f177f.zip",
55-
"939a4ab8b51f1a1cccb59d97f04c318ad41c1d404e666c158ca2810894bc5f5f.zip"
58+
"939a4ab8b51f1a1cccb59d97f04c318ad41c1d404e666c158ca2810894bc5f5f.zip",
59+
"4050143e044258715dc77e0d45e38dfaaba94cba5339fe6f76f1c067fa45020d.zip"
5660
],
5761
"SourceMarkers": [
5862
{},
@@ -76,6 +80,24 @@
7680
"WebsiteURL"
7781
]
7882
}
83+
},
84+
{
85+
"<<marker:0xbaba:0>>": {
86+
"Fn::Select": [
87+
2,
88+
{
89+
"Fn::Split": [
90+
"/",
91+
{
92+
"Fn::GetAtt": [
93+
"Bucket83908E77",
94+
"WebsiteURL"
95+
]
96+
}
97+
]
98+
}
99+
]
100+
}
79101
}
80102
],
81103
"DestinationBucketName": {
@@ -222,12 +244,6 @@
222244
},
223245
"S3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip"
224246
},
225-
"Role": {
226-
"Fn::GetAtt": [
227-
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
228-
"Arn"
229-
]
230-
},
231247
"Environment": {
232248
"Variables": {
233249
"AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
@@ -239,6 +255,12 @@
239255
"Ref": "DeployMeHereAwsCliLayerDDC2FE7D"
240256
}
241257
],
258+
"Role": {
259+
"Fn::GetAtt": [
260+
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
261+
"Arn"
262+
]
263+
},
242264
"Runtime": "python3.9",
243265
"Timeout": 900
244266
},

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/asset.4050143e044258715dc77e0d45e38dfaaba94cba5339fe6f76f1c067fa45020d/my-json/config2.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/cdk.out

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/integ.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/integtestbucketdeploymentdataDefaultTestDeployAssert6FF3075D.assets.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/manifest.json

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.js.snapshot/tree.json

Lines changed: 35 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/aws-s3-deployment/test/integ.bucket-deployment-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const file1 = Source.data('file1.txt', 'boom');
1111
const file2 = Source.data('path/to/file2.txt', `bam! ${bucket.bucketName}`);
1212
const file3 = Source.jsonData('my-json/config.json', { website_url: bucket.bucketWebsiteUrl });
1313
const file4 = Source.yamlData('my-yaml/config.yaml', { website_url: bucket.bucketWebsiteUrl });
14+
const file5 = Source.jsonData('my-json/config2.json', { bucket_domain_name: bucket.bucketWebsiteDomainName });
1415

1516
const deployment = new BucketDeployment(stack, 'DeployMeHere', {
1617
destinationBucket: bucket,
@@ -20,6 +21,7 @@ const deployment = new BucketDeployment(stack, 'DeployMeHere', {
2021
});
2122
deployment.addSource(file3);
2223
deployment.addSource(file4);
24+
deployment.addSource(file5);
2325

2426
new CfnOutput(stack, 'BucketName', { value: bucket.bucketName });
2527

packages/aws-cdk-lib/aws-s3-deployment/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,19 +330,23 @@ new s3deploy.BucketDeployment(this, 'DeployMeWithEfsStorage', {
330330
## Data with deploy-time values
331331

332332
The content passed to `Source.data()`, `Source.jsonData()`, or `Source.yamlData()` can include
333-
references that will get resolved only during deployment.
333+
references that will get resolved only during deployment. Only a subset of CloudFormation functions
334+
are supported however, namely: Ref, Fn::GetAtt, Fn::Join, and Fn::Select (Fn::Split may be nested under Fn::Select).
334335

335336
For example:
336337

337338
```ts
338339
import * as sns from 'aws-cdk-lib/aws-sns';
340+
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
339341

340342
declare const destinationBucket: s3.Bucket;
341343
declare const topic: sns.Topic;
344+
declare const tg: elbv2.ApplicationTargetGroup;
342345

343346
const appConfig = {
344347
topic_arn: topic.topicArn,
345348
base_url: 'https://my-endpoint',
349+
lb_name: tg.firstLoadBalancerFullName,
346350
};
347351

348352
new s3deploy.BucketDeployment(this, 'BucketDeployment', {

packages/aws-cdk-lib/aws-s3-deployment/lib/render-data.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,18 @@ export function renderData(scope: Construct, data: string): Content {
5252
throw new Error(`Unexpected "Fn::Join" part, expecting string or object but got ${typeof (part)}`);
5353
}
5454

55-
} else if (obj.Ref || obj['Fn::GetAtt']) {
55+
} else if (obj.Ref || obj['Fn::GetAtt'] || obj['Fn::Select']) {
5656
addMarker(obj);
5757
} else {
5858
throw new Error('Unexpected: Expecting `resolve()` to return "Fn::Join", "Ref" or "Fn::GetAtt"');
5959
}
6060

61-
function addMarker(part: Ref | GetAtt) {
61+
function addMarker(part: Ref | GetAtt | FnSelect) {
6262
const keys = Object.keys(part);
63-
if (keys.length !== 1 || (keys[0] != 'Ref' && keys[0] != 'Fn::GetAtt')) {
64-
throw new Error(`Invalid CloudFormation reference. "Ref" or "Fn::GetAtt". Got ${JSON.stringify(part)}`);
63+
const acceptedCfnFns = ['Ref', 'Fn::GetAtt', 'Fn::Select'];
64+
if (keys.length !== 1 || !acceptedCfnFns.includes(keys[0])) {
65+
const stringifiedAcceptedCfnFns = acceptedCfnFns.map((fn) => `"${fn}"`).join(' or ');
66+
throw new Error(`Invalid CloudFormation reference. Key must start with any of ${stringifiedAcceptedCfnFns}. Got ${JSON.stringify(part)}`);
6567
}
6668

6769
const marker = `<<marker:0xbaba:${markerIndex++}>>`;
@@ -73,6 +75,8 @@ export function renderData(scope: Construct, data: string): Content {
7375
}
7476

7577
type FnJoin = [string, FnJoinPart[]];
76-
type FnJoinPart = string | Ref | GetAtt;
78+
type FnJoinPart = string | Ref | GetAtt | FnSelect;
7779
type Ref = { Ref: string };
78-
type GetAtt = { 'Fn::GetAtt': [string, string] };
80+
type GetAtt = { 'Fn::GetAtt': [string, string] };
81+
type FnSplit = { 'Fn::Split': [string, string | Ref] };
82+
type FnSelect = { 'Fn::Select': [number, string[] | FnSplit] };

0 commit comments

Comments
 (0)