Skip to content

Commit

Permalink
feat(s3-deployment): support Fn::Select in renderData() (#27237)
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
Justinon authored Oct 4, 2023
1 parent bed9b8d commit 8b20c11
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 36 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"S3Bucket": {
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
},
"S3Key": "68b22621fff135f9e3f225bad7ff80fdf2f45c3d9910af601206a0d9b279933a.zip"
"S3Key": "e2277687077a2abf9ae1af1cc9565e6715e2ebb62f79ec53aa75a1af9298f642.zip"
},
"Description": "/opt/awscli/aws"
}
Expand All @@ -44,6 +44,9 @@
{
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
},
{
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
},
{
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
}
Expand All @@ -52,7 +55,8 @@
"d09271be89b6cb0398f793b40c1531fd9b076aa92ba80b5e436914b1808fe18d.zip",
"0f14dedeaf4386031c978375cbda0f65d7b52b29452cabb8873eb8f0d0fa936b.zip",
"27eff729291aea0a2b33592996b9a764c233dc3387bd9cfd58c6f064073f177f.zip",
"939a4ab8b51f1a1cccb59d97f04c318ad41c1d404e666c158ca2810894bc5f5f.zip"
"939a4ab8b51f1a1cccb59d97f04c318ad41c1d404e666c158ca2810894bc5f5f.zip",
"4050143e044258715dc77e0d45e38dfaaba94cba5339fe6f76f1c067fa45020d.zip"
],
"SourceMarkers": [
{},
Expand All @@ -76,6 +80,24 @@
"WebsiteURL"
]
}
},
{
"<<marker:0xbaba:0>>": {
"Fn::Select": [
2,
{
"Fn::Split": [
"/",
{
"Fn::GetAtt": [
"Bucket83908E77",
"WebsiteURL"
]
}
]
}
]
}
}
],
"DestinationBucketName": {
Expand Down Expand Up @@ -222,12 +244,6 @@
},
"S3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip"
},
"Role": {
"Fn::GetAtt": [
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
"Arn"
]
},
"Environment": {
"Variables": {
"AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
Expand All @@ -239,6 +255,12 @@
"Ref": "DeployMeHereAwsCliLayerDDC2FE7D"
}
],
"Role": {
"Fn::GetAtt": [
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
"Arn"
]
},
"Runtime": "python3.9",
"Timeout": 900
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const file1 = Source.data('file1.txt', 'boom');
const file2 = Source.data('path/to/file2.txt', `bam! ${bucket.bucketName}`);
const file3 = Source.jsonData('my-json/config.json', { website_url: bucket.bucketWebsiteUrl });
const file4 = Source.yamlData('my-yaml/config.yaml', { website_url: bucket.bucketWebsiteUrl });
const file5 = Source.jsonData('my-json/config2.json', { bucket_domain_name: bucket.bucketWebsiteDomainName });

const deployment = new BucketDeployment(stack, 'DeployMeHere', {
destinationBucket: bucket,
Expand All @@ -20,6 +21,7 @@ const deployment = new BucketDeployment(stack, 'DeployMeHere', {
});
deployment.addSource(file3);
deployment.addSource(file4);
deployment.addSource(file5);

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

Expand Down
6 changes: 5 additions & 1 deletion packages/aws-cdk-lib/aws-s3-deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,19 +330,23 @@ new s3deploy.BucketDeployment(this, 'DeployMeWithEfsStorage', {
## Data with deploy-time values

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

For example:

```ts
import * as sns from 'aws-cdk-lib/aws-sns';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';

declare const destinationBucket: s3.Bucket;
declare const topic: sns.Topic;
declare const tg: elbv2.ApplicationTargetGroup;

const appConfig = {
topic_arn: topic.topicArn,
base_url: 'https://my-endpoint',
lb_name: tg.firstLoadBalancerFullName,
};

new s3deploy.BucketDeployment(this, 'BucketDeployment', {
Expand Down
16 changes: 10 additions & 6 deletions packages/aws-cdk-lib/aws-s3-deployment/lib/render-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ export function renderData(scope: Construct, data: string): Content {
throw new Error(`Unexpected "Fn::Join" part, expecting string or object but got ${typeof (part)}`);
}

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

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

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

type FnJoin = [string, FnJoinPart[]];
type FnJoinPart = string | Ref | GetAtt;
type FnJoinPart = string | Ref | GetAtt | FnSelect;
type Ref = { Ref: string };
type GetAtt = { 'Fn::GetAtt': [string, string] };
type GetAtt = { 'Fn::GetAtt': [string, string] };
type FnSplit = { 'Fn::Split': [string, string | Ref] };
type FnSelect = { 'Fn::Select': [number, string[] | FnSplit] };
Loading

0 comments on commit 8b20c11

Please sign in to comment.