Skip to content

Commit

Permalink
feat(cfn-include): add support for retrieving parameter objects (#8658)
Browse files Browse the repository at this point in the history
Resolves #8657

---

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
comcalvi committed Jun 20, 2020
1 parent 1d26dbd commit 52dc123
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cloudformation-include/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ All items unchecked below are currently not supported.
### Ability to retrieve CloudFormation objects from the template:

- [x] Resources
- [ ] Parameters
- [x] Parameters
- [x] Conditions
- [ ] Outputs

Expand Down
53 changes: 48 additions & 5 deletions packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface CfnIncludeProps {
export class CfnInclude extends core.CfnElement {
private readonly conditions: { [conditionName: string]: core.CfnCondition } = {};
private readonly resources: { [logicalId: string]: core.CfnResource } = {};
private readonly parameters: { [logicalId: string]: core.CfnParameter } = {};
private readonly template: any;
private readonly preserveLogicalIds: boolean;

Expand All @@ -35,12 +36,17 @@ export class CfnInclude extends core.CfnElement {
// ToDo implement preserveLogicalIds=false
this.preserveLogicalIds = true;

// first, instantiate the conditions
// instantiate all parameters
for (const logicalId of Object.keys(this.template.Parameters || {})) {
this.createParameter(logicalId);
}

// instantiate the conditions
for (const conditionName of Object.keys(this.template.Conditions || {})) {
this.createCondition(conditionName);
}

// then, instantiate all resources as CDK L1 objects
// instantiate all resources as CDK L1 objects
for (const logicalId of Object.keys(this.template.Resources || {})) {
this.getOrCreateResource(logicalId);
}
Expand Down Expand Up @@ -72,7 +78,7 @@ export class CfnInclude extends core.CfnElement {

/**
* Returns the CfnCondition object from the 'Conditions'
* section of the CloudFormation template with the give name.
* section of the CloudFormation template with the given name.
* Any modifications performed on that object will be reflected in the resulting CDK template.
*
* If a Condition with the given name is not present in the template,
Expand All @@ -88,21 +94,58 @@ export class CfnInclude extends core.CfnElement {
return ret;
}

/**
* Returns the CfnParameter object from the 'Parameters'
* section of the included template
* Any modifications performed on that object will be reflected in the resulting CDK template.
*
* If a Parameter with the given name is not present in the template,
* throws an exception.
*
* @param parameterName the name of the parameter to retrieve
*/
public getParameter(parameterName: string): core.CfnParameter {
const ret = this.parameters[parameterName];
if (!ret) {
throw new Error(`Parameter with name '${parameterName}' was not found in the template`);
}
return ret;
}

/** @internal */
public _toCloudFormation(): object {
const ret: { [section: string]: any } = {};

for (const section of Object.keys(this.template)) {
// render all sections of the template unchanged,
// except Conditions and Resources, which will be taken care of by the created L1s
if (section !== 'Conditions' && section !== 'Resources') {
// except Conditions, Resources, and Parameters, which will be taken care of by the created L1s
if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters') {
ret[section] = this.template[section];
}
}

return ret;
}

private createParameter(logicalId: string): void {
const expression = cfn_parse.FromCloudFormation.parseValue(this.template.Parameters[logicalId]);
const cfnParameter = new core.CfnParameter(this, logicalId, {
type: expression.Type,
default: expression.Default,
allowedPattern: expression.AllowedPattern,
constraintDescription: expression.ConstraintDescription,
description: expression.Description,
maxLength: expression.MaxLength,
maxValue: expression.MaxValue,
minLength: expression.MinLength,
minValue: expression.MinValue,
noEcho: expression.NoEcho,
});

cfnParameter.overrideLogicalId(logicalId);
this.parameters[logicalId] = cfnParameter;
}

private createCondition(conditionName: string): void {
// ToDo condition expressions can refer to other conditions -
// will be important when implementing preserveLogicalIds=false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"Parameters": {
"BucketName": {
"Default": "MyS3Bucket",
"AllowedPattern": "^[a-zA-Z0-9]*$",
"ConstraintDescription": "a string consisting only of alphanumeric characters",
"Description": "The name of your bucket",
"MaxLength": "10",
"MinLength": "1",
"Type": "String",
"NoEcho": "true"
},
"CorsMaxAge": {
"Default": "3",
"Description": "the time in seconds that a browser will cache the preflight response",
"MaxValue": "300",
"MinValue": "0",
"Type": "Number",
"NoEcho": "true"
}
},
"Resources": {
"Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {
"Ref": "BucketName"
},
"CorsConfiguration": {
"CorsRules": [{
"AllowedMethods": [
"GET",
"POST"
],
"AllowedOrigins": [
"origin1",
"origin2"
],
"MaxAge": {
"Ref": "CorsMaxAge"
}
}]
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,40 @@ describe('CDK Include', () => {
);
});

test("correctly parses templates with parameters", () => {
const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json');
const param = cfnTemplate.getParameter('BucketName');
new s3.CfnBucket(stack, 'NewBucket', {
bucketName: param.valueAsString,
});

const originalTemplate = loadTestFileToJsObject('bucket-with-parameters.json');
expect(stack).toMatchTemplate({
"Resources": {
...originalTemplate.Resources,
"NewBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {
"Ref": "BucketName",
},
},
},
},
"Parameters": {
...originalTemplate.Parameters,
},
});
});

test('getParameter() throws an exception if asked for a Parameter with a name that is not present in the template', () => {
const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json');

expect(() => {
cfnTemplate.getParameter('FakeBucketNameThatDoesNotExist');
}).toThrow(/Parameter with name 'FakeBucketNameThatDoesNotExist' was not found in the template/);
});

test('reflects changes to a retrieved CfnCondition object in the resulting template', () => {
const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-condition.json');
const alwaysFalseCondition = cfnTemplate.getCondition('AlwaysFalseCond');
Expand Down

0 comments on commit 52dc123

Please sign in to comment.