From db60c27a772d485cc1a9e2099b201700c983da3a Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Thu, 4 Jun 2020 17:44:41 -0400 Subject: [PATCH 01/32] doc: added slack link to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ba7a422bb528f..286f7a917ec73 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ for tracking bugs and feature requests. * Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/aws-cdk) and tag it with `aws-cdk` * Come join the AWS CDK community on [Gitter](https://gitter.im/awslabs/aws-cdk) +* You'll need an invite to join the [Slack](https://awsdevelopers.slack.com) community * Open a support ticket with [AWS Support](https://console.aws.amazon.com/support/home#/) * If it turns out that you may have found a bug, please open an [issue](https://github.com/aws/aws-cdk/issues/new) From f0c85a36146bdf813638f926f61184661b3683f4 Mon Sep 17 00:00:00 2001 From: comcalvi <66279577+comcalvi@users.noreply.github.com> Date: Fri, 5 Jun 2020 12:49:05 -0400 Subject: [PATCH 02/32] Update README.md Co-authored-by: Adam Ruka --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 286f7a917ec73..9b51cbaba4118 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ for tracking bugs and feature requests. * Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/aws-cdk) and tag it with `aws-cdk` * Come join the AWS CDK community on [Gitter](https://gitter.im/awslabs/aws-cdk) -* You'll need an invite to join the [Slack](https://awsdevelopers.slack.com) community +* Talk in the CDK channel of the [AWS Developers Slack workspace](https://awsdevelopers.slack.com) (invite required) * Open a support ticket with [AWS Support](https://console.aws.amazon.com/support/home#/) * If it turns out that you may have found a bug, please open an [issue](https://github.com/aws/aws-cdk/issues/new) From c6664af7ca1ab35c62782527c5d2a7b8137de5ec Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Mon, 15 Jun 2020 19:08:00 -0400 Subject: [PATCH 03/32] added support for Fn::Select, Fn::FindInMap, Fn::Cidr, Fn::GetAZs, Fn::And, Fn::Not, and Fn::Or, including unit tests. --- .../test/test-templates/conditions.json | 48 ++++++++++++ .../test/test-templates/functions.json | 75 +++++++++++++++++++ .../test/valid-templates.test.ts | 16 ++++ packages/@aws-cdk/core/lib/cfn-parse.ts | 31 ++++++++ 4 files changed, 170 insertions(+) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json new file mode 100644 index 0000000000000..5b23794ea3a14 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json @@ -0,0 +1,48 @@ +{ + "Conditions": { + "AndCond": { + "Fn::And": [ + {"Condition": "AlwaysTrueCond"}, + {"Condition": "AlwaysFalseCond"} + ] + }, + "OrCond": { + "Fn::Or": [ + {"Condition": "AlwaysTrueCond"}, + {"Condition": "AlwaysFalseCond"} + ] + }, + "AlwaysTrueCond": { + "Fn::Not": [{ + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + }] + }, + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "AlwaysTrueCond", + "Name1", + "Name2" + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json new file mode 100644 index 0000000000000..6360df54104fd --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json @@ -0,0 +1,75 @@ +{ + "Mappings" : { + "RegionMap" : { + "region-1" : { + "HVM64" : "name1", "HVMG2" : "name2" + } + } + }, + "Resources": { + + "Vpc": { + "Type" : "AWS::EC2::VPC", + "Properties" : { + "CidrBlock" : { + "Fn::Cidr": [ "10.0.0.0/24", "6", "5"] + } + } + }, + "2ArgVpc": { + "Type" : "AWS::EC2::VPC", + "Properties" : { + "CidrBlock" : { + "Fn::Cidr": [ "10.0.0.0/24", "6"] + } + } + }, + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::FindInMap": [ + "RegionMap", + "region-1", + "HVM64" + ] + } + } + }, + "subnet1" : { + "Type" : "AWS::EC2::Subnet", + "Properties" : { + "VpcId" : { + "Ref" : "VPC" + }, + "CidrBlock" : "10.0.0.0/24", + "AvailabilityZone" : { + "Fn::Select" : [ + "0", + { + "Fn::GetAZs" : "" + } + ] + } + } + }, + + "subnet2" : { + "Type" : "AWS::EC2::Subnet", + "Properties" : { + "VpcId" : { + "Fn::Select" : ["3", ["foo", "aValidVPCID", "foo2"]] + }, + "CidrBlock" : "10.0.0.0/24", + "AvailabilityZone" : { + "Fn::Select" : [ + "0", + { + "Fn::GetAZs" : "eu-west-2" + } + ] + } + } + } + } +} \ 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 0291de98eba95..0b9042c77ae36 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -184,6 +184,22 @@ describe('CDK Include', () => { ); }); + test('can ingest a template with many conditions, and output it unchanged', () => { + includeTestTemplate(stack, 'conditions.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('conditions.json'), + ); + }); + + test('can ingest a template with multiple cloudformation intrinsic functions, and output it unchanged', () => { + includeTestTemplate(stack, 'functions.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('functions.json'), + ); + }); + test('can ingest a template with a Ref expression for an array value, and output it unchanged', () => { includeTestTemplate(stack, 'ref-array-property.json'); diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 67e2d2390ef62..f8fcfd15c49ed 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -179,6 +179,25 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.join(value[0], value[1]); } + case 'Fn::Cidr': { + const value = parseCfnValueToCdkValue(object[key]); + + if (value.length == 2) + return Fn.cidr(value[0], value[1]); + return Fn.cidr(value[0], value[1], value[2]); + } + case 'Fn::FindInMap': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.findInMap(value[0], value[1], value[2]); + } + case 'Fn::Select': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.select(value[0], value[1]); + } + case 'Fn::GetAZs': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.getAzs(value); + } case 'Fn::If': { // Fn::If takes a 3-element list as its argument // ToDo the first argument is the name of the condition, @@ -191,6 +210,18 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.conditionEquals(value[0], value[1]); } + case 'Fn::And': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionAnd(...value); + } + case 'Fn::Not': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionNot(value[0]); + } + case 'Fn::Or': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.conditionOr(...value); + } default: throw new Error(`Unsupported CloudFormation function '${key}'`); } From d0f64d2cdedf75e440c31948164fee1257b3302f Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 14:11:24 -0400 Subject: [PATCH 04/32] added support for the 'Fn::Transform' cloudformation intrinsic function. --- .../test/test-templates/functions.json | 18 ++++++++++++++- packages/@aws-cdk/core/lib/cfn-fn.ts | 22 +++++++++++++++++++ packages/@aws-cdk/core/lib/cfn-parse.ts | 12 ++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json index 6360df54104fd..9ee7fed30f3e1 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json @@ -40,7 +40,10 @@ "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { - "Ref" : "VPC" + "Fn::Split": [ + ",", + { "Fn::ImportValue" : "ImportedVpcId" } + ] }, "CidrBlock" : "10.0.0.0/24", "AvailabilityZone" : { @@ -70,6 +73,19 @@ ] } } + }, + "transformBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "location" + } + } + } + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 30fb6cf435bec..f6e5d02923fa3 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -172,6 +172,15 @@ export class Fn { return new FnFindInMap(mapName, topLevelKey, secondLevelKey).toString(); } + /** + * + * @param macroNameParameters The name of the macro to perform the processing, and the Parameters to pass to it + * @returns a token represented as a string + */ + public static transform(macroNameParameters: string): string { + return new FnTransform(macroNameParameters).toString(); + } + /** * Returns true if all the specified conditions evaluate to true, or returns * false if any one of the conditions evaluates to false. ``Fn::And`` acts as @@ -356,6 +365,19 @@ class FnFindInMap extends FnBase { } } +/** + * The intrinsic function ``Fn::Transform`` specifies a macro to perform custom processing on part of a stack template. + */ +class FnTransform extends FnBase { + /** + * + * @param macroNameParameters The name of the macro to be invoked, and the parameters to pass to it + */ + constructor(macroNameParameters: string) { + super('Fn::Transform', macroNameParameters); + } +} + /** * The ``Fn::GetAtt`` intrinsic function returns the value of an attribute from a resource in the template. */ diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index f8fcfd15c49ed..78249ec1d5f8a 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -198,6 +198,18 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.getAzs(value); } + case 'Fn::ImportValue': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.importValue(value); + } + case 'Fn::Split': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.split(value[0], value[1]); + } + case 'Fn::Transform': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.transform(value); + } case 'Fn::If': { // Fn::If takes a 3-element list as its argument // ToDo the first argument is the name of the condition, From d69e46dce96360149a7f06be64894912e63a8d10 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 14:24:45 -0400 Subject: [PATCH 05/32] added support for the Fn::Base64 Intrinsic Function --- .../test/test-templates/functions.json | 5 ++++- .../only-codecommit-repo-using-cfn-functions.json | 2 +- .../cloudformation-include/test/valid-templates.test.ts | 2 +- packages/@aws-cdk/core/lib/cfn-parse.ts | 4 ++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json index 9ee7fed30f3e1..fa57eab81ed29 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json @@ -81,7 +81,10 @@ "Fn::Transform": { "Name": "AWS::Include", "Parameters": { - "Location": "location" + "Location": "location", + "AnotherParameter": { + "Fn:Base64": "AnotherValue" + } } } } diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json index a1037dbf2402a..10e907c0ea753 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json @@ -5,7 +5,7 @@ "Properties": { "RepositoryName": "my-repository", "RepositoryDescription": { - "Fn::Base64": "my description, in base-64!" + "Fn::DoesNotExist": "my description, in base-64!" } } } 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 0b9042c77ae36..16b45ec30f03e 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -289,7 +289,7 @@ describe('CDK Include', () => { test("throws an exception when encountering a CFN function it doesn't support", () => { expect(() => { includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); - }).toThrow(/Unsupported CloudFormation function 'Fn::Base64'/); + }).toThrow(/Unsupported CloudFormation function 'Fn::DoesNotExist'/); }); test('throws an exception when encountering the CreationPolicy attribute in a resource', () => { diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 78249ec1d5f8a..816902b97b3f4 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -210,6 +210,10 @@ function parseIfCfnIntrinsic(object: any): any { const value = parseCfnValueToCdkValue(object[key]); return Fn.transform(value); } + case 'Fn::Base64': { + const value = parseCfnValueToCdkValue(object[key]); + return Fn.base64(value); + } case 'Fn::If': { // Fn::If takes a 3-element list as its argument // ToDo the first argument is the name of the condition, From df1446a8db9e404341ddd8b584b2d68bbe299a43 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 16:11:19 -0400 Subject: [PATCH 06/32] tested more complex combinations of conditional and non-conditional intrinsic functions --- .../functions-and-conditions.json | 104 ++++++++++++++++++ .../test/valid-templates.test.ts | 9 ++ 2 files changed, 113 insertions(+) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json new file mode 100644 index 0000000000000..17ccd663564ca --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -0,0 +1,104 @@ +{ + "Mappings" : { + "RegionMap" : { + "region-1" : { + "HVM64" : "name1", "HVMG2" : "name2" + } + } + }, + "Conditions": { + "AlwaysTrueCond": { + "Fn::Not": [{ + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + }] + }, + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Resources": { + "Vpc": { + "Type" : "AWS::EC2::VPC", + "Properties" : { + "CidrBlock" : { + "Fn::If": [ + "AlwaysTrueCond", + { "Fn::Cidr": [ "192.168.1.1/24", "2"] }, + { "Fn::Cidr": [ "10.0.0.0/24", "6", "5"] } + ] + } + } + }, + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + {"Fn::Or": ["AlwaysFalseCond", "AlwaysFalseCond"]}, + "Unreachable", + {"Fn::FindInMap": [ + "RegionMap", + "region-1", + "HVM64" + ]} + ] + } + } + }, + "subnet1" : { + "Type" : "AWS::EC2::Subnet", + "Properties" : { + "VpcId" : { + "Fn::If": [ + {"Fn::And": ["AlwaysTrueCond", "AlwaysTrueCond"]}, + { + "Fn::Split": [ + ",", + { "Fn::ImportValue" : "ImportedVpcId" } + ]}, + "Unreachable" + ] + }, + "CidrBlock" : "10.0.0.0/24", + "AvailabilityZone" : { + "Fn::Select" : [ + "0", + { + "Fn::GetAZs" : "" + } + ] + } + } + }, + "transformBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + { "Fn::Or": ["AlwaysFalseCond", "AlwaysTrueCond"]}, { + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "location", + "AnotherParameter": { + "Fn:Base64": "AnotherValue" + } + } + } + } + ] + } + } + } + } + } \ 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 16b45ec30f03e..738d3c1187d2f 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -200,6 +200,15 @@ describe('CDK Include', () => { ); }); + test('can ingest a template with intrinsic functions and conditions, and output it unchanged', () => { + includeTestTemplate(stack, 'functions-and-conditions.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('functions-and-conditions.json'), + ); + }); + + test('can ingest a template with a Ref expression for an array value, and output it unchanged', () => { includeTestTemplate(stack, 'ref-array-property.json'); From 132cb4d87a39ab64b9438107b8ffabef2f8156fe Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 18:03:01 -0400 Subject: [PATCH 07/32] fixed linter issues --- packages/@aws-cdk/core/lib/cfn-fn.ts | 7 ++++--- packages/@aws-cdk/core/lib/cfn-parse.ts | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index f6e5d02923fa3..88c27bb538e29 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -173,8 +173,10 @@ export class Fn { } /** - * - * @param macroNameParameters The name of the macro to perform the processing, and the Parameters to pass to it + * The intrinsic function Fn::Transform specifies a macro to perform custom processing on part of a stack template. + * Macros enable you to perform custom processing on templates, from simple actions like find-and-replace operations + * to extensive transformations of entire templates. + * @param macroNameParameters The name of the macro to perform the processing, and the Parameters to pass to it. * @returns a token represented as a string */ public static transform(macroNameParameters: string): string { @@ -370,7 +372,6 @@ class FnFindInMap extends FnBase { */ class FnTransform extends FnBase { /** - * * @param macroNameParameters The name of the macro to be invoked, and the parameters to pass to it */ constructor(macroNameParameters: string) { diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 816902b97b3f4..d386c5f512e55 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -181,9 +181,10 @@ function parseIfCfnIntrinsic(object: any): any { } case 'Fn::Cidr': { const value = parseCfnValueToCdkValue(object[key]); - - if (value.length == 2) + + if (value.length === 2) { return Fn.cidr(value[0], value[1]); + } return Fn.cidr(value[0], value[1], value[2]); } case 'Fn::FindInMap': { From ae7c5707c74526cb7f67b71f479ed34f029446ca Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 20:36:53 -0400 Subject: [PATCH 08/32] implmented Adam's requests and fixed additional linter issues --- .../test/invalid-templates.test.ts | 6 ++ .../test/test-templates/conditions.json | 48 ---------- .../functions-and-conditions.json | 52 ++++++---- .../test/test-templates/functions.json | 94 ------------------- ...y-codecommit-repo-using-cfn-functions.json | 0 .../test/valid-templates.test.ts | 23 ----- packages/@aws-cdk/core/lib/cfn-fn.ts | 15 +-- packages/@aws-cdk/core/lib/cfn-parse.ts | 2 +- 8 files changed, 51 insertions(+), 189 deletions(-) delete mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json delete mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json rename packages/@aws-cdk/cloudformation-include/test/test-templates/{ => invalid}/only-codecommit-repo-using-cfn-functions.json (100%) diff --git a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts index 038ea1e9e6dde..9fb6f91131329 100644 --- a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts @@ -52,6 +52,12 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-condition.json'); }).toThrow(/Resource 'Bucket' uses Condition 'AlwaysFalseCond' that doesn't exist/); }); + + test("throws an exception when encountering a CFN function it doesn't support", () => { + expect(() => { + includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); + }).toThrow(/Unsupported CloudFormation function 'Fn::DoesNotExist'/); + }); }); function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json deleted file mode 100644 index 5b23794ea3a14..0000000000000 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/conditions.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Conditions": { - "AndCond": { - "Fn::And": [ - {"Condition": "AlwaysTrueCond"}, - {"Condition": "AlwaysFalseCond"} - ] - }, - "OrCond": { - "Fn::Or": [ - {"Condition": "AlwaysTrueCond"}, - {"Condition": "AlwaysFalseCond"} - ] - }, - "AlwaysTrueCond": { - "Fn::Not": [{ - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] - }] - }, - "AlwaysFalseCond": { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] - } - }, - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::If": [ - "AlwaysTrueCond", - "Name1", - "Name2" - ] - } - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json index 17ccd663564ca..a9031ab3ae58c 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -9,21 +9,22 @@ "Conditions": { "AlwaysTrueCond": { "Fn::Not": [{ - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] - }] + "Fn::Equals": [{"Ref": "AWS::Region"}, "completely-made-up-region"]}] }, "AlwaysFalseCond": { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] + "Fn::Equals": [{ "Ref": "AWS::Region" }, "completely-made-up-region"] + }, + "AndCond": { + "Fn::And": [ + {"Condition": "AlwaysTrueCond"}, + {"Condition": "AlwaysFalseCond"} + ] + }, + "OrCond": { + "Fn::Or": [ + {"Condition": "AlwaysTrueCond"}, + {"Condition": "AlwaysFalseCond"} + ] } }, "Resources": { @@ -33,8 +34,8 @@ "CidrBlock" : { "Fn::If": [ "AlwaysTrueCond", - { "Fn::Cidr": [ "192.168.1.1/24", "2"] }, - { "Fn::Cidr": [ "10.0.0.0/24", "6", "5"] } + { "Fn::Cidr": [ "192.168.1.1/24", 2] }, + { "Fn::Cidr": [ "10.0.0.0/24", 6, 5] } ] } } @@ -55,7 +56,7 @@ } } }, - "subnet1" : { + "Subnet1" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { @@ -80,7 +81,24 @@ } } }, - "transformBucket": { + "Subnet2" : { + "Type" : "AWS::EC2::Subnet", + "Properties" : { + "VpcId" : { + "Fn::Select" : ["3", ["foo", "aValidVPCID", "foo2"]] + }, + "CidrBlock" : "10.0.0.0/24", + "AvailabilityZone" : { + "Fn::Select" : [ + "0", + { + "Fn::GetAZs" : "eu-west-2" + } + ] + } + } + }, + "TransformBucket": { "Type": "AWS::S3::Bucket", "Properties": { "BucketName": { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json deleted file mode 100644 index fa57eab81ed29..0000000000000 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "Mappings" : { - "RegionMap" : { - "region-1" : { - "HVM64" : "name1", "HVMG2" : "name2" - } - } - }, - "Resources": { - - "Vpc": { - "Type" : "AWS::EC2::VPC", - "Properties" : { - "CidrBlock" : { - "Fn::Cidr": [ "10.0.0.0/24", "6", "5"] - } - } - }, - "2ArgVpc": { - "Type" : "AWS::EC2::VPC", - "Properties" : { - "CidrBlock" : { - "Fn::Cidr": [ "10.0.0.0/24", "6"] - } - } - }, - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::FindInMap": [ - "RegionMap", - "region-1", - "HVM64" - ] - } - } - }, - "subnet1" : { - "Type" : "AWS::EC2::Subnet", - "Properties" : { - "VpcId" : { - "Fn::Split": [ - ",", - { "Fn::ImportValue" : "ImportedVpcId" } - ] - }, - "CidrBlock" : "10.0.0.0/24", - "AvailabilityZone" : { - "Fn::Select" : [ - "0", - { - "Fn::GetAZs" : "" - } - ] - } - } - }, - - "subnet2" : { - "Type" : "AWS::EC2::Subnet", - "Properties" : { - "VpcId" : { - "Fn::Select" : ["3", ["foo", "aValidVPCID", "foo2"]] - }, - "CidrBlock" : "10.0.0.0/24", - "AvailabilityZone" : { - "Fn::Select" : [ - "0", - { - "Fn::GetAZs" : "eu-west-2" - } - ] - } - } - }, - "transformBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::Transform": { - "Name": "AWS::Include", - "Parameters": { - "Location": "location", - "AnotherParameter": { - "Fn:Base64": "AnotherValue" - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/only-codecommit-repo-using-cfn-functions.json similarity index 100% rename from packages/@aws-cdk/cloudformation-include/test/test-templates/only-codecommit-repo-using-cfn-functions.json rename to packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/only-codecommit-repo-using-cfn-functions.json 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 738d3c1187d2f..70372ad7f3c96 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -184,22 +184,6 @@ describe('CDK Include', () => { ); }); - test('can ingest a template with many conditions, and output it unchanged', () => { - includeTestTemplate(stack, 'conditions.json'); - - expect(stack).toMatchTemplate( - loadTestFileToJsObject('conditions.json'), - ); - }); - - test('can ingest a template with multiple cloudformation intrinsic functions, and output it unchanged', () => { - includeTestTemplate(stack, 'functions.json'); - - expect(stack).toMatchTemplate( - loadTestFileToJsObject('functions.json'), - ); - }); - test('can ingest a template with intrinsic functions and conditions, and output it unchanged', () => { includeTestTemplate(stack, 'functions-and-conditions.json'); @@ -208,7 +192,6 @@ describe('CDK Include', () => { ); }); - test('can ingest a template with a Ref expression for an array value, and output it unchanged', () => { includeTestTemplate(stack, 'ref-array-property.json'); @@ -295,12 +278,6 @@ describe('CDK Include', () => { }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); - test("throws an exception when encountering a CFN function it doesn't support", () => { - expect(() => { - includeTestTemplate(stack, 'only-codecommit-repo-using-cfn-functions.json'); - }).toThrow(/Unsupported CloudFormation function 'Fn::DoesNotExist'/); - }); - test('throws an exception when encountering the CreationPolicy attribute in a resource', () => { expect(() => { includeTestTemplate(stack, 'resource-attribute-creation-policy.json'); diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 88c27bb538e29..98a368b9fdc38 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -176,11 +176,12 @@ export class Fn { * The intrinsic function Fn::Transform specifies a macro to perform custom processing on part of a stack template. * Macros enable you to perform custom processing on templates, from simple actions like find-and-replace operations * to extensive transformations of entire templates. - * @param macroNameParameters The name of the macro to perform the processing, and the Parameters to pass to it. + * @param macroName The name of the macro to perform the processing + * @param parameters The parameters to be passed to the macro * @returns a token represented as a string */ - public static transform(macroNameParameters: string): string { - return new FnTransform(macroNameParameters).toString(); + public static transform(macroName: string, parameters: '{[name: string]: any}'): string { + return new FnTransform(macroName, parameters).toString(); } /** @@ -372,10 +373,12 @@ class FnFindInMap extends FnBase { */ class FnTransform extends FnBase { /** - * @param macroNameParameters The name of the macro to be invoked, and the parameters to pass to it + * creates an ``Fn::Transform`` function. + * @param macroName The name of the macro to be invoked + * @param parameters the parameters to pass to it */ - constructor(macroNameParameters: string) { - super('Fn::Transform', macroNameParameters); + constructor(macroName: string, parameters: '{[name: string]: any}') { + super('Fn::Transform', {Name: macroName, Parameters: parameters}); } } diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index d386c5f512e55..876635e9b57f1 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -209,7 +209,7 @@ function parseIfCfnIntrinsic(object: any): any { } case 'Fn::Transform': { const value = parseCfnValueToCdkValue(object[key]); - return Fn.transform(value); + return Fn.transform(value.Name, value.Parameters); } case 'Fn::Base64': { const value = parseCfnValueToCdkValue(object[key]); From 7ca707e91f463af8e035312acea1138634eb8e46 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 16 Jun 2020 20:41:43 -0400 Subject: [PATCH 09/32] updated README to reflect the newly supported cloudformation functions --- .../@aws-cdk/cloudformation-include/README.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index a64d7b988e9bb..0a9b21dbcc173 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -136,16 +136,16 @@ All items unchecked below are currently not supported. - [x] Fn::GetAtt - [x] Fn::Join - [x] Fn::If -- [ ] Fn::And +- [x] Fn::And - [x] Fn::Equals -- [ ] Fn::Not -- [ ] Fn::Or -- [ ] Fn::Base64 -- [ ] Fn::Cidr -- [ ] Fn::FindInMap -- [ ] Fn::GetAZs -- [ ] Fn::ImportValue -- [ ] Fn::Select -- [ ] Fn::Split +- [x] Fn::Not +- [x] Fn::Or +- [x] Fn::Base64 +- [x] Fn::Cidr +- [x] Fn::FindInMap +- [x] Fn::GetAZs +- [x] Fn::ImportValue +- [x] Fn::Select +- [x] Fn::Split - [ ] Fn::Sub -- [ ] Fn::Transform +- [x] Fn::Transform From 63c27a775196a33b04fde0bcd0d822d87aa132f9 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Wed, 17 Jun 2020 17:35:10 -0400 Subject: [PATCH 10/32] removed quotes from the type of Transform's parameter argument, modified the return type of transform to IResolvable, and updated transform docs --- .../functions-and-conditions.json | 246 +++++++++++------- packages/@aws-cdk/core/lib/cfn-fn.ts | 12 +- 2 files changed, 151 insertions(+), 107 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json index a9031ab3ae58c..873bbe2254d48 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -1,122 +1,168 @@ { - "Mappings" : { - "RegionMap" : { - "region-1" : { - "HVM64" : "name1", "HVMG2" : "name2" - } + "Mappings": { + "RegionMap": { + "region-1": { + "HVM64": "name1", + "HVMG2": "name2" } + } + }, + "Conditions": { + "AlwaysTrueCond": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + ] }, - "Conditions": { - "AlwaysTrueCond": { - "Fn::Not": [{ - "Fn::Equals": [{"Ref": "AWS::Region"}, "completely-made-up-region"]}] + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" }, - "AlwaysFalseCond": { - "Fn::Equals": [{ "Ref": "AWS::Region" }, "completely-made-up-region"] + "completely-made-up-region" + ] + }, + "AndCond": { + "Fn::And": [ + { + "Condition": "AlwaysTrueCond" }, - "AndCond": { - "Fn::And": [ - {"Condition": "AlwaysTrueCond"}, - {"Condition": "AlwaysFalseCond"} - ] + { + "Condition": "AlwaysFalseCond" + } + ] + }, + "OrCond": { + "Fn::Or": [ + { + "Condition": "AlwaysTrueCond" }, - "OrCond": { - "Fn::Or": [ - {"Condition": "AlwaysTrueCond"}, - {"Condition": "AlwaysFalseCond"} - ] + { + "Condition": "AlwaysFalseCond" } - }, - "Resources": { - "Vpc": { - "Type" : "AWS::EC2::VPC", - "Properties" : { - "CidrBlock" : { - "Fn::If": [ - "AlwaysTrueCond", - { "Fn::Cidr": [ "192.168.1.1/24", 2] }, - { "Fn::Cidr": [ "10.0.0.0/24", 6, 5] } + ] + } + }, + "Resources": { + "Vpc": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": { + "Fn::If": [ + "AlwaysTrueCond", + { + "Fn::Cidr": [ + "192.168.1.1/24", + 2 ] - } + }, + { + "Fn::Cidr": [ + "10.0.0.0/24", + 6, + 5 + ] + } + ] } - }, - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::If": [ - {"Fn::Or": ["AlwaysFalseCond", "AlwaysFalseCond"]}, - "Unreachable", - {"Fn::FindInMap": [ + } + }, + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "OrCond", + { + "Fn::FindInMap": [ "RegionMap", "region-1", "HVM64" - ]} - ] - } + ] + }, + "Unreachable" + ] } - }, - "Subnet1" : { - "Type" : "AWS::EC2::Subnet", - "Properties" : { - "VpcId" : { - "Fn::If": [ - {"Fn::And": ["AlwaysTrueCond", "AlwaysTrueCond"]}, - { - "Fn::Split": [ - ",", - { "Fn::ImportValue" : "ImportedVpcId" } - ]}, - "Unreachable" - ] - }, - "CidrBlock" : "10.0.0.0/24", - "AvailabilityZone" : { - "Fn::Select" : [ - "0", - { - "Fn::GetAZs" : "" - } - ] - } + } + }, + "Subnet1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Fn::If": [ + "AlwaysTrueCond", + { + "Fn::Split": [ + ",", + { + "Fn::ImportValue": "ImportedVpcId" + } + ] + }, + "Unreachable" + ] + }, + "CidrBlock": "10.0.0.0/24", + "AvailabilityZone": { + "Fn::Select": [ + "0", + { + "Fn::GetAZs": "" + } + ] } - }, - "Subnet2" : { - "Type" : "AWS::EC2::Subnet", - "Properties" : { - "VpcId" : { - "Fn::Select" : ["3", ["foo", "aValidVPCID", "foo2"]] - }, - "CidrBlock" : "10.0.0.0/24", - "AvailabilityZone" : { - "Fn::Select" : [ - "0", - { - "Fn::GetAZs" : "eu-west-2" - } + } + }, + "Subnet2": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Fn::Select": [ + "3", + [ + "foo", + "aValidVPCID", + "foo2" ] - } + ] + }, + "CidrBlock": "10.0.0.0/24", + "AvailabilityZone": { + "Fn::Select": [ + "0", + { + "Fn::GetAZs": "eu-west-2" + } + ] } - }, - "TransformBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::If": [ - { "Fn::Or": ["AlwaysFalseCond", "AlwaysTrueCond"]}, { - "Fn::Transform": { - "Name": "AWS::Include", - "Parameters": { - "Location": "location", - "AnotherParameter": { - "Fn:Base64": "AnotherValue" - } + } + }, + "TransformBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "AndCond", + { + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "location", + "AnotherParameter": { + "Fn:Base64": "AnotherValue" } } } - ] - } + } + ] } } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 98a368b9fdc38..31269f531a792 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -173,15 +173,13 @@ export class Fn { } /** - * The intrinsic function Fn::Transform specifies a macro to perform custom processing on part of a stack template. - * Macros enable you to perform custom processing on templates, from simple actions like find-and-replace operations - * to extensive transformations of entire templates. + * Creates a token representing the ``Fn::Transform`` expression * @param macroName The name of the macro to perform the processing * @param parameters The parameters to be passed to the macro - * @returns a token represented as a string + * @returns a token representing the transform expression */ - public static transform(macroName: string, parameters: '{[name: string]: any}'): string { - return new FnTransform(macroName, parameters).toString(); + public static transform(macroName: string, parameters: { [name: string]: any }): IResolvable { + return new FnTransform(macroName, parameters); } /** @@ -377,7 +375,7 @@ class FnTransform extends FnBase { * @param macroName The name of the macro to be invoked * @param parameters the parameters to pass to it */ - constructor(macroName: string, parameters: '{[name: string]: any}') { + constructor(macroName: string, parameters: { [name: string]: any }) { super('Fn::Transform', {Name: macroName, Parameters: parameters}); } } From 41792710439cd994d2e582fbd69de0a696c5142e Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Wed, 17 Jun 2020 20:09:23 -0400 Subject: [PATCH 11/32] fixed teseting issue related to Fn::Select --- .../functions-and-conditions.json | 48 ++++++++----------- packages/@aws-cdk/core/lib/cfn-fn.ts | 3 +- packages/@aws-cdk/core/lib/cfn-parse.ts | 4 -- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json index 873bbe2254d48..c0ac971e316a6 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -20,31 +20,20 @@ } ] }, - "AlwaysFalseCond": { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] - }, "AndCond": { "Fn::And": [ { "Condition": "AlwaysTrueCond" }, { - "Condition": "AlwaysFalseCond" - } - ] - }, - "OrCond": { - "Fn::Or": [ - { - "Condition": "AlwaysTrueCond" - }, - { - "Condition": "AlwaysFalseCond" + "Fn::Or": [ + { + "Condition": "AlwaysTrueCond" + }, + { + "Condition": "AlwaysTrueCond" + } + ] } ] } @@ -59,14 +48,15 @@ { "Fn::Cidr": [ "192.168.1.1/24", - 2 + 2, + 5 ] }, { "Fn::Cidr": [ "10.0.0.0/24", - 6, - 5 + "6", + "5" ] } ] @@ -124,12 +114,14 @@ "Properties": { "VpcId": { "Fn::Select": [ - "3", - [ - "foo", - "aValidVPCID", - "foo2" - ] + 0, + { + "Fn::Cidr": [ + "10.0.0.0/24", + 5, + 2 + ] + } ] }, "CidrBlock": "10.0.0.0/24", diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 31269f531a792..73d0f61c03236 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -174,6 +174,7 @@ export class Fn { /** * Creates a token representing the ``Fn::Transform`` expression + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html * @param macroName The name of the macro to perform the processing * @param parameters The parameters to be passed to the macro * @returns a token representing the transform expression @@ -376,7 +377,7 @@ class FnTransform extends FnBase { * @param parameters the parameters to pass to it */ constructor(macroName: string, parameters: { [name: string]: any }) { - super('Fn::Transform', {Name: macroName, Parameters: parameters}); + super('Fn::Transform', { Name: macroName, Parameters: parameters }); } } diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 876635e9b57f1..f8c2a84a98fd4 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -181,10 +181,6 @@ function parseIfCfnIntrinsic(object: any): any { } case 'Fn::Cidr': { const value = parseCfnValueToCdkValue(object[key]); - - if (value.length === 2) { - return Fn.cidr(value[0], value[1]); - } return Fn.cidr(value[0], value[1], value[2]); } case 'Fn::FindInMap': { From 14a0d647bdf042f6707dd5457900985c1d3fdbbf Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Thu, 18 Jun 2020 11:03:33 -0400 Subject: [PATCH 12/32] merge conflict resolution --- .../test/valid-templates.test.ts | 12 ------------ 1 file changed, 12 deletions(-) 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 987b96cf54ec2..1855c46acab5a 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -299,18 +299,6 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-resource-type.json'); }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); - - test('throws an exception when encountering the CreationPolicy attribute in a resource', () => { - expect(() => { - includeTestTemplate(stack, 'resource-attribute-creation-policy.json'); - }).toThrow(/The CreationPolicy resource attribute is not supported by cloudformation-include yet/); - }); - - test('throws an exception when encountering the UpdatePolicy attribute in a resource', () => { - expect(() => { - includeTestTemplate(stack, 'resource-attribute-update-policy.json'); - }).toThrow(/The UpdatePolicy resource attribute is not supported by cloudformation-include yet/); - }); }); interface IncludeTestTemplateProps { From 4c9d1eca5bf1ec9e6492932aec13404c46dcc398 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Thu, 18 Jun 2020 12:57:08 -0400 Subject: [PATCH 13/32] fixed typo in condition name --- .../test/test-templates/functions-and-conditions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json index c0ac971e316a6..ce57b3e1a8b9f 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/functions-and-conditions.json @@ -68,7 +68,7 @@ "Properties": { "BucketName": { "Fn::If": [ - "OrCond", + "AndCond", { "Fn::FindInMap": [ "RegionMap", From 42c0228f7702216f7ccde494d9edaeb761134bf8 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Thu, 18 Jun 2020 18:26:05 -0400 Subject: [PATCH 14/32] removed parameters from _toCloudFormation() --- .../cloudformation-include/lib/cfn-include.ts | 40 +++++++++++++++++-- .../bucket-with-parameters.json | 15 +++++++ .../test/valid-templates.test.ts | 14 +++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 07c6dee3bfcbf..0c351bedb6781 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: { [parameterName: string]: core.CfnParameter } = {}; private readonly template: any; private readonly preserveLogicalIds: boolean; @@ -44,6 +45,11 @@ export class CfnInclude extends core.CfnElement { for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); } + + // instantiate all parameters + for (const parameterName of Object.keys(this.template.Parameters || {})) { + this.createParameter(parameterName); + } } /** @@ -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,34 @@ export class CfnInclude extends core.CfnElement { return ret; } + /** + * Returns the CfnParameter object from the 'Parameters' + * 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 Parameter with the given name is not present in the template, + * throws an exception. + * + * @param parameterName the name of the Parameter in the CloudFormation template file + */ + /* 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 + // ToDo + if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters') { ret[section] = this.template[section]; } } @@ -103,6 +129,14 @@ export class CfnInclude extends core.CfnElement { return ret; } + private createParameter(parameterName: string): void { + const expression = cfn_parse.FromCloudFormation.parseValue(this.template.Parameters[parameterName]); + const CfnParameter = new core.CfnParameter(this, parameterName, expression); + + CfnParameter.overrideLogicalId(parameterName); + this.parameters[parameterName] = 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..0104ef6541831 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json @@ -0,0 +1,15 @@ +{ + "Parameters": { + "BucketName": "MyS3Bucket" + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "BucketName" + } + } + } + } +} \ 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..52cf132eaab46 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,19 @@ describe('CDK Include', () => { ); }); + test("correctly parses templates with parameters", () => { + const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json'); + //const bucketNameParam = cfnTemplate.getParameter('BucketName'); + + expect(stack).toHaveResourceLike('AWS::S3::Bucket', { + "Properties": { + "BucketName": "MyS3Bucket" + } + }); + + //expect(bucketNameParam).toBe("MyS3Bucket"); + }); + 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'); @@ -299,6 +312,7 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-resource-type.json'); }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); + }); interface IncludeTestTemplateProps { From 6a2df1e910e4302c8348827bc02f6b2d8e112ada Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 19 Jun 2020 13:23:02 -0400 Subject: [PATCH 15/32] added support for parameters in templates --- .../cloudformation-include/lib/cfn-include.ts | 56 ++++++++++--------- .../bucket-with-parameters.json | 6 +- .../test/valid-templates.test.ts | 45 +++++++++++++-- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 0c351bedb6781..df26cc198d5b2 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -23,7 +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: { [parameterName: string]: core.CfnParameter } = {}; + private readonly parameters: { [logicalId: string]: core.CfnParameter } = {}; private readonly template: any; private readonly preserveLogicalIds: boolean; @@ -47,27 +47,11 @@ export class CfnInclude extends core.CfnElement { } // instantiate all parameters - for (const parameterName of Object.keys(this.template.Parameters || {})) { - this.createParameter(parameterName); + for (const logicalId of Object.keys(this.template.Parameters || {})) { + this.createParameter(logicalId); } } - /** - * Returns the low-level CfnResource from the template with the given logical ID. - * Any modifications performed on that resource will be reflected in the resulting CDK template. - * - * The returned object will be of the proper underlying class; - * you can always cast it to the correct type in your code: - * - * // assume the template contains an AWS::S3::Bucket with logical ID 'Bucket' - * const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; - * // cfnBucket is of type s3.CfnBucket - * - * If the template does not contain a resource with the given logical ID, - * an exception will be thrown. - * - * @param logicalId the logical ID of the resource in the CloudFormation template file - */ public getResource(logicalId: string): core.CfnResource { const ret = this.resources[logicalId]; if (!ret) { @@ -94,6 +78,14 @@ export class CfnInclude extends core.CfnElement { return ret; } + 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; + } + /** * Returns the CfnParameter object from the 'Parameters' * section of the CloudFormation template with the given name. @@ -102,7 +94,7 @@ export class CfnInclude extends core.CfnElement { * If a Parameter with the given name is not present in the template, * throws an exception. * - * @param parameterName the name of the Parameter in the CloudFormation template file + * @param logicalId the name of the Parameter in the CloudFormation template file */ /* public getParameter(parameterName: string): core.CfnParameter { const ret = this.parameters[parameterName]; @@ -129,12 +121,24 @@ export class CfnInclude extends core.CfnElement { return ret; } - private createParameter(parameterName: string): void { - const expression = cfn_parse.FromCloudFormation.parseValue(this.template.Parameters[parameterName]); - const CfnParameter = new core.CfnParameter(this, parameterName, expression); - - CfnParameter.overrideLogicalId(parameterName); - this.parameters[parameterName] = CfnParameter; + private createParameter(logicalId: string): void { + const expression = cfn_parse.FromCloudFormation.parseValue(this.template.Parameters[logicalId]); + const expressionCaseCorrected = { + 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, + }; + const CfnParameter = new core.CfnParameter(this, logicalId, expressionCaseCorrected); + + CfnParameter.overrideLogicalId(logicalId); + this.parameters[logicalId] = CfnParameter; } private createCondition(conditionName: string): void { 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 index 0104ef6541831..531db51c9f10d 100644 --- 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 @@ -1,6 +1,10 @@ { "Parameters": { - "BucketName": "MyS3Bucket" + "BucketName": { + "Default": "MyS3Bucket", + "Description": "The name of your bucket", + "Type": "String" + } }, "Resources": { "Bucket": { 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 52cf132eaab46..a7e80df958d54 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -253,15 +253,48 @@ describe('CDK Include', () => { test("correctly parses templates with parameters", () => { const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json'); - //const bucketNameParam = cfnTemplate.getParameter('BucketName'); + const param = cfnTemplate.getParameter('BucketName'); + const bucket = new s3.CfnBucket(stack, "newBucket", {"bucketName": param.valueAsString}); - expect(stack).toHaveResourceLike('AWS::S3::Bucket', { - "Properties": { - "BucketName": "MyS3Bucket" + expect(stack).toMatchTemplate( + { + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "BucketName" + } + } + }, + "newBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "BucketName" + } + } + } + }, + "Parameters": { + "BucketName": { + "Type": "String", + "Default": "MyS3Bucket", + "Description": "The name of your bucket" + } + } } - }); + ); + + expect(bucket.bucketName).toBeDefined(); + }); - //expect(bucketNameParam).toBe("MyS3Bucket"); + test('getParameter() throws an exception when a parameter is not found', () => { + 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', () => { From cd27ff6053f12d310d823c6f0e88dc3e0afb5d25 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 19 Jun 2020 14:59:14 -0400 Subject: [PATCH 16/32] updated documentation --- .../cloudformation-include/lib/cfn-include.ts | 13 ++++++++++- .../test/valid-templates.test.ts | 22 +++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index df26cc198d5b2..b9a0ff9f4cc72 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -78,6 +78,17 @@ 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) { @@ -136,7 +147,7 @@ export class CfnInclude extends core.CfnElement { noEcho: expression.NoEcho, }; const CfnParameter = new core.CfnParameter(this, logicalId, expressionCaseCorrected); - + CfnParameter.overrideLogicalId(logicalId); this.parameters[logicalId] = CfnParameter; } 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 47d7d70899fec..fe9c50341d6db 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -263,27 +263,27 @@ describe('CDK Include', () => { "Type": "AWS::S3::Bucket", "Properties": { "BucketName": { - "Ref": "BucketName" - } - } + "Ref": "BucketName", + }, + }, }, "newBucket": { "Type": "AWS::S3::Bucket", "Properties": { "BucketName": { - "Ref": "BucketName" - } - } - } + "Ref": "BucketName", + }, + }, + }, }, "Parameters": { "BucketName": { "Type": "String", "Default": "MyS3Bucket", - "Description": "The name of your bucket" - } - } - } + "Description": "The name of your bucket", + }, + }, + }, ); expect(bucket.bucketName).toBeDefined(); From 1e72465697888396fb7b3ba0594ab3681f80cad2 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 19 Jun 2020 15:06:01 -0400 Subject: [PATCH 17/32] updated readme --- packages/@aws-cdk/cloudformation-include/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 070f902759c5f8528cf3a509dada354d115972ce Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 19 Jun 2020 18:22:52 -0400 Subject: [PATCH 18/32] incorporated adam's comments --- .../cloudformation-include/lib/cfn-include.ts | 55 ++++++++----------- .../bucket-with-parameters.json | 30 +++++++++- .../test/valid-templates.test.ts | 46 ++++++---------- 3 files changed, 70 insertions(+), 61 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index b9a0ff9f4cc72..7906bd92c5cb5 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -36,6 +36,10 @@ export class CfnInclude extends core.CfnElement { // ToDo implement preserveLogicalIds=false this.preserveLogicalIds = true; + // instantiate all parameters + for (const logicalId of Object.keys(this.template.Parameters || {})) { + this.createParameter(logicalId); + } // first, instantiate the conditions for (const conditionName of Object.keys(this.template.Conditions || {})) { this.createCondition(conditionName); @@ -45,13 +49,24 @@ export class CfnInclude extends core.CfnElement { for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); } - - // instantiate all parameters - for (const logicalId of Object.keys(this.template.Parameters || {})) { - this.createParameter(logicalId); - } } + /** + * Returns the low-level CfnResource from the template with the given logical ID. + * Any modifications performed on that resource will be reflected in the resulting CDK template. + * + * The returned object will be of the proper underlying class; + * you can always cast it to the correct type in your code: + * + * // assume the template contains an AWS::S3::Bucket with logical ID 'Bucket' + * const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; + * // cfnBucket is of type s3.CfnBucket + * + * If the template does not contain a resource with the given logical ID, + * an exception will be thrown. + * + * @param logicalId the logical ID of the resource in the CloudFormation template file + */ public getResource(logicalId: string): core.CfnResource { const ret = this.resources[logicalId]; if (!ret) { @@ -88,7 +103,6 @@ export class CfnInclude extends core.CfnElement { * * @param parameterName the name of the parameter to retrieve */ - public getParameter(parameterName: string): core.CfnParameter { const ret = this.parameters[parameterName]; if (!ret) { @@ -97,25 +111,6 @@ export class CfnInclude extends core.CfnElement { return ret; } - /** - * Returns the CfnParameter object from the 'Parameters' - * 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 Parameter with the given name is not present in the template, - * throws an exception. - * - * @param logicalId the name of the Parameter in the CloudFormation template file - */ - /* 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 } = {}; @@ -123,7 +118,6 @@ export class CfnInclude extends core.CfnElement { for (const section of Object.keys(this.template)) { // render all sections of the template unchanged, // except Conditions, Resources, and Parameters, which will be taken care of by the created L1s - // ToDo if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters') { ret[section] = this.template[section]; } @@ -134,7 +128,7 @@ export class CfnInclude extends core.CfnElement { private createParameter(logicalId: string): void { const expression = cfn_parse.FromCloudFormation.parseValue(this.template.Parameters[logicalId]); - const expressionCaseCorrected = { + const cfnParameter = new core.CfnParameter(this, logicalId, { type: expression.Type, default: expression.Default, allowedPattern: expression.AllowedPattern, @@ -145,11 +139,10 @@ export class CfnInclude extends core.CfnElement { minLength: expression.MinLength, minValue: expression.MinValue, noEcho: expression.NoEcho, - }; - const CfnParameter = new core.CfnParameter(this, logicalId, expressionCaseCorrected); + }); - CfnParameter.overrideLogicalId(logicalId); - this.parameters[logicalId] = CfnParameter; + cfnParameter.overrideLogicalId(logicalId); + this.parameters[logicalId] = cfnParameter; } private createCondition(conditionName: string): void { 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 index 531db51c9f10d..deec4ffa24e81 100644 --- 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 @@ -2,8 +2,21 @@ "Parameters": { "BucketName": { "Default": "MyS3Bucket", + "AllowedPattern": "^[a-zA-Z0-9]*$", + "ConstraintDescription": "a string consisting only of alphanumeric characters", "Description": "The name of your bucket", - "Type": "String" + "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": { @@ -12,6 +25,21 @@ "Properties": { "BucketName": { "Ref": "BucketName" + }, + "CorsConfiguration": { + "CorsRules": [{ + "AllowedMethods": [ + "GET", + "POST" + ], + "AllowedOrigins": [ + "origin1", + "origin2" + ], + "MaxAge": { + "Ref": "CorsMaxAge" + } + }] } } } 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 fe9c50341d6db..91cc14be619b2 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -253,43 +253,31 @@ describe('CDK Include', () => { test("correctly parses templates with parameters", () => { const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json'); + const originalTemplate = loadTestFileToJsObject('bucket-with-parameters.json'); const param = cfnTemplate.getParameter('BucketName'); - const bucket = new s3.CfnBucket(stack, "newBucket", {"bucketName": param.valueAsString}); + new s3.CfnBucket(stack, "NewBucket", { + "bucketName": param.valueAsString, + }); - expect(stack).toMatchTemplate( - { - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Ref": "BucketName", - }, - }, - }, - "newBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Ref": "BucketName", - }, + expect(stack).toMatchTemplate({ + "Resources": { + ...originalTemplate.Resources, + "NewBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Ref": "BucketName", }, }, }, - "Parameters": { - "BucketName": { - "Type": "String", - "Default": "MyS3Bucket", - "Description": "The name of your bucket", - }, - }, }, - ); - - expect(bucket.bucketName).toBeDefined(); + "Parameters": { + ...originalTemplate.Parameters, + }, + }); }); - test('getParameter() throws an exception when a parameter is not found', () => { + 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(() => { From b10a1ed08ba7171ea5394ed8f5baa36f4d32f319 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 19 Jun 2020 19:08:41 -0400 Subject: [PATCH 19/32] fixed spacing --- packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 7906bd92c5cb5..600a97d65f991 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -40,6 +40,7 @@ export class CfnInclude extends core.CfnElement { for (const logicalId of Object.keys(this.template.Parameters || {})) { this.createParameter(logicalId); } + // first, instantiate the conditions for (const conditionName of Object.keys(this.template.Conditions || {})) { this.createCondition(conditionName); From 685d57dea6100127077ac7157fc7851203aba18d Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Mon, 29 Jun 2020 14:03:00 -0400 Subject: [PATCH 20/32] added outputs array --- packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 49c5a769de73e..fed550419fd68 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -24,6 +24,7 @@ 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 outputs: { [outputName: string]: core.CfnOutput } = {}; private readonly template: any; private readonly preserveLogicalIds: boolean; From e334814a2bef242e07e827b107dadc7b27791da0 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 17:47:50 -0400 Subject: [PATCH 21/32] added support for retrieving and modifying outputs --- .../cloudformation-include/lib/cfn-include.ts | 62 +++++++++++++++++- .../outputs-with-references.json | 42 +++++++++++++ .../test/test-templates/outputs.json | 28 +++++++++ .../test/valid-templates.test.ts | 63 +++++++++++++++++++ packages/@aws-cdk/core/lib/cfn-output.ts | 40 ++++++++++-- 5 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index fed550419fd68..879c5b5082402 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -2,6 +2,7 @@ import * as core from '@aws-cdk/core'; import * as cfn_parse from '@aws-cdk/core/lib/cfn-parse'; import * as cfn_type_to_l1_mapping from './cfn-type-to-l1-mapping'; import * as futils from './file-utils'; +import { CfnCondition } from '@aws-cdk/core'; /** * Construction properties of {@link CfnInclude}. @@ -24,7 +25,7 @@ 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 outputs: { [outputName: string]: core.CfnOutput } = {}; + private readonly outputs: { [logicalId: string]: core.CfnOutput } = {}; private readonly template: any; private readonly preserveLogicalIds: boolean; @@ -47,6 +48,10 @@ export class CfnInclude extends core.CfnElement { this.createCondition(conditionName); } + for (const logicalId of Object.keys(this.template.Outputs || {})) { + this.createOutput(logicalId); + } + // instantiate all resources as CDK L1 objects for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); @@ -113,14 +118,32 @@ export class CfnInclude extends core.CfnElement { return ret; } + /** + * Returns the CfnOutput object from the 'Outputs' + * 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 outputName the name of the output to retrieve + */ + public getOutput(outputName: string): core.CfnOutput { + const ret = this.outputs[outputName]; + if (!ret) { + throw new Error(`Output with logical ID '${outputName}' 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, Resources, and Parameters, which will be taken care of by the created L1s - if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters') { + // except Conditions, Resources, Parameters, and Outputs which will be taken care of by the created L1s + if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters' && section !== 'Outputs') { ret[section] = this.template[section]; } } @@ -153,6 +176,39 @@ export class CfnInclude extends core.CfnElement { this.parameters[logicalId] = cfnParameter; } + private createOutput(logicalId: string): void { + const self = this; + const expression = new cfn_parse.CfnParser({ + finder: { + findResource(lId: string): core.CfnResource | undefined { + if (!(lId in (self.template.Resources || {}))) { + return undefined; + } + return self.getOrCreateResource(lId); + }, + findRefTarget(elementName: string): core.CfnElement | undefined { + if (elementName in self.parameters) { + return self.parameters[elementName]; + } + + return this.findResource(elementName); + }, + findCondition(): undefined { + return undefined; + }, + }, + }).parseValue(this.template.Outputs[logicalId]); + const cfnOutput = new core.CfnOutput(this, logicalId, { + value: expression.Value, + description: expression.Description, + exportName: expression.Export, + condition: expression.Condition ? self.getCondition(expression.Condition) as CfnCondition : undefined + }); + + cfnOutput.overrideLogicalId(logicalId); + this.outputs[logicalId] = cfnOutput; + } + 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/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json new file mode 100644 index 0000000000000..80518492651e1 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -0,0 +1,42 @@ +{ + "Conditions": { + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Parameters": { + "MyParam": { + "Type": "String" + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "my-referenced-bucket-name" + }, + "Condition": "AlwaysFalseCond" + } + }, + "Outputs": { + "Output1": { + "Value": { "Ref": "Bucket" }, + "Description": { "Ref": "MyParam" }, + "Condition": "AlwaysFalseCond" + }, + "Output2": { + "Value": { + "Fn::GetAtt": [ + "Bucket", + "Arn" + ] + }, + "Condition": "AlwaysFalseCond" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json new file mode 100644 index 0000000000000..b2343a4ce3145 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json @@ -0,0 +1,28 @@ +{ + "Conditions": { + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "my-referenced-bucket-name" + } + } + }, + "Outputs": { + "Output1": { + "Description": "the description of the output value", + "Value": "the output value", + "Export": "the export", + "Condition": "AlwaysFalseCond" + } + } +} 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 6994a77f6bff1..2032fe6ec473f 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -6,6 +6,7 @@ import * as core from '@aws-cdk/core'; import * as path from 'path'; import * as inc from '../lib'; import * as futils from '../lib/file-utils'; +import { CfnCondition } from '@aws-cdk/core'; // tslint:disable:object-literal-key-quotes /* eslint-disable quotes */ @@ -414,6 +415,68 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-resource-type.json'); }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); + + test("can ingest a template that contains outputs and modify them", () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs.json'); + const output = cfnTemplate.getOutput('Output1'); + const bucket = cfnTemplate.getResource('Bucket'); + + output.getValue(); + output.setValue('a mutated value'); + output.setDescription(undefined); + output.setExport("an export"); + output.setCondition(new core.CfnCondition(stack, 'MyCondition', {})); + + new core.CfnOutput(stack, 'Output2', { + value: bucket.ref, + description: "a description", + }); + + const originalTemplate = loadTestFileToJsObject('outputs.json'); + + expect(stack).toMatchTemplate({ + "Conditions": { + ...originalTemplate.Conditions, + }, + "Outputs": { + "Output1": { + "Value": "a mutated value", + "Export": { + "Name": "an export", + }, + "Condition": "MyCondition", + }, + "Output2": { + "Value": { + "Ref": "Bucket", + }, + "Description": "a description", + }, + }, + "Resources": { + ...originalTemplate.Resources, + }, + }); + }); + + test("can ingest a template that contains outputs and get those outputs", () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs.json'); + const output = cfnTemplate.getOutput('Output1'); + + expect(output.getDescription()).toBe("the description of the output value"); + expect(output.getValue()).toBe("the output value"); + expect(output.getExport()).toBe("the export"); + expect(output.getCondition()).toBe(cfnTemplate.getCondition('AlwaysFalseCond')); + }); + + test('can ingest a template with outputs that reference resources', () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); + + expect(stack).toMatchTemplate(loadTestFileToJsObject('outputs-with-references.json')); + expect(() => { + cfnTemplate.getOutput('FakeOutput'); + }).toThrow(/Output with logical ID 'FakeOutput' was not found in the template/); + }); }); interface IncludeTestTemplateProps { diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index d99622badb1ef..6806c456ed01e 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -36,10 +36,10 @@ export interface CfnOutputProps { } export class CfnOutput extends CfnElement { - private readonly _description?: string; - private readonly _condition?: CfnCondition; - private readonly _value?: any; - private readonly _export?: string; + private _description?: string; + private _condition?: CfnCondition; + private _value?: any; + private _export?: string; /** * Creates an CfnOutput value for this stack. @@ -59,6 +59,38 @@ export class CfnOutput extends CfnElement { this._export = props.exportName; } + public getDescription(): string | undefined { + return this._description; + } + + public getValue(): any { + return this._value; + } + + public getCondition(): CfnCondition | undefined { + return this._condition; + } + + public getExport(): string | undefined { + return this._export; + } + + public setDescription(newDescription: string | undefined): void { + this._description = newDescription; + } + + setValue(newValue: any): void { + this._value = newValue; + } + + public setCondition(newCondition: CfnCondition | undefined): void { + this._condition = newCondition; + } + + public setExport(newExport: string): void { + this._export = newExport; + } + /** * @internal */ From 34d5c34a90e7ada88d20ae6e7c8b34ab6e2b2452 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 17:53:10 -0400 Subject: [PATCH 22/32] fixed linter issues --- .../cloudformation-include/lib/cfn-include.ts | 13 ++++++------- .../test/valid-templates.test.ts | 3 +-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 879c5b5082402..e186a36d55ccc 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -2,7 +2,6 @@ import * as core from '@aws-cdk/core'; import * as cfn_parse from '@aws-cdk/core/lib/cfn-parse'; import * as cfn_type_to_l1_mapping from './cfn-type-to-l1-mapping'; import * as futils from './file-utils'; -import { CfnCondition } from '@aws-cdk/core'; /** * Construction properties of {@link CfnInclude}. @@ -123,15 +122,15 @@ export class CfnInclude extends core.CfnElement { * 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, + * If an Output with the given name is not present in the template, * throws an exception. * - * @param outputName the name of the output to retrieve + * @param logicalId the name of the output to retrieve */ - public getOutput(outputName: string): core.CfnOutput { - const ret = this.outputs[outputName]; + public getOutput(logicalId: string): core.CfnOutput { + const ret = this.outputs[logicalId]; if (!ret) { - throw new Error(`Output with logical ID '${outputName}' was not found in the template`); + throw new Error(`Output with logical ID '${logicalId}' was not found in the template`); } return ret; } @@ -202,7 +201,7 @@ export class CfnInclude extends core.CfnElement { value: expression.Value, description: expression.Description, exportName: expression.Export, - condition: expression.Condition ? self.getCondition(expression.Condition) as CfnCondition : undefined + condition: expression.Condition ? self.getCondition(expression.Condition) : undefined, }); cfnOutput.overrideLogicalId(logicalId); 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 2032fe6ec473f..5895d1ba68b09 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -6,7 +6,6 @@ import * as core from '@aws-cdk/core'; import * as path from 'path'; import * as inc from '../lib'; import * as futils from '../lib/file-utils'; -import { CfnCondition } from '@aws-cdk/core'; // tslint:disable:object-literal-key-quotes /* eslint-disable quotes */ @@ -473,7 +472,7 @@ describe('CDK Include', () => { const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); expect(stack).toMatchTemplate(loadTestFileToJsObject('outputs-with-references.json')); - expect(() => { + expect(() => { cfnTemplate.getOutput('FakeOutput'); }).toThrow(/Output with logical ID 'FakeOutput' was not found in the template/); }); From 95badbeb3304c25660c1a42fd0943c497eb219c0 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 17:56:16 -0400 Subject: [PATCH 23/32] updated README --- packages/@aws-cdk/cloudformation-include/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 68f7b0e2a8b12..17d009e001f11 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -135,7 +135,7 @@ All items unchecked below are currently not supported. - [x] Resources - [x] Parameters - [x] Conditions -- [ ] Outputs +- [x] Outputs ### [Resource attributes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-attribute-reference.html): From fa39d72177a3d50c41c8c191e1b4edcb931a58a9 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 18:06:23 -0400 Subject: [PATCH 24/32] updated documentation --- .../cloudformation-include/lib/cfn-include.ts | 1 - packages/@aws-cdk/core/lib/cfn-output.ts | 30 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index e186a36d55ccc..ebc6d7360960a 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -189,7 +189,6 @@ export class CfnInclude extends core.CfnElement { if (elementName in self.parameters) { return self.parameters[elementName]; } - return this.findResource(elementName); }, findCondition(): undefined { diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index 6806c456ed01e..a66308592ef48 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -59,34 +59,62 @@ export class CfnOutput extends CfnElement { this._export = props.exportName; } + /** + * Returns the description of this Output + */ public getDescription(): string | undefined { return this._description; } + /** + * Returns the value of this Output + */ public getValue(): any { return this._value; } + /** + * Returns the condition of this Output + */ public getCondition(): CfnCondition | undefined { return this._condition; } + /** + * Returns the export of this Output + */ public getExport(): string | undefined { return this._export; } + /** + * Sets this output's description to the parameter + * @param newDescription the description to update this Output's description to + */ public setDescription(newDescription: string | undefined): void { this._description = newDescription; } - setValue(newValue: any): void { + /** + * Sets this output's value to the parameter + * @param newValue the value to update this Output's value to + */ + public setValue(newValue: any): void { this._value = newValue; } + /** + * Sets this output's condition to the parameter + * @param newCondition the condition to update this Output's condition to + */ public setCondition(newCondition: CfnCondition | undefined): void { this._condition = newCondition; } + /** + * Sets this output's export to the parameter + * @param newRxport the export to update this Output's export to + */ public setExport(newExport: string): void { this._export = newExport; } From a3940e340ee33e982e75fed1feccb6f571f1787a Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 18:11:44 -0400 Subject: [PATCH 25/32] removed unneeded line in tests --- .../@aws-cdk/cloudformation-include/test/valid-templates.test.ts | 1 - 1 file changed, 1 deletion(-) 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 5895d1ba68b09..ce44d8ceb3d73 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -420,7 +420,6 @@ describe('CDK Include', () => { const output = cfnTemplate.getOutput('Output1'); const bucket = cfnTemplate.getResource('Bucket'); - output.getValue(); output.setValue('a mutated value'); output.setDescription(undefined); output.setExport("an export"); From fbf17f2f2df09d80f04ea5a7a483d2c2b49ae0d3 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 18:13:36 -0400 Subject: [PATCH 26/32] added newline --- .../test/test-templates/outputs-with-references.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json index 80518492651e1..8742eb36a819f 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -39,4 +39,4 @@ "Condition": "AlwaysFalseCond" } } -} \ No newline at end of file +} From c719e85908198fc43febd0cb2203a024334d819b Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 30 Jun 2020 21:41:42 -0400 Subject: [PATCH 27/32] incorporated PR requests --- .../@aws-cdk/cloudformation-include/README.md | 18 +++++++ .../cloudformation-include/lib/cfn-include.ts | 30 +++++------ .../outputs-with-references.json | 23 ++++---- .../test/test-templates/outputs.json | 28 ---------- .../test/valid-templates.test.ts | 54 ++++++++++--------- packages/@aws-cdk/core/lib/cfn-output.ts | 48 ++++++++--------- 6 files changed, 91 insertions(+), 110 deletions(-) delete mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 17d009e001f11..1547228dafcfc 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -124,6 +124,24 @@ and any changes you make to it will be reflected in the resulting template: condition.expression = core.Fn.conditionEquals(1, 2); ``` +## Outputs + +If your template uses [CloudFormation Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html), +you can retrieve them from your template: + +```typescript +import * as core from '@aws-cdk/core'; + +const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); +``` + +The `CfnOutput` object is mutable, +and any changes you make to it will be reflected in the resulting template: + +```typescript +output.value = "some new value"); +``` + ## Known limitations This module is still in its early, experimental stage, diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index ebc6d7360960a..9f37f7b90ffb1 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -47,14 +47,14 @@ export class CfnInclude extends core.CfnElement { this.createCondition(conditionName); } - for (const logicalId of Object.keys(this.template.Outputs || {})) { - this.createOutput(logicalId); - } - // instantiate all resources as CDK L1 objects for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); } + + for (const logicalId of Object.keys(this.template.Outputs || {})) { + this.createOutput(logicalId); + } } /** @@ -177,19 +177,13 @@ export class CfnInclude extends core.CfnElement { private createOutput(logicalId: string): void { const self = this; - const expression = new cfn_parse.CfnParser({ + const outputAttributes = new cfn_parse.CfnParser({ finder: { - findResource(lId: string): core.CfnResource | undefined { - if (!(lId in (self.template.Resources || {}))) { - return undefined; - } - return self.getOrCreateResource(lId); + findResource(lId): core.CfnResource | undefined { + return self.resources[lId]; }, findRefTarget(elementName: string): core.CfnElement | undefined { - if (elementName in self.parameters) { - return self.parameters[elementName]; - } - return this.findResource(elementName); + return self.resources[elementName] ?? self.parameters[elementName]; }, findCondition(): undefined { return undefined; @@ -197,10 +191,10 @@ export class CfnInclude extends core.CfnElement { }, }).parseValue(this.template.Outputs[logicalId]); const cfnOutput = new core.CfnOutput(this, logicalId, { - value: expression.Value, - description: expression.Description, - exportName: expression.Export, - condition: expression.Condition ? self.getCondition(expression.Condition) : undefined, + value: outputAttributes.Value, + description: outputAttributes.Description, + exportName: outputAttributes.Export, + condition: outputAttributes.Condition ? self.getCondition(outputAttributes.Condition) : undefined, }); cfnOutput.overrideLogicalId(logicalId); diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json index 8742eb36a819f..25666c8354155 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -16,27 +16,22 @@ }, "Resources": { "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": "my-referenced-bucket-name" - }, - "Condition": "AlwaysFalseCond" + "Type": "AWS::S3::Bucket" } }, "Outputs": { "Output1": { - "Value": { "Ref": "Bucket" }, - "Description": { "Ref": "MyParam" }, - "Condition": "AlwaysFalseCond" - }, - "Output2": { "Value": { - "Fn::GetAtt": [ - "Bucket", - "Arn" + "Fn::Join": [ "", [ + { "Ref": "MyParam" }, + { "Fn::GetAtt": [ "Bucket", "Arn" ] } + ] ] }, + "Description": { + "Ref": "Bucket" + }, "Condition": "AlwaysFalseCond" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json deleted file mode 100644 index b2343a4ce3145..0000000000000 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "Conditions": { - "AlwaysFalseCond": { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "completely-made-up-region" - ] - } - }, - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": "my-referenced-bucket-name" - } - } - }, - "Outputs": { - "Output1": { - "Description": "the description of the output value", - "Value": "the output value", - "Export": "the export", - "Condition": "AlwaysFalseCond" - } - } -} 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 ce44d8ceb3d73..161360734d279 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -396,8 +396,8 @@ describe('CDK Include', () => { ], }, }, - "Metadata" : { - "Object1" : "Location1", + "Metadata": { + "Object1": "Location1", "KeyRef": { "Ref": "TotallyDifferentKey" }, "KeyArn": { "Fn::GetAtt": ["TotallyDifferentKey", "Arn"] }, }, @@ -416,25 +416,36 @@ describe('CDK Include', () => { }); test("can ingest a template that contains outputs and modify them", () => { - const cfnTemplate = includeTestTemplate(stack, 'outputs.json'); + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); const output = cfnTemplate.getOutput('Output1'); - const bucket = cfnTemplate.getResource('Bucket'); - output.setValue('a mutated value'); - output.setDescription(undefined); - output.setExport("an export"); - output.setCondition(new core.CfnCondition(stack, 'MyCondition', {})); + const ifCond = core.Fn.conditionIf("AlwaysFalseCond", 'AWS::NoValue', 'AWS::NoValue'); - new core.CfnOutput(stack, 'Output2', { - value: bucket.ref, - description: "a description", + output.value = 'a mutated value'; + output.description = undefined; + output.exportName = "an export"; + output.condition = new core.CfnCondition(stack, 'MyCondition', { + expression: ifCond, }); - const originalTemplate = loadTestFileToJsObject('outputs.json'); + const originalTemplate = loadTestFileToJsObject('outputs-with-references.json'); expect(stack).toMatchTemplate({ "Conditions": { ...originalTemplate.Conditions, + "MyCondition": { + "Fn::If": [ + "AlwaysFalseCond", + "AWS::NoValue", + "AWS::NoValue", + ], + }, + }, + "Parameters": { + ...originalTemplate.Parameters, + }, + "Resources": { + ...originalTemplate.Resources, }, "Outputs": { "Output1": { @@ -444,27 +455,18 @@ describe('CDK Include', () => { }, "Condition": "MyCondition", }, - "Output2": { - "Value": { - "Ref": "Bucket", - }, - "Description": "a description", - }, - }, - "Resources": { - ...originalTemplate.Resources, }, }); }); test("can ingest a template that contains outputs and get those outputs", () => { - const cfnTemplate = includeTestTemplate(stack, 'outputs.json'); + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); const output = cfnTemplate.getOutput('Output1'); - expect(output.getDescription()).toBe("the description of the output value"); - expect(output.getValue()).toBe("the output value"); - expect(output.getExport()).toBe("the export"); - expect(output.getCondition()).toBe(cfnTemplate.getCondition('AlwaysFalseCond')); + expect(output.condition).toBe(cfnTemplate.getCondition('AlwaysFalseCond')); + expect(output.description).toBeDefined(); + expect(output.value).toBeDefined(); + expect(output.exportName).toBeUndefined(); }); test('can ingest a template with outputs that reference resources', () => { diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index a66308592ef48..9d988ec9a6d92 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -62,61 +62,61 @@ export class CfnOutput extends CfnElement { /** * Returns the description of this Output */ - public getDescription(): string | undefined { + public get description() { return this._description; } /** - * Returns the value of this Output + * Sets this output's description to the parameter + * @param newDescription the description to update this Output's description to */ - public getValue(): any { - return this._value; + public set description(description: string | undefined) { + this._description = description; } /** - * Returns the condition of this Output + * Returns the value of this Output */ - public getCondition(): CfnCondition | undefined { - return this._condition; + public get value() { + return this._value; } /** - * Returns the export of this Output + * Sets this output's value to the parameter + * @param newValue the value to update this Output's value to */ - public getExport(): string | undefined { - return this._export; + public set value(value: any) { + this._value = value; } /** - * Sets this output's description to the parameter - * @param newDescription the description to update this Output's description to + * Returns the condition of this Output */ - public setDescription(newDescription: string | undefined): void { - this._description = newDescription; + public get condition() { + return this._condition; } /** - * Sets this output's value to the parameter - * @param newValue the value to update this Output's value to + * Sets this output's condition to the parameter + * @param newCondition the condition to update this Output's condition to */ - public setValue(newValue: any): void { - this._value = newValue; + public set condition(condition: CfnCondition | undefined) { + this._condition = condition; } /** - * Sets this output's condition to the parameter - * @param newCondition the condition to update this Output's condition to + * Returns the export of this Output */ - public setCondition(newCondition: CfnCondition | undefined): void { - this._condition = newCondition; + public get exportName() { + return this._export; } /** * Sets this output's export to the parameter * @param newRxport the export to update this Output's export to */ - public setExport(newExport: string): void { - this._export = newExport; + public set exportName(exportName: string | undefined) { + this._export = exportName; } /** From f05aae1b3883c3a7d9f19ecad8f4bcb01bc55574 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Wed, 1 Jul 2020 12:01:39 -0400 Subject: [PATCH 28/32] updated the example in the readme --- packages/@aws-cdk/cloudformation-include/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 1547228dafcfc..01be06067dde4 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -139,7 +139,7 @@ The `CfnOutput` object is mutable, and any changes you make to it will be reflected in the resulting template: ```typescript -output.value = "some new value"); +output.value = cfnBucket.attrArn; ``` ## Known limitations From 69223d2acf488eb02095ae0aad465c86e0265179 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Wed, 1 Jul 2020 15:19:46 -0400 Subject: [PATCH 29/32] added support for common-named outputs. Fixed a bug in the export name output --- .../cloudformation-include/lib/cfn-include.ts | 10 ++++++---- .../outputs-with-references.json | 8 +++++++- .../test/valid-templates.test.ts | 2 +- packages/@aws-cdk/core/lib/cfn-output.ts | 18 +++++++++--------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 9f37f7b90ffb1..1805818863f5f 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -52,8 +52,10 @@ export class CfnInclude extends core.CfnElement { this.getOrCreateResource(logicalId); } + const outputScope = new core.Construct(this, '$Ouputs'); + for (const logicalId of Object.keys(this.template.Outputs || {})) { - this.createOutput(logicalId); + this.createOutput(logicalId, outputScope); } } @@ -175,7 +177,7 @@ export class CfnInclude extends core.CfnElement { this.parameters[logicalId] = cfnParameter; } - private createOutput(logicalId: string): void { + private createOutput(logicalId: string, scope: core.Construct): void { const self = this; const outputAttributes = new cfn_parse.CfnParser({ finder: { @@ -190,10 +192,10 @@ export class CfnInclude extends core.CfnElement { }, }, }).parseValue(this.template.Outputs[logicalId]); - const cfnOutput = new core.CfnOutput(this, logicalId, { + const cfnOutput = new core.CfnOutput(scope, logicalId, { value: outputAttributes.Value, description: outputAttributes.Description, - exportName: outputAttributes.Export, + exportName: outputAttributes.Export ? outputAttributes.Export.Name: undefined, condition: outputAttributes.Condition ? self.getCondition(outputAttributes.Condition) : undefined, }); diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json index 25666c8354155..48197c1938c8e 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -17,6 +17,9 @@ "Resources": { "Bucket": { "Type": "AWS::S3::Bucket" + }, + "Output1": { + "Type": "AWS::S3::Bucket" } }, "Outputs": { @@ -31,7 +34,10 @@ "Description": { "Ref": "Bucket" }, - "Condition": "AlwaysFalseCond" + "Condition": "AlwaysFalseCond", + "Export": { + "Name": "Bucket" + } } } } \ 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 161360734d279..f28029d9cd396 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -466,7 +466,7 @@ describe('CDK Include', () => { expect(output.condition).toBe(cfnTemplate.getCondition('AlwaysFalseCond')); expect(output.description).toBeDefined(); expect(output.value).toBeDefined(); - expect(output.exportName).toBeUndefined(); + expect(output.exportName).toBeDefined(); }); test('can ingest a template with outputs that reference resources', () => { diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index 9d988ec9a6d92..387c5216ead91 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -39,7 +39,7 @@ export class CfnOutput extends CfnElement { private _description?: string; private _condition?: CfnCondition; private _value?: any; - private _export?: string; + private _exportName?: string; /** * Creates an CfnOutput value for this stack. @@ -56,7 +56,7 @@ export class CfnOutput extends CfnElement { this._description = props.description; this._value = props.value; this._condition = props.condition; - this._export = props.exportName; + this._exportName = props.exportName; } /** @@ -68,7 +68,7 @@ export class CfnOutput extends CfnElement { /** * Sets this output's description to the parameter - * @param newDescription the description to update this Output's description to + * @param description the description to update this Output's description to */ public set description(description: string | undefined) { this._description = description; @@ -83,7 +83,7 @@ export class CfnOutput extends CfnElement { /** * Sets this output's value to the parameter - * @param newValue the value to update this Output's value to + * @param value the value to update this Output's value to */ public set value(value: any) { this._value = value; @@ -98,7 +98,7 @@ export class CfnOutput extends CfnElement { /** * Sets this output's condition to the parameter - * @param newCondition the condition to update this Output's condition to + * @param condition the condition to update this Output's condition to */ public set condition(condition: CfnCondition | undefined) { this._condition = condition; @@ -108,15 +108,15 @@ export class CfnOutput extends CfnElement { * Returns the export of this Output */ public get exportName() { - return this._export; + return this._exportName; } /** * Sets this output's export to the parameter - * @param newRxport the export to update this Output's export to + * @param exportName the export to update this Output's export to */ public set exportName(exportName: string | undefined) { - this._export = exportName; + this._exportName = exportName; } /** @@ -128,7 +128,7 @@ export class CfnOutput extends CfnElement { [this.logicalId]: { Description: this._description, Value: this._value, - Export: this._export != null ? { Name: this._export } : undefined, + Export: this._exportName != null ? { Name: this._exportName } : undefined, Condition: this._condition ? this._condition.logicalId : undefined, }, }, From 0c551800738d13aa5ef94928dac376a1ec6c1439 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Wed, 1 Jul 2020 17:41:47 -0400 Subject: [PATCH 30/32] added a negative test case and a new error message if an output references a nonexistant condition --- .../cloudformation-include/lib/cfn-include.ts | 13 +++++++++-- .../test/invalid-templates.test.ts | 6 +++++ ...put-referencing-nonexistant-condition.json | 7 ++++++ .../outputs-with-references.json | 8 ++++--- .../test/valid-templates.test.ts | 22 +++++++++++-------- 5 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 1805818863f5f..035631fd282d5 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -195,8 +195,17 @@ export class CfnInclude extends core.CfnElement { const cfnOutput = new core.CfnOutput(scope, logicalId, { value: outputAttributes.Value, description: outputAttributes.Description, - exportName: outputAttributes.Export ? outputAttributes.Export.Name: undefined, - condition: outputAttributes.Condition ? self.getCondition(outputAttributes.Condition) : undefined, + exportName: outputAttributes.Export ? outputAttributes.Export.Name : undefined, + condition: (() => { + if (!outputAttributes.Condition) { + return undefined; + } else if (this.conditions[outputAttributes.Condition]) { + return self.getCondition(outputAttributes.Condition); + } + + throw new Error(`Output with name '${logicalId}' refers to a Condition with name\ + '${outputAttributes.Condition}' which was not found in this template`); + })(), }); cfnOutput.overrideLogicalId(logicalId); diff --git a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts index eb04ef059e15e..0d1ba70795e5d 100644 --- a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts @@ -76,6 +76,12 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'getting-attribute-of-a-non-existent-resource.json'); }).toThrow(/Resource used in GetAtt expression with logical ID: 'DoesNotExist' not found/); }); + + test("throws a validation exception when an output references a condition that doesn't exist", () => { + expect(() => { + includeTestTemplate(stack, 'output-referencing-nonexistant-condition.json'); + }).toThrow(/Output with name 'SomeOutput' refers to a Condition with name 'NonexistantCondition' which was not found in this template/); + }); }); function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json new file mode 100644 index 0000000000000..fbb06694b8ae6 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json @@ -0,0 +1,7 @@ +{ + "Outputs": { + "SomeOutput": { + "Condition": "NonexistantCondition" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json index 48197c1938c8e..206b1d1d15009 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -26,9 +26,8 @@ "Output1": { "Value": { "Fn::Join": [ "", [ - { "Ref": "MyParam" }, - { "Fn::GetAtt": [ "Bucket", "Arn" ] } - ] + { "Ref": "MyParam" }, + { "Fn::GetAtt": [ "Bucket", "Arn" ] } ] ] }, "Description": { @@ -38,6 +37,9 @@ "Export": { "Name": "Bucket" } + }, + "OutputWithNoCondition": { + "Value": "some-value" } } } \ 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 f28029d9cd396..13b7d050970a0 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -251,7 +251,7 @@ describe('CDK Include', () => { ); }); - test("correctly parses templates with parameters", () => { + test('correctly parses templates with parameters', () => { const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json'); const param = cfnTemplate.getParameter('BucketName'); new s3.CfnBucket(stack, 'NewBucket', { @@ -415,17 +415,15 @@ describe('CDK Include', () => { }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); - test("can ingest a template that contains outputs and modify them", () => { + test('can ingest a template that contains outputs and modify them', () => { const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); - const output = cfnTemplate.getOutput('Output1'); - - const ifCond = core.Fn.conditionIf("AlwaysFalseCond", 'AWS::NoValue', 'AWS::NoValue'); + const output = cfnTemplate.getOutput('Output1'); output.value = 'a mutated value'; output.description = undefined; - output.exportName = "an export"; + output.exportName = 'an export'; output.condition = new core.CfnCondition(stack, 'MyCondition', { - expression: ifCond, + expression: core.Fn.conditionIf('AlwaysFalseCond', 'AWS::NoValue', 'AWS::NoValue'), }); const originalTemplate = loadTestFileToJsObject('outputs-with-references.json'); @@ -455,11 +453,14 @@ describe('CDK Include', () => { }, "Condition": "MyCondition", }, + "OutputWithNoCondition": { + "Value": "some-value", + }, }, }); }); - test("can ingest a template that contains outputs and get those outputs", () => { + test('can ingest a template that contains outputs and get those outputs', () => { const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); const output = cfnTemplate.getOutput('Output1'); @@ -467,12 +468,15 @@ describe('CDK Include', () => { expect(output.description).toBeDefined(); expect(output.value).toBeDefined(); expect(output.exportName).toBeDefined(); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('outputs-with-references.json'), + ); }); test('can ingest a template with outputs that reference resources', () => { const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); - expect(stack).toMatchTemplate(loadTestFileToJsObject('outputs-with-references.json')); expect(() => { cfnTemplate.getOutput('FakeOutput'); }).toThrow(/Output with logical ID 'FakeOutput' was not found in the template/); From 32a241a516948811b84d2664a90105781b792e63 Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Fri, 31 Jul 2020 15:18:15 -0400 Subject: [PATCH 31/32] fixed the CfnOutput comment docs --- packages/@aws-cdk/core/lib/cfn-output.ts | 36 +++++++++++------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index 387c5216ead91..3f2784f890073 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -60,61 +60,57 @@ export class CfnOutput extends CfnElement { } /** - * Returns the description of this Output + * A String type that describes the output value. + * The description can be a maximum of 4 K in length. + * + * @default - No description. */ public get description() { return this._description; } - /** - * Sets this output's description to the parameter - * @param description the description to update this Output's description to - */ public set description(description: string | undefined) { this._description = description; } /** - * Returns the value of this Output + * The value of the property returned by the aws cloudformation describe-stacks command. + * The value of an output can include literals, parameter references, pseudo-parameters, + * a mapping value, or intrinsic functions. */ public get value() { return this._value; } - /** - * Sets this output's value to the parameter - * @param value the value to update this Output's value to - */ public set value(value: any) { this._value = value; } /** - * Returns the condition of this Output + * A condition to associate with this output value. If the condition evaluates + * to `false`, this output value will not be included in the stack. + * + * @default - No condition is associated with the output. */ public get condition() { return this._condition; } - /** - * Sets this output's condition to the parameter - * @param condition the condition to update this Output's condition to - */ public set condition(condition: CfnCondition | undefined) { this._condition = condition; } /** - * Returns the export of this Output + * The name used to export the value of this output across stacks. + * + * To import the value from another stack, use `Fn.importValue(exportName)`. + * + * @default - the output is not exported */ public get exportName() { return this._exportName; } - /** - * Sets this output's export to the parameter - * @param exportName the export to update this Output's export to - */ public set exportName(exportName: string | undefined) { this._exportName = exportName; } From 02d907ba3c00af3b84947f66c57c95d4fc74393c Mon Sep 17 00:00:00 2001 From: Calvin Combs Date: Tue, 18 Aug 2020 18:02:43 -0400 Subject: [PATCH 32/32] fixed DependsOn array comparison --- .../cloudformation-diff/lib/diff/util.ts | 42 ++++++- .../test/diff-template.test.ts | 113 ++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts index c8fcbf694c17e..02858f802647c 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts @@ -17,7 +17,9 @@ export function deepEqual(lvalue: any, rvalue: any): boolean { if (lvalue === rvalue) { return true; } // allows a numeric 10 and a literal "10" to be equivalent; // this is consistent with CloudFormation. - if (parseFloat(lvalue) === parseFloat(rvalue)) { return true; } + if (((typeof lvalue === 'string') || (typeof rvalue === 'string')) && (parseFloat(lvalue) === parseFloat(rvalue))) { + return true; + } if (typeof lvalue !== typeof rvalue) { return false; } if (Array.isArray(lvalue) !== Array.isArray(rvalue)) { return false; } if (Array.isArray(lvalue) /* && Array.isArray(rvalue) */) { @@ -36,6 +38,7 @@ export function deepEqual(lvalue: any, rvalue: any): boolean { if (keys.length !== Object.keys(rvalue).length) { return false; } for (const key of keys) { if (!rvalue.hasOwnProperty(key)) { return false; } + if (key === 'DependsOn') { return dependsOnEqual(lvalue[key], rvalue[key]); } if (!deepEqual(lvalue[key], rvalue[key])) { return false; } } return true; @@ -45,6 +48,43 @@ export function deepEqual(lvalue: any, rvalue: any): boolean { return false; } +/** + * Compares two arguments to DependsOn for equality. + * + * @param lvalue the left operand of the equality comparison. + * @param rvalue the right operand of the equality comparison. + * + * @returns +true+ if both +lvalue+ and +rvalue+ are equivalent to each other. + */ +function dependsOnEqual(lvalue: any, rvalue: any): boolean { + // allows ['Value'] and 'Value' to be equal + if (Array.isArray(lvalue) !== Array.isArray(rvalue)) { + const array = Array.isArray(lvalue) ? lvalue : rvalue; + const nonArray = Array.isArray(lvalue) ? rvalue : lvalue; + + if (array.length === 1 && deepEqual(array[0], nonArray)) { + return true; + } + return false; + } + + // allows arrays passed to DependsOn to be equivalent irrespective of element order + if (Array.isArray(lvalue) && Array.isArray(rvalue)) { + if (lvalue.length !== rvalue.length) { return false; } + for (let i = 0 ; i < lvalue.length ; i++) { + for (let j = 0 ; j < lvalue.length ; j++) { + if ((!deepEqual(lvalue[i], rvalue[j])) && (j === lvalue.length - 1)) { + return false; + } + break; + } + } + return true; + } + + return false; +} + /** * Produce the differences between two maps, as a map, using a specified diff function. * diff --git a/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts b/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts index e351162863cb3..9aff935252717 100644 --- a/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts +++ b/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts @@ -374,3 +374,116 @@ test('adding and removing quotes from a numeric property causes no changes', () differences = diffTemplate(newTemplate, currentTemplate); expect(differences.resources.differenceCount).toBe(0); }); + +test('single element arrays are equivalent to the single element in DependsOn expressions', () => { + // GIVEN + const currentTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: ['SomeResource'], + }, + }, + }; + + // WHEN + const newTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: 'SomeResource', + }, + }, + }; + + let differences = diffTemplate(currentTemplate, newTemplate); + expect(differences.resources.differenceCount).toBe(0); + + differences = diffTemplate(newTemplate, currentTemplate); + expect(differences.resources.differenceCount).toBe(0); +}); + + +test('array equivalence is independent of element order in DependsOn expressions', () => { + // GIVEN + const currentTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: ['SomeResource', 'AnotherResource'], + }, + }, + }; + + // WHEN + const newTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: ['AnotherResource', 'SomeResource'], + }, + }, + }; + + let differences = diffTemplate(currentTemplate, newTemplate); + expect(differences.resources.differenceCount).toBe(0); + + differences = diffTemplate(newTemplate, currentTemplate); + expect(differences.resources.differenceCount).toBe(0); +}); + +test('arrays of different length are considered unequal in DependsOn expressions', () => { + // GIVEN + const currentTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: ['SomeResource', 'AnotherResource', 'LastResource'], + }, + }, + }; + + // WHEN + const newTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + DependsOn: ['AnotherResource', 'SomeResource'], + }, + }, + }; + + let differences = diffTemplate(currentTemplate, newTemplate); + expect(differences.resources.differenceCount).toBe(1); + + differences = diffTemplate(newTemplate, currentTemplate); + expect(differences.resources.differenceCount).toBe(1); +}); + +test('arrays that differ only in element order are considered unequal outside of DependsOn expressions', () => { + // GIVEN + const currentTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + BucketName: { 'Fn::Select': [0, ['name1', 'name2']] }, + }, + }, + }; + + // WHEN + const newTemplate = { + Resources: { + BucketResource: { + Type: 'AWS::S3::Bucket', + BucketName: { 'Fn::Select': [0, ['name2', 'name1']] }, + }, + }, + }; + + let differences = diffTemplate(currentTemplate, newTemplate); + expect(differences.resources.differenceCount).toBe(1); + + differences = diffTemplate(newTemplate, currentTemplate); + expect(differences.resources.differenceCount).toBe(1); +});