Skip to content

Commit a388d70

Browse files
authored
fix(cfn-include): correctly parse YAML strings in short-form GetAtt (#10197)
We weren't recursively parsing the argument of the short-form `Fn::GetAtt` if the arguments to it where given in the string form; which meant, if they were quoted (which is legal in YAML), we would add the quote to the logical ID of the resource, which is obviously incorrect. Fixes #10177 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent b2cc277 commit a388d70

File tree

5 files changed

+39
-20
lines changed

5 files changed

+39
-20
lines changed

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

+22-16
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,29 @@ const shortForms: yaml_types.Schema.CustomTag[] = [
3737
makeTagForCfnIntrinsic('Ref', false),
3838
makeTagForCfnIntrinsic('Condition', false),
3939
makeTagForCfnIntrinsic('GetAtt', true, (_doc: yaml.Document, cstNode: yaml_cst.CST.Node): any => {
40-
// The position of the leftmost period and opening bracket tell us what syntax is being used
41-
// If no brackets are found, then the dot notation is being used; the leftmost dot separates the
42-
// logical ID from the attribute.
43-
//
44-
// If a bracket is found, then the list notation is being used; if present, the leftmost dot separates the
45-
// logical ID from the attribute.
46-
const firstDot = cstNode.toString().indexOf('.');
47-
const firstBracket = cstNode.toString().indexOf('[');
40+
const parsedArguments = parseYamlStrWithCfnTags(cstNode.toString().substring('!GetAtt'.length));
4841

49-
return {
50-
'Fn::GetAtt': firstDot !== -1 && firstBracket === -1
51-
? [
52-
cstNode.toString().substring('!GetAtt '.length, firstDot),
53-
parseYamlStrWithCfnTags((cstNode.toString().substring(firstDot + 1))),
54-
]
55-
: parseYamlStrWithCfnTags(cstNode.toString().substring('!GetAtt'.length)),
56-
};
42+
let value: any;
43+
if (typeof parsedArguments === 'string') {
44+
// if the arguments to !GetAtt are a string,
45+
// the part before the first '.' is the logical ID,
46+
// and the rest is the attribute name
47+
// (which can contain '.')
48+
const firstDot = parsedArguments.indexOf('.');
49+
if (firstDot === -1) {
50+
throw new Error(`Short-form Fn::GetAtt must contain a '.' in its string argument, got: '${parsedArguments}'`);
51+
}
52+
value = [
53+
parsedArguments.substring(0, firstDot),
54+
parsedArguments.substring(firstDot + 1), // the + 1 is to skip the actual '.'
55+
];
56+
} else {
57+
// this is the form where the arguments to Fn::GetAtt are already an array -
58+
// in this case, nothing more to do
59+
value = parsedArguments;
60+
}
61+
62+
return { 'Fn::GetAtt': value };
5763
}),
5864
);
5965

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,19 @@ describe('CDK Include', () => {
125125
}).toThrow(/Element referenced in Fn::Sub expression with logical ID: '' was not found in the template/);
126126
});
127127

128-
test('throws an error when a template supplies an invalid string to a number parameter', () => {
128+
test("throws an exception for a template with a non-number string passed to a property with type 'number'", () => {
129129
includeTestTemplate(stack, 'alphabetical-string-passed-to-number.json');
130130

131131
expect(() => {
132132
SynthUtils.synthesize(stack);
133133
}).toThrow(/"abc" should be a number/);
134134
});
135+
136+
test('throws an exception for a template with a short-form Fn::GetAtt whose string argument does not contain a dot', () => {
137+
expect(() => {
138+
includeTestTemplate(stack, 'short-form-get-att-no-dot.yaml');
139+
}).toThrow(/Short-form Fn::GetAtt must contain a '.' in its string argument, got: 'Bucket1Arn'/);
140+
});
135141
});
136142

137143
function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Resources:
2+
Bucket1:
3+
Type: AWS::S3::Bucket
4+
Bucket2:
5+
Type: AWS::S3::Bucket
6+
Metadata:
7+
Bucket1Name: !GetAtt Bucket1Arn

packages/@aws-cdk/cloudformation-include/test/test-templates/yaml/short-form-get-att.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ Resources:
1616
Type: AWS::S3::Bucket
1717
Properties:
1818
BucketName: !GetAtt Bucket0.Arn
19-
AccessControl: !GetAtt [ ELB, SourceSecurityGroup.GroupName ]
19+
AccessControl: !GetAtt [ELB, SourceSecurityGroup.GroupName]
2020
Bucket2:
2121
Type: AWS::S3::Bucket
2222
Properties:
2323
BucketName: !GetAtt [ Bucket1, Arn ]
24-
AccessControl: !GetAtt ELB.SourceSecurityGroup.GroupName
24+
AccessControl: !GetAtt 'ELB.SourceSecurityGroup.GroupName'

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ describe('CDK Include', () => {
254254
});
255255
});
256256

257-
// Note that this yaml template fails validation. It is unclear how to invoke !Transform.
258257
test('can ingest a template with the short form !Transform function', () => {
258+
// Note that this yaml template fails validation. It is unclear how to invoke !Transform.
259259
includeTestTemplate(stack, 'invalid/short-form-transform.yaml');
260260

261261
expect(stack).toMatchTemplate({

0 commit comments

Comments
 (0)