Skip to content

Commit c38bf5a

Browse files
committed
feat(cfn-include): allow renaming the template elements logical IDs
To facilitate using CfnInclude for vending CloudFormation templates as CDK Constructs, add a new preserveLogicalIds parameter to CfnInclude that re-names all logical IDs of all elements (including references to them) with the standard CDK logical ID generation algorithm. Closes #9714
1 parent a1b5bf6 commit c38bf5a

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

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

+29
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,32 @@ role.addToPolicy(new iam.PolicyStatement({
289289
resources: [cfnBucket.attrArn],
290290
}));
291291
```
292+
293+
## Vending CloudFormation templates as Constructs
294+
295+
In many cases, there are existing CloudFormation templates that are not entire applications,
296+
but more like specialized fragments, implementing a particular pattern or best practice.
297+
If you have templates like that,
298+
you can use the `CfnInclude` class to vend them as a CDK Constructs:
299+
300+
```ts
301+
import * as path from 'path';
302+
303+
export class MyConstruct extends Construct {
304+
constructor(scope: Construct, id: string) {
305+
super(scope, id);
306+
307+
// include a template inside the Construct
308+
new cfn_inc.CfnInclude(this, 'MyConstruct', {
309+
templateFile: path.join(__dirname, 'my-template.json'),
310+
preserveLogicalIds: false, // <--- !!!
311+
});
312+
}
313+
}
314+
```
315+
316+
Notice the `preserveLogicalIds` parameter -
317+
it makes sure the logical IDs of all the included template elements are re-named using CDK's algorithm,
318+
guaranteeing they are unique within your application.
319+
Without that parameter passed,
320+
instantiating `MyConstruct` twice in the same Stack would result in duplicated logical IDs.

packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ export interface CfnIncludeProps {
1414
*/
1515
readonly templateFile: string;
1616

17+
/**
18+
* Whether the resources should have the same logical IDs in the resulting CDK template
19+
* as they did in the original CloudFormation template file.
20+
* If you're vending a Construct using an existing CloudFormation template,
21+
* make sure to pass this as `false`.
22+
*
23+
* **Note**: regardless of whether this option is true or false,
24+
* the {@link CfnInclude.getResource} and related methods always uses the original logical ID of the resource/element,
25+
* as specified in the template file.
26+
*
27+
* @default true
28+
*/
29+
readonly preserveLogicalIds?: boolean;
30+
1731
/**
1832
* Specifies the template files that define nested stacks that should be included.
1933
*
@@ -86,8 +100,7 @@ export class CfnInclude extends core.CfnElement {
86100
// read the template into a JS object
87101
this.template = futils.readYamlSync(props.templateFile);
88102

89-
// ToDo implement preserveLogicalIds=false
90-
this.preserveLogicalIds = true;
103+
this.preserveLogicalIds = props.preserveLogicalIds ?? true;
91104

92105
// check if all user specified parameter values exist in the template
93106
for (const logicalId of Object.keys(this.parametersToReplace)) {
@@ -326,7 +339,7 @@ export class CfnInclude extends core.CfnElement {
326339
mapping: cfnParser.parseValue(this.template.Mappings[mappingName]),
327340
});
328341
this.mappings[mappingName] = cfnMapping;
329-
cfnMapping.overrideLogicalId(mappingName);
342+
this.overrideLogicalIdIfNeeded(cfnMapping, mappingName);
330343
}
331344

332345
private createParameter(logicalId: string): void {
@@ -357,7 +370,7 @@ export class CfnInclude extends core.CfnElement {
357370
noEcho: expression.NoEcho,
358371
});
359372

360-
cfnParameter.overrideLogicalId(logicalId);
373+
this.overrideLogicalIdIfNeeded(cfnParameter, logicalId);
361374
this.parameters[logicalId] = cfnParameter;
362375
}
363376

@@ -384,7 +397,7 @@ export class CfnInclude extends core.CfnElement {
384397
assertions: ruleProperties.Assertions,
385398
});
386399
this.rules[ruleName] = rule;
387-
rule.overrideLogicalId(ruleName);
400+
this.overrideLogicalIdIfNeeded(rule, ruleName);
388401
}
389402

390403
private createOutput(logicalId: string, scope: core.Construct): void {
@@ -422,7 +435,7 @@ export class CfnInclude extends core.CfnElement {
422435
})(),
423436
});
424437

425-
cfnOutput.overrideLogicalId(logicalId);
438+
this.overrideLogicalIdIfNeeded(cfnOutput, logicalId);
426439
this.outputs[logicalId] = cfnOutput;
427440
}
428441

@@ -455,8 +468,7 @@ export class CfnInclude extends core.CfnElement {
455468
expression: cfnParser.parseValue(this.template.Conditions[conditionName]),
456469
});
457470

458-
// ToDo handle renaming of the logical IDs of the conditions
459-
cfnCondition.overrideLogicalId(conditionName);
471+
this.overrideLogicalIdIfNeeded(cfnCondition, conditionName);
460472
this.conditions[conditionName] = cfnCondition;
461473
return cfnCondition;
462474
}
@@ -533,11 +545,7 @@ export class CfnInclude extends core.CfnElement {
533545
}
534546
}
535547

536-
if (this.preserveLogicalIds) {
537-
// override the logical ID to match the original template
538-
l1Instance.overrideLogicalId(logicalId);
539-
}
540-
548+
this.overrideLogicalIdIfNeeded(l1Instance, logicalId);
541549
this.resources[logicalId] = l1Instance;
542550
return l1Instance;
543551
}
@@ -585,4 +593,10 @@ export class CfnInclude extends core.CfnElement {
585593
}
586594
return ret;
587595
}
596+
597+
private overrideLogicalIdIfNeeded(element: core.CfnElement, id: string): void {
598+
if (this.preserveLogicalIds) {
599+
element.overrideLogicalId(id);
600+
}
601+
}
588602
}

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('CDK Include', () => {
114114
);
115115
});
116116

117-
xtest('correctly changes the logical IDs, including references, if imported with preserveLogicalIds=false', () => {
117+
test('correctly changes the logical IDs, including references, if imported with preserveLogicalIds=false', () => {
118118
const cfnTemplate = includeTestTemplate(stack, 'bucket-with-encryption-key.json', {
119119
preserveLogicalIds: false,
120120
});
@@ -177,6 +177,11 @@ describe('CDK Include', () => {
177177
],
178178
},
179179
},
180+
"Metadata": {
181+
"Object1": "Location1",
182+
"KeyRef": { "Ref": "MyScopeKey7673692F" },
183+
"KeyArn": { "Fn::GetAtt": ["MyScopeKey7673692F", "Arn"] },
184+
},
180185
"DeletionPolicy": "Retain",
181186
"UpdateReplacePolicy": "Retain",
182187
},
@@ -918,7 +923,7 @@ function includeTestTemplate(scope: core.Construct, testTemplate: string, props:
918923
return new inc.CfnInclude(scope, 'MyScope', {
919924
templateFile: _testTemplateFilePath(testTemplate),
920925
parameters: props.parameters,
921-
// preserveLogicalIds: props.preserveLogicalIds,
926+
preserveLogicalIds: props.preserveLogicalIds,
922927
});
923928
}
924929

0 commit comments

Comments
 (0)