diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 4023f2b214069..188f9989075ea 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -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 diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 07c6dee3bfcbf..b7f712c23af56 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -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; @@ -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); } @@ -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, @@ -88,14 +94,32 @@ 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]; } } @@ -103,6 +127,25 @@ export class CfnInclude extends core.CfnElement { 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 diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json new file mode 100644 index 0000000000000..deec4ffa24e81 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json @@ -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" + } + }] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 1855c46acab5a..f09331895f079 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -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');