Skip to content

Commit 2a48495

Browse files
authored
feat(cfn-include): add support for the Fn::Sub function (#9275)
---- Closes #9228 *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 239e686 commit 2a48495

21 files changed

+415
-55
lines changed

packages/@aws-cdk/cloudformation-include/README.md

-44
Original file line numberDiff line numberDiff line change
@@ -218,47 +218,3 @@ bucketReadRole.addToPolicy(new iam.PolicyStatement({
218218
resources: [bucket.attrArn],
219219
}));
220220
```
221-
222-
## Known limitations
223-
224-
This module is still in its early, experimental stage,
225-
and so does not implement all features of CloudFormation templates.
226-
All items unchecked below are currently not supported.
227-
228-
### Ability to retrieve CloudFormation objects from the template:
229-
230-
- [x] Resources
231-
- [x] Parameters
232-
- [x] Conditions
233-
- [x] Outputs
234-
235-
### [Resource attributes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-attribute-reference.html):
236-
237-
- [x] Properties
238-
- [x] Condition
239-
- [x] DependsOn
240-
- [x] CreationPolicy
241-
- [x] UpdatePolicy
242-
- [x] UpdateReplacePolicy
243-
- [x] DeletionPolicy
244-
- [x] Metadata
245-
246-
### [CloudFormation functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html):
247-
248-
- [x] Ref
249-
- [x] Fn::GetAtt
250-
- [x] Fn::Join
251-
- [x] Fn::If
252-
- [x] Fn::And
253-
- [x] Fn::Equals
254-
- [x] Fn::Not
255-
- [x] Fn::Or
256-
- [x] Fn::Base64
257-
- [x] Fn::Cidr
258-
- [x] Fn::FindInMap
259-
- [x] Fn::GetAZs
260-
- [x] Fn::ImportValue
261-
- [x] Fn::Select
262-
- [x] Fn::Split
263-
- [ ] Fn::Sub
264-
- [x] Fn::Transform

packages/@aws-cdk/cloudformation-include/lib/file-utils.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ function makeTagForCfnIntrinsic(
3131
}
3232

3333
const shortForms: yaml_types.Schema.CustomTag[] = [
34-
'Base64', 'Cidr', 'FindInMap', 'GetAZs', 'ImportValue', 'Join',
34+
'Base64', 'Cidr', 'FindInMap', 'GetAZs', 'ImportValue', 'Join', 'Sub',
3535
'Select', 'Split', 'Transform', 'And', 'Equals', 'If', 'Not', 'Or',
3636
].map(name => makeTagForCfnIntrinsic(name)).concat(
37-
// ToDo: special logic for ImportValue will be needed when support for Fn::Sub is added. See
38-
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html
3937
makeTagForCfnIntrinsic('Ref', false),
4038
makeTagForCfnIntrinsic('GetAtt', true, (_doc: yaml.Document, cstNode: yaml_cst.CST.Node): any => {
4139
// The position of the leftmost period and opening bracket tell us what syntax is being used

packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ describe('CDK Include', () => {
100100
includeTestTemplate(stack, 'output-referencing-nonexistant-condition.json');
101101
}).toThrow(/Output with name 'SomeOutput' refers to a Condition with name 'NonexistantCondition' which was not found in this template/);
102102
});
103+
104+
test("throws a validation exception when Fn::Sub in string form uses a key that isn't in the template", () => {
105+
expect(() => {
106+
includeTestTemplate(stack, 'fn-sub-key-not-in-template-string.json');
107+
}).toThrow(/Element referenced in Fn::Sub expression with logical ID: 'AFakeResource' was not found in the template/);
108+
});
109+
110+
test('throws a validation exception when Fn::Sub has an empty ${} reference', () => {
111+
expect(() => {
112+
includeTestTemplate(stack, 'fn-sub-${}-only.json');
113+
}).toThrow(/Element referenced in Fn::Sub expression with logical ID: '' was not found in the template/);
114+
});
103115
});
104116

105117
function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "Custom::ManyStrings",
5+
"Properties": {
6+
"SymbolsOnly": {
7+
"DollarSign": {
8+
"Fn::Sub": "$"
9+
},
10+
"OpeningBrace": {
11+
"Fn::Sub": "{"
12+
},
13+
"ClosingBrace": {
14+
"Fn::Sub": "}"
15+
},
16+
"DollarOpeningBrace": {
17+
"Fn::Sub": "${"
18+
},
19+
"DollarClosingBrace": {
20+
"Fn::Sub": "$}"
21+
},
22+
"OpeningBraceDollar": {
23+
"Fn::Sub": "{$"
24+
},
25+
"ClosingBraceDollar": {
26+
"Fn::Sub": "}$"
27+
}
28+
},
29+
"SymbolsAndResources": {
30+
"DollarSign": {
31+
"Fn::Sub": "DoesNotExist$DoesNotExist"
32+
},
33+
"OpeningBrace": {
34+
"Fn::Sub": "DoesNotExist{DoesNotExist"
35+
},
36+
"ClosingBrace": {
37+
"Fn::Sub": "DoesNotExist}DoesNotExist"
38+
},
39+
"DollarOpeningBrace": {
40+
"Fn::Sub": "DoesNotExist${DoesNotExist"
41+
},
42+
"DollarClosingBrace": {
43+
"Fn::Sub": "DoesNotExist$}DoesNotExist"
44+
},
45+
"OpeningBraceDollar": {
46+
"Fn::Sub": "DoesNotExist{$DoesNotExist"
47+
},
48+
"ClosingBraceDollar": {
49+
"Fn::Sub": "DoesNotExist}$DoesNotExist"
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": { "Fn::Sub": "some-bucket${!AWS::AccountId}7896${ ! DoesNotExist}${!Immediate}234" }
7+
}
8+
}
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": {
7+
"Fn::Sub": "${ELB.SourceSecurityGroup.GroupName}"
8+
}
9+
}
10+
},
11+
"ELB": {
12+
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
13+
"Properties": {
14+
"AvailabilityZones": [
15+
"us-east-1a"
16+
],
17+
"Listeners": [{
18+
"LoadBalancerPort": "80",
19+
"InstancePort": "80",
20+
"Protocol": "HTTP"
21+
}]
22+
}
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": "bucket"
7+
}
8+
},
9+
"AnotherBucket": {
10+
"Type": "AWS::S3::Bucket",
11+
"Properties": {
12+
"BucketName": { "Fn::Sub": "${Bucket}-${!Bucket}-${Bucket.DomainName}" }
13+
}
14+
}
15+
}
16+
}

packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-parsing-edges.json

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": {
7+
"Fn::Sub": [
8+
"${AnotherBucket.DomainName}",
9+
{
10+
"AnotherBucket": "whatever"
11+
}
12+
]
13+
}
14+
}
15+
},
16+
"AnotherBucket": {
17+
"Type": "AWS::S3::Bucket"
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": {
7+
"Fn::Sub": [
8+
"${AnotherBucket}",
9+
{
10+
"AnotherBucket": { "Ref" : "AnotherBucket" }
11+
}
12+
]
13+
}
14+
}
15+
},
16+
"AnotherBucket": {
17+
"Type": "AWS::S3::Bucket"
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": "bucket"
7+
}
8+
},
9+
"AnotherBucket": {
10+
"Type": "AWS::S3::Bucket",
11+
"Properties": {
12+
"BucketName": { "Fn::Sub": "1-${AWS::Region}-foo-${Bucket}-${!Literal}-${Bucket.DomainName}-${AWS::Region}" }
13+
}
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"BucketName": {
7+
"Fn::Sub": "${}"
8+
}
9+
}
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Resources": {
3+
"Bucket": {
4+
"Type": "AWS::S3::Bucket",
5+
"Properties": {
6+
"AccessControl": { "Fn::Sub": "${AFakeResource}" }
7+
}
8+
}
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Resources:
2+
Bucket:
3+
Type: AWS::S3::Bucket
4+
Properties:
5+
BucketName:
6+
!ImportValue
7+
!Sub ${AWS::Region}

packages/@aws-cdk/cloudformation-include/test/test-templates/yaml/long-form-vpc.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,11 @@ Resources:
4242
Location: location,
4343
AnotherParameter:
4444
Fn::Base64: AnotherValue
45+
AccessControl:
46+
Fn::ImportValue:
47+
Fn::Sub:
48+
- "${Region}-foo-${!Immediate}-foo-${Vpc}-${Vpc.Id}-${Name}"
49+
- Name:
50+
Ref: Vpc
51+
Region:
52+
Fn::Base64: AWS::Region
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Resources:
2+
Bucket:
3+
Type: AWS::S3::Bucket
4+
Properties:
5+
BucketName:
6+
Fn::Sub: some-bucket${!AWS::AccountId}7896${ ! AWS::Region}1-1${!Immediate}234
7+
AnotherBucket:
8+
Type: AWS::S3::Bucket
9+
Properties:
10+
BucketName:
11+
!Sub 1-${AWS::Region}-foo-${Bucket}-${!Literal}-${Bucket.DomainName}-${AWS::Region}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Resources:
2+
Bucket:
3+
Type: AWS::S3::Bucket
4+
Properties:
5+
BucketName:
6+
!Sub
7+
- "${Region}-foo-${!Immediate}-foo-${AnotherBucket}-${AnotherBucket.DomainName}-${Name}"
8+
- Name:
9+
Ref: AnotherBucket
10+
Region:
11+
Fn::Base64: AWS::Region
12+
AnotherBucket:
13+
Type: AWS::S3::Bucket
14+
Properties:
15+
BucketName: another-bucket

0 commit comments

Comments
 (0)