diff --git a/CHANGELOG.v2.alpha.md b/CHANGELOG.v2.alpha.md index 865907d5085c7..e67365cc322e2 100644 --- a/CHANGELOG.v2.alpha.md +++ b/CHANGELOG.v2.alpha.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.50.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.49.1-alpha.0...v2.50.0-alpha.0) (2022-11-01) + + +### Features + +* **synthetics:** aws synthetics runtime version syn-nodejs-puppeteer-3.8 ([#22707](https://github.com/aws/aws-cdk/issues/22707)) ([228c865](https://github.com/aws/aws-cdk/commit/228c86532118b143e365b2268d06ee3a36fcf3a7)) + ## [2.49.1-alpha.0](https://github.com/aws/aws-cdk/compare/v2.49.0-alpha.0...v2.49.1-alpha.0) (2022-10-31) ## [2.49.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.48.0-alpha.0...v2.49.0-alpha.0) (2022-10-27) diff --git a/CHANGELOG.v2.md b/CHANGELOG.v2.md index 980969b915ee8..cae138cbac2f7 100644 --- a/CHANGELOG.v2.md +++ b/CHANGELOG.v2.md @@ -2,6 +2,26 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.50.0](https://github.com/aws/aws-cdk/compare/v2.49.1...v2.50.0) (2022-11-01) + + +### Features + +* **aws-ecs-patterns:** entryPoint and command support within ApplicationLoadBalancedFargateService and ApplicationLoadBalancedEc2Service ([#22609](https://github.com/aws/aws-cdk/issues/22609)) ([6925293](https://github.com/aws/aws-cdk/commit/6925293047ff02fbe68234740327f3513a86ef74)), closes [#17092](https://github.com/aws/aws-cdk/issues/17092) +* **codedeploy:** CodeDeploy deployment group construct for ECS ([#22295](https://github.com/aws/aws-cdk/issues/22295)) ([efd24d1](https://github.com/aws/aws-cdk/commit/efd24d1bb9bc1c113e81e033012d99b7d5f8a146)), closes [#1559](https://github.com/aws/aws-cdk/issues/1559) +* **core:** automatic cross stack, cross region references (under feature flag) ([#22008](https://github.com/aws/aws-cdk/issues/22008)) ([f1b5497](https://github.com/aws/aws-cdk/commit/f1b5497879b4ba117723dad4255082f081d4fec7)) +* **ec2:** Vpc supports reserving space for future AZs ([#22705](https://github.com/aws/aws-cdk/issues/22705)) ([7b51ea9](https://github.com/aws/aws-cdk/commit/7b51ea9ae1e61d57b8ed6b99510cf26d423bb991)) +* **stepfunctions:** add intrinsic functions ([#22431](https://github.com/aws/aws-cdk/issues/22431)) ([8f85b08](https://github.com/aws/aws-cdk/commit/8f85b081724d425f452babe1f38f4cda211c17b9)), closes [#22068](https://github.com/aws/aws-cdk/issues/22068) [#22629](https://github.com/aws/aws-cdk/issues/22629) + + +### Bug Fixes + +* **opensearch:** log group policies ignore incorrect error code on delete ([#22364](https://github.com/aws/aws-cdk/issues/22364)) ([ebba9e3](https://github.com/aws/aws-cdk/commit/ebba9e371c22542a5ae98bbd0e6a2f130eef77d6)) +* revert jsii to version 1.69.0 ([#22715](https://github.com/aws/aws-cdk/issues/22715)) ([0837c1a](https://github.com/aws/aws-cdk/commit/0837c1a6af705474dfe127203c2b99a6ff201d77)) +* **apigateway:** race condition exists between stage and cfnaccount in specrestapi ([#22671](https://github.com/aws/aws-cdk/issues/22671)) ([4cb008b](https://github.com/aws/aws-cdk/commit/4cb008bd6d27a8e3366ea600a8b9027f15ae6dcd)), closes [#18925](https://github.com/aws/aws-cdk/issues/18925) +* **aws-events:** restrict eventbus statementId to 64 characters ([#22296](https://github.com/aws/aws-cdk/issues/22296)) ([fadbfc1](https://github.com/aws/aws-cdk/commit/fadbfc1eb07f4f2daecfe623812fee029c81e31a)), closes [#22120](https://github.com/aws/aws-cdk/issues/22120) [#21808](https://github.com/aws/aws-cdk/issues/21808) +* **stepfunctions-tasks:** athenaStartQueryExecution task generates invalid s3 arn ([#22692](https://github.com/aws/aws-cdk/issues/22692)) ([6e0cb2b](https://github.com/aws/aws-cdk/commit/6e0cb2ba2e1bfb55d183e65c811d4e17a80cc4b8)), closes [#22608](https://github.com/aws/aws-cdk/issues/22608) + ## [2.49.1](https://github.com/aws/aws-cdk/compare/v2.49.0...v2.49.1) (2022-10-31) @@ -32,7 +52,7 @@ All notable changes to this project will be documented in this file. See [standa * **cli:** allow disabling parallel asset publishing ([#22579](https://github.com/aws/aws-cdk/issues/22579)) ([69981ac](https://github.com/aws/aws-cdk/commit/69981ac07b40ce3f690f6c1ad0010b51f29103a6)), closes [#19367](https://github.com/aws/aws-cdk/issues/19367) * **ec2:** Vpc supports allocating CIDR from AWS IPAM ([#22458](https://github.com/aws/aws-cdk/issues/22458)) ([7ed9cd1](https://github.com/aws/aws-cdk/commit/7ed9cd14aa5aaff90badb6438a0941fbca2d370c)) * **eks:** support for Kubernetes version 1.22 ([#22604](https://github.com/aws/aws-cdk/issues/22604)) ([91704aa](https://github.com/aws/aws-cdk/commit/91704aa3632dd6424017ae7aafebda832f309315)), closes [#20263](https://github.com/aws/aws-cdk/issues/20263) -* **rds:** dual-stack mode support ([#22596](https://github.com/aws/aws-cdk/issues/22596)) ([89a7365](https://github.com/aws/aws-cdk/commit/89a73651ccd619b9b1878c40214e4647095803de)), closes [#19525](https://github.com/aws/aws-cdk/issues/19525) +* **rds:** dual-stack mode support ([#22596](https://github.com/aws/aws-cdk/issues/22596)) ([89a7365](https://github.com/aws/aws-cdk/commit/89a73651ccd619b9b1878c40214e4647095803de)) ### Bug Fixes diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 63a554927cfcc..36b3ab7e8809a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -662,16 +662,16 @@ export class SpecRestApi extends RestApiBase { this.restApiRootResourceId = resource.attrRootResourceId; this.root = new RootResource(this, {}, this.restApiRootResourceId); - this._configureDeployment(props); - if (props.domainName) { - this.addDomainName('CustomDomain', props.domainName); - } - const cloudWatchRoleDefault = FeatureFlags.of(this).isEnabled(APIGATEWAY_DISABLE_CLOUDWATCH_ROLE) ? false : true; const cloudWatchRole = props.cloudWatchRole ?? cloudWatchRoleDefault; if (cloudWatchRole) { this._configureCloudWatchRole(resource); } + + this._configureDeployment(props); + if (props.domainName) { + this.addDomainName('CustomDomain', props.domainName); + } } } diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integ.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integ.json index 3a04e2a204bc4..3642781150673 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integ.json @@ -1,11 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "restapi-fromdefinition/DefaultTest": { "stacks": [ "integtest-restapi-fromdefinition-asset" ], - "assertionStack": "restapi-fromdefinition/DefaultTest/DeployAssert" + "assertionStack": "restapi-fromdefinition/DefaultTest/DeployAssert", + "assertionStackName": "restapifromdefinitionDefaultTestDeployAssertDF3B0845" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.assets.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.assets.json index 1ce11bc2039fa..f75337b8eb59e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.assets.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb": { "source": { @@ -14,7 +14,7 @@ } } }, - "b6f4053913c5258c89b2cb1e8d48d0a04346c7b0736f15235a74f8fd890de556": { + "2fcb710210eb75f31b39dd1787cc4271df763ad6b541a188cb417095e1b83aa7": { "source": { "path": "integtest-restapi-fromdefinition-asset.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "b6f4053913c5258c89b2cb1e8d48d0a04346c7b0736f15235a74f8fd890de556.json", + "objectKey": "2fcb710210eb75f31b39dd1787cc4271df763ad6b541a188cb417095e1b83aa7.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.template.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.template.json index 455891fe64b96..d0c86fe82cb02 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.template.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/integtest-restapi-fromdefinition-asset.template.json @@ -57,31 +57,6 @@ ] } }, - "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "myapi4C7BF186" - }, - "Description": "Automatically created by the RestApi construct" - }, - "DependsOn": [ - "myapibooksGETD6B2F597", - "myapibooks51D54548" - ] - }, - "myapiDeploymentStageprod298F01AF": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "RestApiId": { - "Ref": "myapi4C7BF186" - }, - "DeploymentId": { - "Ref": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" - }, - "StageName": "prod" - } - }, "myapiCloudWatchRole095452E5": { "Type": "AWS::IAM::Role", "Properties": { @@ -130,6 +105,34 @@ ], "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" + }, + "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "myapibooksGETD6B2F597", + "myapibooks51D54548" + ] + }, + "myapiDeploymentStageprod298F01AF": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "DeploymentId": { + "Ref": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" + }, + "StageName": "prod" + }, + "DependsOn": [ + "myapiAccountEC421A0A" + ] } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/manifest.json index 9fbd842127260..91f11081bae87 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b6f4053913c5258c89b2cb1e8d48d0a04346c7b0736f15235a74f8fd890de556.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2fcb710210eb75f31b39dd1787cc4271df763ad6b541a188cb417095e1b83aa7.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,34 +57,34 @@ "data": "myapibooksGETD6B2F597" } ], - "/integtest-restapi-fromdefinition-asset/my-api/Deployment/Resource": [ + "/integtest-restapi-fromdefinition-asset/my-api/CloudWatchRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" + "data": "myapiCloudWatchRole095452E5" } ], - "/integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod/Resource": [ + "/integtest-restapi-fromdefinition-asset/my-api/Account": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeploymentStageprod298F01AF" + "data": "myapiAccountEC421A0A" } ], - "/integtest-restapi-fromdefinition-asset/my-api/Endpoint": [ + "/integtest-restapi-fromdefinition-asset/my-api/Deployment/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiEndpoint3628AFE3" + "data": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" } ], - "/integtest-restapi-fromdefinition-asset/my-api/CloudWatchRole/Resource": [ + "/integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiCloudWatchRole095452E5" + "data": "myapiDeploymentStageprod298F01AF" } ], - "/integtest-restapi-fromdefinition-asset/my-api/Account": [ + "/integtest-restapi-fromdefinition-asset/my-api/Endpoint": [ { "type": "aws:cdk:logicalId", - "data": "myapiAccountEC421A0A" + "data": "myapiEndpoint3628AFE3" } ], "/integtest-restapi-fromdefinition-asset/PetsURL": [ @@ -110,15 +110,6 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } - ], - "myapiDeployment92F2CB49fe116fef7f552ff0fc433c9aa3930d2f": [ - { - "type": "aws:cdk:logicalId", - "data": "myapiDeployment92F2CB49fe116fef7f552ff0fc433c9aa3930d2f", - "trace": [ - "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" - ] - } ] }, "displayName": "integtest-restapi-fromdefinition-asset" diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/restapifromdefinitionDefaultTestDeployAssertDF3B0845.assets.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/restapifromdefinitionDefaultTestDeployAssertDF3B0845.assets.json index 3782cf9fbfd06..f2c9a528d1255 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/restapifromdefinitionDefaultTestDeployAssertDF3B0845.assets.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/restapifromdefinitionDefaultTestDeployAssertDF3B0845.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/tree.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/tree.json index f1f93487fda32..d67ec0c01dceb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.asset.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "integtest-restapi-fromdefinition-asset": { @@ -28,8 +28,8 @@ "id": "Stage", "path": "integtest-restapi-fromdefinition-asset/my-api/APIDefinition/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { @@ -157,71 +157,6 @@ "version": "0.0.0" } }, - "Deployment": { - "id": "Deployment", - "path": "integtest-restapi-fromdefinition-asset/my-api/Deployment", - "children": { - "Resource": { - "id": "Resource", - "path": "integtest-restapi-fromdefinition-asset/my-api/Deployment/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", - "aws:cdk:cloudformation:props": { - "restApiId": { - "Ref": "myapi4C7BF186" - }, - "description": "Automatically created by the RestApi construct" - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.Deployment", - "version": "0.0.0" - } - }, - "DeploymentStage.prod": { - "id": "DeploymentStage.prod", - "path": "integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod", - "children": { - "Resource": { - "id": "Resource", - "path": "integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", - "aws:cdk:cloudformation:props": { - "restApiId": { - "Ref": "myapi4C7BF186" - }, - "deploymentId": { - "Ref": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" - }, - "stageName": "prod" - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.CfnStage", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.Stage", - "version": "0.0.0" - } - }, - "Endpoint": { - "id": "Endpoint", - "path": "integtest-restapi-fromdefinition-asset/my-api/Endpoint", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "CloudWatchRole": { "id": "CloudWatchRole", "path": "integtest-restapi-fromdefinition-asset/my-api/CloudWatchRole", @@ -289,6 +224,71 @@ "fqn": "@aws-cdk/aws-apigateway.CfnAccount", "version": "0.0.0" } + }, + "Deployment": { + "id": "Deployment", + "path": "integtest-restapi-fromdefinition-asset/my-api/Deployment", + "children": { + "Resource": { + "id": "Resource", + "path": "integtest-restapi-fromdefinition-asset/my-api/Deployment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "myapi4C7BF186" + }, + "description": "Automatically created by the RestApi construct" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Deployment", + "version": "0.0.0" + } + }, + "DeploymentStage.prod": { + "id": "DeploymentStage.prod", + "path": "integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod", + "children": { + "Resource": { + "id": "Resource", + "path": "integtest-restapi-fromdefinition-asset/my-api/DeploymentStage.prod/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "myapi4C7BF186" + }, + "deploymentId": { + "Ref": "myapiDeployment92F2CB49d7e5c9cfe50a1616e1cef4517d6b8f96" + }, + "stageName": "prod" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnStage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Stage", + "version": "0.0.0" + } + }, + "Endpoint": { + "id": "Endpoint", + "path": "integtest-restapi-fromdefinition-asset/my-api/Endpoint", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } } }, "constructInfo": { @@ -300,22 +300,22 @@ "id": "PetsURL", "path": "integtest-restapi-fromdefinition-asset/PetsURL", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "BooksURL": { "id": "BooksURL", "path": "integtest-restapi-fromdefinition-asset/BooksURL", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } }, "restapi-fromdefinition": { @@ -331,15 +331,15 @@ "path": "restapi-fromdefinition/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "DeployAssert": { "id": "DeployAssert", "path": "restapi-fromdefinition/DefaultTest/DeployAssert", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, @@ -356,8 +356,8 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/inlineapidefinitionDefaultTestDeployAssert923CAC29.assets.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/inlineapidefinitionDefaultTestDeployAssert923CAC29.assets.json index 85b203d3df54e..08ccfa198b17b 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/inlineapidefinitionDefaultTestDeployAssert923CAC29.assets.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/inlineapidefinitionDefaultTestDeployAssert923CAC29.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integ.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integ.json index b854867d69b44..d3fc1877a76d3 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integ.json @@ -1,11 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "inline-api-definition/DefaultTest": { "stacks": [ "integtest-restapi-fromdefinition-inline" ], - "assertionStack": "inline-api-definition/DefaultTest/DeployAssert" + "assertionStack": "inline-api-definition/DefaultTest/DeployAssert", + "assertionStackName": "inlineapidefinitionDefaultTestDeployAssert923CAC29" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.assets.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.assets.json index 29c558f859ea5..73195906af2a1 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.assets.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "43fa955c096081646078278a942ed067da2e8d4eb1d70897d691852a07063bb6": { + "c3150fcf8006f26abb83d3525cd55948b2e4643294d8f6e1e93931bb2469e58b": { "source": { "path": "integtest-restapi-fromdefinition-inline.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "43fa955c096081646078278a942ed067da2e8d4eb1d70897d691852a07063bb6.json", + "objectKey": "c3150fcf8006f26abb83d3525cd55948b2e4643294d8f6e1e93931bb2469e58b.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.template.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.template.json index 66a9a9fbab07c..64f67df83aeb1 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.template.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/integtest-restapi-fromdefinition-inline.template.json @@ -53,27 +53,6 @@ "Name": "my-api" } }, - "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "myapi4C7BF186" - }, - "Description": "Automatically created by the RestApi construct" - } - }, - "myapiDeploymentStageprod298F01AF": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "RestApiId": { - "Ref": "myapi4C7BF186" - }, - "DeploymentId": { - "Ref": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" - }, - "StageName": "prod" - } - }, "myapiCloudWatchRole095452E5": { "Type": "AWS::IAM::Role", "Properties": { @@ -122,6 +101,30 @@ ], "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" + }, + "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "Description": "Automatically created by the RestApi construct" + } + }, + "myapiDeploymentStageprod298F01AF": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "DeploymentId": { + "Ref": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" + }, + "StageName": "prod" + }, + "DependsOn": [ + "myapiAccountEC421A0A" + ] } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/manifest.json index 62d77adcd9ba7..0ff4535137332 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/43fa955c096081646078278a942ed067da2e8d4eb1d70897d691852a07063bb6.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c3150fcf8006f26abb83d3525cd55948b2e4643294d8f6e1e93931bb2469e58b.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,34 +45,34 @@ "data": "myapi4C7BF186" } ], - "/integtest-restapi-fromdefinition-inline/my-api/Deployment/Resource": [ + "/integtest-restapi-fromdefinition-inline/my-api/CloudWatchRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" + "data": "myapiCloudWatchRole095452E5" } ], - "/integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod/Resource": [ + "/integtest-restapi-fromdefinition-inline/my-api/Account": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeploymentStageprod298F01AF" + "data": "myapiAccountEC421A0A" } ], - "/integtest-restapi-fromdefinition-inline/my-api/Endpoint": [ + "/integtest-restapi-fromdefinition-inline/my-api/Deployment/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiEndpoint3628AFE3" + "data": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" } ], - "/integtest-restapi-fromdefinition-inline/my-api/CloudWatchRole/Resource": [ + "/integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiCloudWatchRole095452E5" + "data": "myapiDeploymentStageprod298F01AF" } ], - "/integtest-restapi-fromdefinition-inline/my-api/Account": [ + "/integtest-restapi-fromdefinition-inline/my-api/Endpoint": [ { "type": "aws:cdk:logicalId", - "data": "myapiAccountEC421A0A" + "data": "myapiEndpoint3628AFE3" } ], "/integtest-restapi-fromdefinition-inline/PetsURL": [ diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/tree.json b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/tree.json index ca38b8fcbac8e..2d4a6cc83c010 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.inline.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "integtest-restapi-fromdefinition-inline": { @@ -89,71 +89,6 @@ "version": "0.0.0" } }, - "Deployment": { - "id": "Deployment", - "path": "integtest-restapi-fromdefinition-inline/my-api/Deployment", - "children": { - "Resource": { - "id": "Resource", - "path": "integtest-restapi-fromdefinition-inline/my-api/Deployment/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", - "aws:cdk:cloudformation:props": { - "restApiId": { - "Ref": "myapi4C7BF186" - }, - "description": "Automatically created by the RestApi construct" - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.Deployment", - "version": "0.0.0" - } - }, - "DeploymentStage.prod": { - "id": "DeploymentStage.prod", - "path": "integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod", - "children": { - "Resource": { - "id": "Resource", - "path": "integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", - "aws:cdk:cloudformation:props": { - "restApiId": { - "Ref": "myapi4C7BF186" - }, - "deploymentId": { - "Ref": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" - }, - "stageName": "prod" - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.CfnStage", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.Stage", - "version": "0.0.0" - } - }, - "Endpoint": { - "id": "Endpoint", - "path": "integtest-restapi-fromdefinition-inline/my-api/Endpoint", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "CloudWatchRole": { "id": "CloudWatchRole", "path": "integtest-restapi-fromdefinition-inline/my-api/CloudWatchRole", @@ -221,6 +156,71 @@ "fqn": "@aws-cdk/aws-apigateway.CfnAccount", "version": "0.0.0" } + }, + "Deployment": { + "id": "Deployment", + "path": "integtest-restapi-fromdefinition-inline/my-api/Deployment", + "children": { + "Resource": { + "id": "Resource", + "path": "integtest-restapi-fromdefinition-inline/my-api/Deployment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "myapi4C7BF186" + }, + "description": "Automatically created by the RestApi construct" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Deployment", + "version": "0.0.0" + } + }, + "DeploymentStage.prod": { + "id": "DeploymentStage.prod", + "path": "integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod", + "children": { + "Resource": { + "id": "Resource", + "path": "integtest-restapi-fromdefinition-inline/my-api/DeploymentStage.prod/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "myapi4C7BF186" + }, + "deploymentId": { + "Ref": "myapiDeployment92F2CB49a59bca458e4fac1fcd742212ded42a65" + }, + "stageName": "prod" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnStage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Stage", + "version": "0.0.0" + } + }, + "Endpoint": { + "id": "Endpoint", + "path": "integtest-restapi-fromdefinition-inline/my-api/Endpoint", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } } }, "constructInfo": { @@ -232,14 +232,14 @@ "id": "PetsURL", "path": "integtest-restapi-fromdefinition-inline/PetsURL", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } }, "inline-api-definition": { @@ -255,15 +255,15 @@ "path": "inline-api-definition/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "DeployAssert": { "id": "DeployAssert", "path": "inline-api-definition/DefaultTest/DeployAssert", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, @@ -280,8 +280,8 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/manifest.json index 615066d18ff38..ec86acb97e7a9 100644 --- a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/551655898503491ac7795f943b15b78ff3cd7b88de0661f6b5a09b11ec6976f6.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4b4324bd66c0a5352124c718d0cb3990046afe7ccf1cea94d4488ed7cd67191b.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -141,34 +141,34 @@ "data": "myapiv1booksPOST53E2832E" } ], - "/test-apigateway-spec-restapi/my-api/Deployment/Resource": [ + "/test-apigateway-spec-restapi/my-api/CloudWatchRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeployment92F2CB49e2ce3595b92ff44fad021c2e55149db1" + "data": "myapiCloudWatchRole095452E5" } ], - "/test-apigateway-spec-restapi/my-api/DeploymentStage.beta/Resource": [ + "/test-apigateway-spec-restapi/my-api/Account": [ { "type": "aws:cdk:logicalId", - "data": "myapiDeploymentStagebeta96434BEB" + "data": "myapiAccountEC421A0A" } ], - "/test-apigateway-spec-restapi/my-api/Endpoint": [ + "/test-apigateway-spec-restapi/my-api/Deployment/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiEndpoint3628AFE3" + "data": "myapiDeployment92F2CB49e2ce3595b92ff44fad021c2e55149db1" } ], - "/test-apigateway-spec-restapi/my-api/CloudWatchRole/Resource": [ + "/test-apigateway-spec-restapi/my-api/DeploymentStage.beta/Resource": [ { "type": "aws:cdk:logicalId", - "data": "myapiCloudWatchRole095452E5" + "data": "myapiDeploymentStagebeta96434BEB" } ], - "/test-apigateway-spec-restapi/my-api/Account": [ + "/test-apigateway-spec-restapi/my-api/Endpoint": [ { "type": "aws:cdk:logicalId", - "data": "myapiAccountEC421A0A" + "data": "myapiEndpoint3628AFE3" } ], "/test-apigateway-spec-restapi/my-api/ApiKey/Resource": [ diff --git a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.assets.json b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.assets.json index 193341ba31c89..2bff91cb920f7 100644 --- a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.assets.json +++ b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.assets.json @@ -14,7 +14,7 @@ } } }, - "551655898503491ac7795f943b15b78ff3cd7b88de0661f6b5a09b11ec6976f6": { + "4b4324bd66c0a5352124c718d0cb3990046afe7ccf1cea94d4488ed7cd67191b": { "source": { "path": "test-apigateway-spec-restapi.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "551655898503491ac7795f943b15b78ff3cd7b88de0661f6b5a09b11ec6976f6.json", + "objectKey": "4b4324bd66c0a5352124c718d0cb3990046afe7ccf1cea94d4488ed7cd67191b.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.template.json b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.template.json index 77d098581aa9a..5214b610b9995 100644 --- a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.template.json +++ b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/test-apigateway-spec-restapi.template.json @@ -467,6 +467,55 @@ } } }, + "myapiCloudWatchRole095452E5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "myapiAccountEC421A0A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "myapiCloudWatchRole095452E5", + "Arn" + ] + } + }, + "DependsOn": [ + "myapi4C7BF186" + ], + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "myapiDeployment92F2CB49e2ce3595b92ff44fad021c2e55149db1": { "Type": "AWS::ApiGateway::Deployment", "Properties": { @@ -517,56 +566,10 @@ } ], "StageName": "beta" - } - }, - "myapiCloudWatchRole095452E5": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "apigateway.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" - ] - ] - } - ] - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "myapiAccountEC421A0A": { - "Type": "AWS::ApiGateway::Account", - "Properties": { - "CloudWatchRoleArn": { - "Fn::GetAtt": [ - "myapiCloudWatchRole095452E5", - "Arn" - ] - } }, "DependsOn": [ - "myapi4C7BF186" - ], - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" + "myapiAccountEC421A0A" + ] }, "myapiApiKey43446CCF": { "Type": "AWS::ApiGateway::ApiKey", diff --git a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/tree.json b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/tree.json index 241e0aab49369..10cba98b03d1a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-apigateway/test/spec-restapi.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.102" + "version": "10.1.140" } }, "test-apigateway-spec-restapi": { @@ -759,6 +759,74 @@ "version": "0.0.0" } }, + "CloudWatchRole": { + "id": "CloudWatchRole", + "path": "test-apigateway-spec-restapi/my-api/CloudWatchRole", + "children": { + "Resource": { + "id": "Resource", + "path": "test-apigateway-spec-restapi/my-api/CloudWatchRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Account": { + "id": "Account", + "path": "test-apigateway-spec-restapi/my-api/Account", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Account", + "aws:cdk:cloudformation:props": { + "cloudWatchRoleArn": { + "Fn::GetAtt": [ + "myapiCloudWatchRole095452E5", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnAccount", + "version": "0.0.0" + } + }, "Deployment": { "id": "Deployment", "path": "test-apigateway-spec-restapi/my-api/Deployment", @@ -841,74 +909,6 @@ "version": "0.0.0" } }, - "CloudWatchRole": { - "id": "CloudWatchRole", - "path": "test-apigateway-spec-restapi/my-api/CloudWatchRole", - "children": { - "Resource": { - "id": "Resource", - "path": "test-apigateway-spec-restapi/my-api/CloudWatchRole/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::IAM::Role", - "aws:cdk:cloudformation:props": { - "assumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "apigateway.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "managedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" - ] - ] - } - ] - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-iam.CfnRole", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-iam.Role", - "version": "0.0.0" - } - }, - "Account": { - "id": "Account", - "path": "test-apigateway-spec-restapi/my-api/Account", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ApiGateway::Account", - "aws:cdk:cloudformation:props": { - "cloudWatchRoleArn": { - "Fn::GetAtt": [ - "myapiCloudWatchRole095452E5", - "Arn" - ] - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-apigateway.CfnAccount", - "version": "0.0.0" - } - }, "ApiKey": { "id": "ApiKey", "path": "test-apigateway-spec-restapi/my-api/ApiKey", @@ -1119,7 +1119,7 @@ "path": "apigateway-spec-restapi/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.102" + "version": "10.1.140" } }, "DeployAssert": { diff --git a/packages/@aws-cdk/aws-apigateway/test/stage.test.ts b/packages/@aws-cdk/aws-apigateway/test/stage.test.ts index 41422403c165e..156fa29ca7e0b 100644 --- a/packages/@aws-cdk/aws-apigateway/test/stage.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/stage.test.ts @@ -70,7 +70,7 @@ describe('stage', () => { }); }); - test('stage depends on the CloudWatch role when it exists', () => { + test('RestApi - stage depends on the CloudWatch role when it exists', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: true, deploy: false }); @@ -80,6 +80,23 @@ describe('stage', () => { // WHEN new apigateway.Stage(stack, 'my-stage', { deployment }); + // THEN + Template.fromStack(stack).hasResource('AWS::ApiGateway::Stage', { + DependsOn: ['testapiAccount9B907665'], + }); + }); + + test('SpecRestApi - stage depends on the CloudWatch role when it exists', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.SpecRestApi(stack, 'test-api', { apiDefinition: apigateway.ApiDefinition.fromInline( { foo: 'bar' }) }); + const deployment = new apigateway.Deployment(stack, 'my-deployment', { api }); + api.root.addMethod('GET'); + + // WHEN + new apigateway.Stage(stack, 'my-stage', { deployment }); + + // THEN Template.fromStack(stack).hasResource('AWS::ApiGateway::Stage', { DependsOn: ['testapiAccount9B907665'], }); diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index b0ec5bb8968f7..9854e31e29859 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -85,6 +85,7 @@ "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.108", diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js new file mode 100644 index 0000000000000..2d6c2f0e85497 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle/index.js @@ -0,0 +1,768 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js new file mode 100644 index 0000000000000..9f71f540e4994 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js @@ -0,0 +1,148 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +const aws_sdk_1 = require("aws-sdk"); +async function handler(event) { + const props = event.ResourceProperties.WriterProps; + const exports = props.exports; + const ssm = new aws_sdk_1.SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await throwIfAnyInUse(ssm, exports); + await putParameters(ssm, exports); + return; + case 'Update': + const oldProps = event.OldResourceProperties.WriterProps; + const oldExports = oldProps.exports; + const newExports = except(exports, oldExports); + // throw an error to fail the deployment if any export value is changing + const changedExports = changed(oldExports, exports); + if (changedExports.length > 0) { + throw new Error('Some exports have changed!\n' + changedExports.join('\n')); + } + // if we are removing any exports that are in use, then throw an + // error to fail the deployment + const removedExports = except(oldExports, exports); + await throwIfAnyInUse(ssm, removedExports); + // if the ones we are removing are not in use then delete them + await ssm.deleteParameters({ + Names: Object.keys(removedExports), + }).promise(); + // also throw an error if we are creating a new export that already exists for some reason + await throwIfAnyInUse(ssm, newExports); + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await putParameters(ssm, newExports); + return; + case 'Delete': + // if any of the exports are currently in use then throw an error to fail + // the stack deletion. + await throwIfAnyInUse(ssm, exports); + // if none are in use then delete all of them + await ssm.deleteParameters({ + Names: Object.keys(exports), + }).promise(); + return; + default: + return; + } + } + catch (e) { + console.error('Error processing event: ', e); + throw e; + } +} +exports.handler = handler; +; +/** + * Create parameters for existing exports + */ +async function putParameters(ssm, parameters) { + await Promise.all(Array.from(Object.entries(parameters), ([name, value]) => { + return ssm.putParameter({ + Name: name, + Value: value, + Type: 'String', + }).promise(); + })); +} +/** + * Query for existing parameters that are in use + */ +async function throwIfAnyInUse(ssm, parameters) { + const tagResults = new Map(); + await Promise.all(Object.keys(parameters).map(async (name) => { + const result = await isInUse(ssm, name); + if (result.size > 0) { + tagResults.set(name, result); + } + })); + if (tagResults.size > 0) { + const message = Object.entries(tagResults) + .map((result) => `${result[0]} is in use by stack(s) ${result[1].join(' ')}`) + .join('\n'); + throw new Error(`Exports cannot be updated: \n${message}`); + } +} +/** + * Check if a parameter is in use + */ +async function isInUse(ssm, parameterName) { + const tagResults = new Set(); + try { + const result = await ssm.listTagsForResource({ + ResourceId: parameterName, + ResourceType: 'Parameter', + }).promise(); + result.TagList?.forEach(tag => { + const tagParts = tag.Key.split(':'); + if (tagParts[0] === 'aws-cdk' && tagParts[1] === 'strong-ref') { + tagResults.add(tagParts[2]); + } + }); + } + catch (e) { + // an InvalidResourceId means that the parameter doesn't exist + // which we should ignore since that means it's not in use + if (e.code === 'InvalidResourceId') { + return new Set(); + } + throw e; + } + return tagResults; +} +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + * @returns any exports that don't exist in the filter + */ +function except(source, filter) { + return Object.keys(source) + .filter(key => (!filter.hasOwnProperty(key))) + .reduce((acc, curr) => { + acc[curr] = source[curr]; + return acc; + }, {}); +} +/** + * Return items that exist in both the the old parameters and the new parameters, + * but have different values + * + * @param oldParams the exports that existed previous to this execution + * @param newParams the exports for the current execution + * @returns any parameters that have different values + */ +function changed(oldParams, newParams) { + return Object.keys(oldParams) + .filter(key => (newParams.hasOwnProperty(key) && oldParams[key] !== newParams[key])) + .reduce((acc, curr) => { + acc.push(curr); + return acc; + }, []); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFDN0Isc0RBQXNEO0FBQ3RELHFDQUE4QjtBQUd2QixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLE1BQU0sS0FBSyxHQUF3QixLQUFLLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUE2QixDQUFDO0lBRXBELE1BQU0sR0FBRyxHQUFHLElBQUksYUFBRyxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLElBQUk7UUFDRixRQUFRLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDekIsS0FBSyxRQUFRO2dCQUNYLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0RBQWdELEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO2dCQUM3RSxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLE1BQU0sYUFBYSxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDbEMsT0FBTztZQUNULEtBQUssUUFBUTtnQkFDWCxNQUFNLFFBQVEsR0FBd0IsS0FBSyxDQUFDLHFCQUFxQixDQUFDLFdBQVcsQ0FBQztnQkFDOUUsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLE9BQTZCLENBQUM7Z0JBQzFELE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUM7Z0JBRS9DLHdFQUF3RTtnQkFDeEUsTUFBTSxjQUFjLEdBQUcsT0FBTyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDcEQsSUFBSSxjQUFjLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtvQkFDN0IsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsR0FBRSxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7aUJBQzVFO2dCQUNELGdFQUFnRTtnQkFDaEUsK0JBQStCO2dCQUMvQixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUNuRCxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDLENBQUM7Z0JBQzNDLDhEQUE4RDtnQkFDOUQsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQztpQkFDbkMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUViLDBGQUEwRjtnQkFDMUYsTUFBTSxlQUFlLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUN2QyxPQUFPLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztnQkFDN0UsTUFBTSxhQUFhLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUNyQyxPQUFPO1lBQ1QsS0FBSyxRQUFRO2dCQUNYLHlFQUF5RTtnQkFDekUsc0JBQXNCO2dCQUN0QixNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLDZDQUE2QztnQkFDN0MsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztpQkFDNUIsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNiLE9BQU87WUFDVDtnQkFDRSxPQUFPO1NBQ1Y7S0FDRjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsT0FBTyxDQUFDLEtBQUssQ0FBQywwQkFBMEIsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUM3QyxNQUFNLENBQUMsQ0FBQztLQUNUO0FBQ0gsQ0FBQztBQXBERCwwQkFvREM7QUFBQSxDQUFDO0FBRUY7O0dBRUc7QUFDSCxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQVEsRUFBRSxVQUE4QjtJQUNuRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRTtRQUN6RSxPQUFPLEdBQUcsQ0FBQyxZQUFZLENBQUM7WUFDdEIsSUFBSSxFQUFFLElBQUk7WUFDVixLQUFLLEVBQUUsS0FBSztZQUNaLElBQUksRUFBRSxRQUFRO1NBQ2YsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUM7QUFFRDs7R0FFRztBQUNILEtBQUssVUFBVSxlQUFlLENBQUMsR0FBUSxFQUFFLFVBQThCO0lBQ3JFLE1BQU0sVUFBVSxHQUE2QixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsSUFBWSxFQUFFLEVBQUU7UUFDbkUsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3hDLElBQUksTUFBTSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUU7WUFDbkIsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7U0FDOUI7SUFDSCxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRUosSUFBSSxVQUFVLENBQUMsSUFBSSxHQUFHLENBQUMsRUFBRTtRQUN2QixNQUFNLE9BQU8sR0FBVyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQzthQUMvQyxHQUFHLENBQUMsQ0FBQyxNQUEwQixFQUFFLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsMEJBQTBCLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQzthQUNoRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO0tBQzVEO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsS0FBSyxVQUFVLE9BQU8sQ0FBQyxHQUFRLEVBQUUsYUFBcUI7SUFDcEQsTUFBTSxVQUFVLEdBQWdCLElBQUksR0FBRyxFQUFFLENBQUM7SUFDMUMsSUFBSTtRQUNGLE1BQU0sTUFBTSxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDO1lBQzNDLFVBQVUsRUFBRSxhQUFhO1lBQ3pCLFlBQVksRUFBRSxXQUFXO1NBQzFCLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1lBQzVCLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3BDLElBQUksUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLFNBQVMsSUFBSSxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssWUFBWSxFQUFFO2dCQUM3RCxVQUFVLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQzdCO1FBQ0gsQ0FBQyxDQUFDLENBQUM7S0FDSjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsOERBQThEO1FBQzlELDBEQUEwRDtRQUMxRCxJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssbUJBQW1CLEVBQUU7WUFDbEMsT0FBTyxJQUFJLEdBQUcsRUFBRSxDQUFDO1NBQ2xCO1FBQ0QsTUFBTSxDQUFDLENBQUM7S0FDVDtJQUNELE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLE1BQU0sQ0FBQyxNQUEwQixFQUFFLE1BQTBCO0lBQ3BFLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7U0FDdkIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztTQUM1QyxNQUFNLENBQUMsQ0FBQyxHQUF1QixFQUFFLElBQVksRUFBRSxFQUFFO1FBQ2hELEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekIsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQVMsT0FBTyxDQUFDLFNBQTZCLEVBQUUsU0FBNkI7SUFDM0UsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztTQUMxQixNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLElBQUksU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1NBQ25GLE1BQU0sQ0FBQyxDQUFDLEdBQWEsRUFBRSxJQUFZLEVBQUUsRUFBRTtRQUN0QyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2YsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyplc2xpbnQtZGlzYWJsZSBuby1jb25zb2xlKi9cbi8qIGVzbGludC1kaXNhYmxlIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llcyAqL1xuaW1wb3J0IHsgU1NNIH0gZnJvbSAnYXdzLXNkayc7XG5pbXBvcnQgeyBDcm9zc1JlZ2lvbkV4cG9ydHMsIEV4cG9ydFdyaXRlckNSUHJvcHMgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICBjb25zdCBleHBvcnRzID0gcHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTSh7IHJlZ2lvbjogcHJvcHMucmVnaW9uIH0pO1xuICB0cnkge1xuICAgIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICAgIGNvbnNvbGUuaW5mbyhgQ3JlYXRpbmcgbmV3IFNTTSBQYXJhbWV0ZXIgZXhwb3J0cyBpbiByZWdpb24gJHtwcm9wcy5yZWdpb259YCk7XG4gICAgICAgIGF3YWl0IHRocm93SWZBbnlJblVzZShzc20sIGV4cG9ydHMpO1xuICAgICAgICBhd2FpdCBwdXRQYXJhbWV0ZXJzKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGNhc2UgJ1VwZGF0ZSc6XG4gICAgICAgIGNvbnN0IG9sZFByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICAgICAgICBjb25zdCBvbGRFeHBvcnRzID0gb2xkUHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG4gICAgICAgIGNvbnN0IG5ld0V4cG9ydHMgPSBleGNlcHQoZXhwb3J0cywgb2xkRXhwb3J0cyk7XG5cbiAgICAgICAgLy8gdGhyb3cgYW4gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudCBpZiBhbnkgZXhwb3J0IHZhbHVlIGlzIGNoYW5naW5nXG4gICAgICAgIGNvbnN0IGNoYW5nZWRFeHBvcnRzID0gY2hhbmdlZChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgaWYgKGNoYW5nZWRFeHBvcnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NvbWUgZXhwb3J0cyBoYXZlIGNoYW5nZWQhXFxuJysgY2hhbmdlZEV4cG9ydHMuam9pbignXFxuJykpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGlmIHdlIGFyZSByZW1vdmluZyBhbnkgZXhwb3J0cyB0aGF0IGFyZSBpbiB1c2UsIHRoZW4gdGhyb3cgYW5cbiAgICAgICAgLy8gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudFxuICAgICAgICBjb25zdCByZW1vdmVkRXhwb3J0cyA9IGV4Y2VwdChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgcmVtb3ZlZEV4cG9ydHMpO1xuICAgICAgICAvLyBpZiB0aGUgb25lcyB3ZSBhcmUgcmVtb3ZpbmcgYXJlIG5vdCBpbiB1c2UgdGhlbiBkZWxldGUgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKHJlbW92ZWRFeHBvcnRzKSxcbiAgICAgICAgfSkucHJvbWlzZSgpO1xuXG4gICAgICAgIC8vIGFsc28gdGhyb3cgYW4gZXJyb3IgaWYgd2UgYXJlIGNyZWF0aW5nIGEgbmV3IGV4cG9ydCB0aGF0IGFscmVhZHkgZXhpc3RzIGZvciBzb21lIHJlYXNvblxuICAgICAgICBhd2FpdCB0aHJvd0lmQW55SW5Vc2Uoc3NtLCBuZXdFeHBvcnRzKTtcbiAgICAgICAgY29uc29sZS5pbmZvKGBDcmVhdGluZyBuZXcgU1NNIFBhcmFtZXRlciBleHBvcnRzIGluIHJlZ2lvbiAke3Byb3BzLnJlZ2lvbn1gKTtcbiAgICAgICAgYXdhaXQgcHV0UGFyYW1ldGVycyhzc20sIG5ld0V4cG9ydHMpO1xuICAgICAgICByZXR1cm47XG4gICAgICBjYXNlICdEZWxldGUnOlxuICAgICAgICAvLyBpZiBhbnkgb2YgdGhlIGV4cG9ydHMgYXJlIGN1cnJlbnRseSBpbiB1c2UgdGhlbiB0aHJvdyBhbiBlcnJvciB0byBmYWlsXG4gICAgICAgIC8vIHRoZSBzdGFjayBkZWxldGlvbi5cbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIC8vIGlmIG5vbmUgYXJlIGluIHVzZSB0aGVuIGRlbGV0ZSBhbGwgb2YgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKGV4cG9ydHMpLFxuICAgICAgICB9KS5wcm9taXNlKCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIGV2ZW50OiAnLCBlKTtcbiAgICB0aHJvdyBlO1xuICB9XG59O1xuXG4vKipcbiAqIENyZWF0ZSBwYXJhbWV0ZXJzIGZvciBleGlzdGluZyBleHBvcnRzXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHB1dFBhcmFtZXRlcnMoc3NtOiBTU00sIHBhcmFtZXRlcnM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IFByb21pc2U8dm9pZD4ge1xuICBhd2FpdCBQcm9taXNlLmFsbChBcnJheS5mcm9tKE9iamVjdC5lbnRyaWVzKHBhcmFtZXRlcnMpLCAoW25hbWUsIHZhbHVlXSkgPT4ge1xuICAgIHJldHVybiBzc20ucHV0UGFyYW1ldGVyKHtcbiAgICAgIE5hbWU6IG5hbWUsXG4gICAgICBWYWx1ZTogdmFsdWUsXG4gICAgICBUeXBlOiAnU3RyaW5nJyxcbiAgICB9KS5wcm9taXNlKCk7XG4gIH0pKTtcbn1cblxuLyoqXG4gKiBRdWVyeSBmb3IgZXhpc3RpbmcgcGFyYW1ldGVycyB0aGF0IGFyZSBpbiB1c2VcbiAqL1xuYXN5bmMgZnVuY3Rpb24gdGhyb3dJZkFueUluVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJzOiBDcm9zc1JlZ2lvbkV4cG9ydHMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgdGFnUmVzdWx0czogTWFwPHN0cmluZywgU2V0PHN0cmluZz4+ID0gbmV3IE1hcCgpO1xuICBhd2FpdCBQcm9taXNlLmFsbChPYmplY3Qua2V5cyhwYXJhbWV0ZXJzKS5tYXAoYXN5bmMgKG5hbWU6IHN0cmluZykgPT4ge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGlzSW5Vc2Uoc3NtLCBuYW1lKTtcbiAgICBpZiAocmVzdWx0LnNpemUgPiAwKSB7XG4gICAgICB0YWdSZXN1bHRzLnNldChuYW1lLCByZXN1bHQpO1xuICAgIH1cbiAgfSkpO1xuXG4gIGlmICh0YWdSZXN1bHRzLnNpemUgPiAwKSB7XG4gICAgY29uc3QgbWVzc2FnZTogc3RyaW5nID0gT2JqZWN0LmVudHJpZXModGFnUmVzdWx0cylcbiAgICAgIC5tYXAoKHJlc3VsdDogW3N0cmluZywgc3RyaW5nW11dKSA9PiBgJHtyZXN1bHRbMF19IGlzIGluIHVzZSBieSBzdGFjayhzKSAke3Jlc3VsdFsxXS5qb2luKCcgJyl9YClcbiAgICAgIC5qb2luKCdcXG4nKTtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cG9ydHMgY2Fubm90IGJlIHVwZGF0ZWQ6IFxcbiR7bWVzc2FnZX1gKTtcbiAgfVxufVxuXG4vKipcbiAqIENoZWNrIGlmIGEgcGFyYW1ldGVyIGlzIGluIHVzZVxuICovXG5hc3luYyBmdW5jdGlvbiBpc0luVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJOYW1lOiBzdHJpbmcpOiBQcm9taXNlPFNldDxzdHJpbmc+PiB7XG4gIGNvbnN0IHRhZ1Jlc3VsdHM6IFNldDxzdHJpbmc+ID0gbmV3IFNldCgpO1xuICB0cnkge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHNzbS5saXN0VGFnc0ZvclJlc291cmNlKHtcbiAgICAgIFJlc291cmNlSWQ6IHBhcmFtZXRlck5hbWUsXG4gICAgICBSZXNvdXJjZVR5cGU6ICdQYXJhbWV0ZXInLFxuICAgIH0pLnByb21pc2UoKTtcbiAgICByZXN1bHQuVGFnTGlzdD8uZm9yRWFjaCh0YWcgPT4ge1xuICAgICAgY29uc3QgdGFnUGFydHMgPSB0YWcuS2V5LnNwbGl0KCc6Jyk7XG4gICAgICBpZiAodGFnUGFydHNbMF0gPT09ICdhd3MtY2RrJyAmJiB0YWdQYXJ0c1sxXSA9PT0gJ3N0cm9uZy1yZWYnKSB7XG4gICAgICAgIHRhZ1Jlc3VsdHMuYWRkKHRhZ1BhcnRzWzJdKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIGFuIEludmFsaWRSZXNvdXJjZUlkIG1lYW5zIHRoYXQgdGhlIHBhcmFtZXRlciBkb2Vzbid0IGV4aXN0XG4gICAgLy8gd2hpY2ggd2Ugc2hvdWxkIGlnbm9yZSBzaW5jZSB0aGF0IG1lYW5zIGl0J3Mgbm90IGluIHVzZVxuICAgIGlmIChlLmNvZGUgPT09ICdJbnZhbGlkUmVzb3VyY2VJZCcpIHtcbiAgICAgIHJldHVybiBuZXcgU2V0KCk7XG4gICAgfVxuICAgIHRocm93IGU7XG4gIH1cbiAgcmV0dXJuIHRhZ1Jlc3VsdHM7XG59XG5cbi8qKlxuICogUmV0dXJuIG9ubHkgdGhlIGl0ZW1zIGZyb20gc291cmNlIHRoYXQgZG8gbm90IGV4aXN0IGluIHRoZSBmaWx0ZXJcbiAqXG4gKiBAcGFyYW0gc291cmNlIHRoZSBzb3VyY2Ugb2JqZWN0IHRvIHBlcmZvcm0gdGhlIGZpbHRlciBvblxuICogQHBhcmFtIGZpbHRlciBmaWx0ZXIgb3V0IGl0ZW1zIHRoYXQgZXhpc3QgaW4gdGhpcyBvYmplY3RcbiAqIEByZXR1cm5zIGFueSBleHBvcnRzIHRoYXQgZG9uJ3QgZXhpc3QgaW4gdGhlIGZpbHRlclxuICovXG5mdW5jdGlvbiBleGNlcHQoc291cmNlOiBDcm9zc1JlZ2lvbkV4cG9ydHMsIGZpbHRlcjogQ3Jvc3NSZWdpb25FeHBvcnRzKTogQ3Jvc3NSZWdpb25FeHBvcnRzIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKHNvdXJjZSlcbiAgICAuZmlsdGVyKGtleSA9PiAoIWZpbHRlci5oYXNPd25Qcm9wZXJ0eShrZXkpKSlcbiAgICAucmVkdWNlKChhY2M6IENyb3NzUmVnaW9uRXhwb3J0cywgY3Vycjogc3RyaW5nKSA9PiB7XG4gICAgICBhY2NbY3Vycl0gPSBzb3VyY2VbY3Vycl07XG4gICAgICByZXR1cm4gYWNjO1xuICAgIH0sIHt9KTtcbn1cblxuLyoqXG4gKiBSZXR1cm4gaXRlbXMgdGhhdCBleGlzdCBpbiBib3RoIHRoZSB0aGUgb2xkIHBhcmFtZXRlcnMgYW5kIHRoZSBuZXcgcGFyYW1ldGVycyxcbiAqIGJ1dCBoYXZlIGRpZmZlcmVudCB2YWx1ZXNcbiAqXG4gKiBAcGFyYW0gb2xkUGFyYW1zIHRoZSBleHBvcnRzIHRoYXQgZXhpc3RlZCBwcmV2aW91cyB0byB0aGlzIGV4ZWN1dGlvblxuICogQHBhcmFtIG5ld1BhcmFtcyB0aGUgZXhwb3J0cyBmb3IgdGhlIGN1cnJlbnQgZXhlY3V0aW9uXG4gKiBAcmV0dXJucyBhbnkgcGFyYW1ldGVycyB0aGF0IGhhdmUgZGlmZmVyZW50IHZhbHVlc1xuICovXG5mdW5jdGlvbiBjaGFuZ2VkKG9sZFBhcmFtczogQ3Jvc3NSZWdpb25FeHBvcnRzLCBuZXdQYXJhbXM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IHN0cmluZ1tdIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKG9sZFBhcmFtcylcbiAgICAuZmlsdGVyKGtleSA9PiAobmV3UGFyYW1zLmhhc093blByb3BlcnR5KGtleSkgJiYgb2xkUGFyYW1zW2tleV0gIT09IG5ld1BhcmFtc1trZXldKSlcbiAgICAucmVkdWNlKChhY2M6IHN0cmluZ1tdLCBjdXJyOiBzdHJpbmcpID0+IHtcbiAgICAgIGFjYy5wdXNoKGN1cnIpO1xuICAgICAgcmV0dXJuIGFjYztcbiAgICB9LCBbXSk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.assets.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.assets.json new file mode 100644 index 0000000000000..2eb2f3a9bf7f4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.assets.json @@ -0,0 +1,48 @@ +{ + "version": "21.0.0", + "files": { + "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741": { + "source": { + "path": "asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + }, + "e8a524074c21b1828365d86b0554b5a5282843ef099edb36de4091e50a8b4ac2": { + "source": { + "path": "crossregionconsumerIntegNested815BEF8A.nested.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "e8a524074c21b1828365d86b0554b5a5282843ef099edb36de4091e50a8b4ac2.json", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + }, + "33212ba7662e584fce97d4b64b2b7d157f5f1bac2b2ffe4e25e18545b514ec8b": { + "source": { + "path": "cross-region-consumer.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "33212ba7662e584fce97d4b64b2b7d157f5f1bac2b2ffe4e25e18545b514ec8b.json", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.template.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.template.json new file mode 100644 index 0000000000000..b441ed812b9b7 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-consumer.template.json @@ -0,0 +1,198 @@ +{ + "Resources": { + "IntegNestedNestedStackIntegNestedNestedStackResource168C5881": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.us-east-2.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2" + }, + "/e8a524074c21b1828365d86b0554b5a5282843ef099edb36de4091e50a8b4ac2.json" + ] + ] + }, + "Parameters": { + "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B" + ] + }, + "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E" + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "IntegParameter02A1817A4": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B" + ] + }, + "Name": "integ-parameter0" + } + }, + "IntegParameter1EDBEF1C6": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E" + ] + }, + "Name": "integ-parameter1" + } + }, + "ExportsReader8B249524": { + "Type": "Custom::CrossRegionExportReader", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68", + "Arn" + ] + }, + "ReaderProps": { + "region": "us-east-2", + "prefix": "cross-region-consumer", + "imports": { + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B": "{{resolve:ssm:/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B}}", + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E": "{{resolve:ssm:/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E}}" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/cdk/exports/cross-region-consumer/*" + ] + ] + }, + "Action": [ + "ssm:AddTagsToResource", + "ssm:RemoveTagsFromResource", + "ssm:GetParameters" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2" + }, + "S3Key": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.assets.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.assets.json new file mode 100644 index 0000000000000..a98a88fd5f9f8 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.assets.json @@ -0,0 +1,48 @@ +{ + "version": "21.0.0", + "files": { + "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3": { + "source": { + "path": "asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + }, + "db4b89d277ac97fb3b94206516c7e60648f2e3a4d53793e2e8d073a607b04fdc": { + "source": { + "path": "crossregionproducerIntegNested3342EBEB.nested.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "db4b89d277ac97fb3b94206516c7e60648f2e3a4d53793e2e8d073a607b04fdc.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + }, + "70e0b1af0ea278adf024d80a32c2797554d527123f1dc1eb9f2a894a6d46bc7a": { + "source": { + "path": "cross-region-producer.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "70e0b1af0ea278adf024d80a32c2797554d527123f1dc1eb9f2a894a6d46bc7a.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.template.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.template.json new file mode 100644 index 0000000000000..f0b15d4ab2e9e --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/cross-region-producer.template.json @@ -0,0 +1,173 @@ +{ + "Resources": { + "IntegNestedNestedStackIntegNestedNestedStackResource168C5881": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.us-east-1.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1" + }, + "/db4b89d277ac97fb3b94206516c7e60648f2e3a4d53793e2e8d073a607b04fdc.json" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "IntegQueue3A18718A": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ExportsWriteruseast2828FA26B86FBEFA7": { + "Type": "Custom::CrossRegionExportWriter", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A", + "Arn" + ] + }, + "WriterProps": { + "region": "us-east-2", + "exports": { + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B": { + "Fn::GetAtt": [ + "IntegQueue3A18718A", + "QueueName" + ] + }, + "/cdk/exports/cross-region-consumer/crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E": { + "Fn::GetAtt": [ + "IntegNestedNestedStackIntegNestedNestedStackResource168C5881", + "Outputs.crossregionproducerIntegNestedNestedIntegQueueD686DB69QueueName" + ] + } + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/cdk/exports/*" + ] + ] + }, + "Action": [ + "ssm:DeleteParameters", + "ssm:ListTagsForResource", + "ssm:GetParameters", + "ssm:PutParameter" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1" + }, + "S3Key": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionconsumerIntegNested815BEF8A.nested.template.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionconsumerIntegNested815BEF8A.nested.template.json new file mode 100644 index 0000000000000..fea781033a45c --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionconsumerIntegNested815BEF8A.nested.template.json @@ -0,0 +1,32 @@ +{ + "Resources": { + "IntegNestedParameter04B9B8A01": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B" + }, + "Name": "integ-nested-parameter0" + } + }, + "IntegNestedParameter1DE6274D4": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E" + }, + "Name": "integ-nested-parameter1" + } + } + }, + "Parameters": { + "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B": { + "Type": "String" + }, + "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E": { + "Type": "String" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionproducerIntegNested3342EBEB.nested.template.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionproducerIntegNested3342EBEB.nested.template.json new file mode 100644 index 0000000000000..4dbe064e0bc4a --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionproducerIntegNested3342EBEB.nested.template.json @@ -0,0 +1,19 @@ +{ + "Resources": { + "NestedIntegQueue0DFF7C28": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "crossregionproducerIntegNestedNestedIntegQueueD686DB69QueueName": { + "Value": { + "Fn::GetAtt": [ + "NestedIntegQueue0DFF7C28", + "QueueName" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.assets.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.assets.json new file mode 100644 index 0000000000000..3d31b494852c6 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b": { + "source": { + "path": "asset.b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "e62b4ad819f8f21c0c8707091f053b8b322398afc1a04fd089b1be1436fb011a": { + "source": { + "path": "crossregionreferencesDefaultTestDeployAssertAB7415FD.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e62b4ad819f8f21c0c8707091f053b8b322398afc1a04fd089b1be1436fb011a.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.template.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.template.json new file mode 100644 index 0000000000000..cb2253ab681b4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/crossregionreferencesDefaultTestDeployAssertAB7415FD.template.json @@ -0,0 +1,404 @@ +{ + "Resources": { + "AwsApiCallCloudFormationdeleteStack": { + "Type": "Custom::DeployAssert@SdkCallCloudFormationdeleteStack", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "CloudFormation", + "api": "deleteStack", + "parameters": { + "StackName": "cross-region-producer" + }, + "flattenResponse": "false", + "salt": "1666292907086" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "cloudformation:DeleteStack" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "cloudformation:DescribeStacks" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + }, + "AwsApiCallCloudFormationdescribeStacks": { + "Type": "Custom::DeployAssert@SdkCallCloudFormationdescribeStacks", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "CloudFormation", + "api": "describeStacks", + "expected": "{\"$ObjectLike\":{\"Stacks\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"StackName\":\"cross-region-producer\",\"StackStatus\":\"DELETE_FAILED\"}}]}}}", + "stateMachineArn": { + "Ref": "AwsApiCallCloudFormationdescribeStacksWaitFor1D722558" + }, + "parameters": { + "StackName": "cross-region-producer" + }, + "flattenResponse": "false", + "salt": "1666292907087" + }, + "DependsOn": [ + "AwsApiCallCloudFormationdeleteStack" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallCloudFormationdescribeStacksWaitForIsCompleteProviderInvokeD8EB59C7": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallCloudFormationdeleteStack" + ] + }, + "AwsApiCallCloudFormationdescribeStacksWaitForTimeoutProviderInvokeA2598EF3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "Principal": { + "Fn::GetAtt": [ + "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallCloudFormationdeleteStack" + ] + }, + "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "states.amazonaws.com" + } + } + ] + }, + "Policies": [ + { + "PolicyName": "InlineInvokeFunctions", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + } + ] + } + ] + } + } + ] + }, + "DependsOn": [ + "AwsApiCallCloudFormationdeleteStack" + ] + }, + "AwsApiCallCloudFormationdescribeStacksWaitFor1D722558": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"framework-isComplete-task\",\"States\":{\"framework-isComplete-task\":{\"End\":true,\"Retry\":[{\"ErrorEquals\":[\"States.ALL\"],\"IntervalSeconds\":5,\"MaxAttempts\":360,\"BackoffRate\":1}],\"Catch\":[{\"ErrorEquals\":[\"States.ALL\"],\"Next\":\"framework-onTimeout-task\"}],\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE", + "Arn" + ] + }, + "\"},\"framework-onTimeout-task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"", + { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA", + "Arn" + ] + }, + "\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0", + "Arn" + ] + } + }, + "DependsOn": [ + "AwsApiCallCloudFormationdeleteStack", + "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0" + ] + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "cloudformation:DescribeStacks" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.isComplete", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB", + "Arn" + ] + } + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b54b99043c35bd080b9d9d1afce31e3541cf15b679799ba980ed40c837dcb03b.zip" + }, + "Timeout": 120, + "Handler": "index.onTimeout", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallCloudFormationdescribeStacks": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallCloudFormationdescribeStacks", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/integ.json new file mode 100644 index 0000000000000..42ba38db2813c --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "21.0.0", + "testCases": { + "cross-region-references/DefaultTest": { + "stacks": [ + "cross-region-consumer" + ], + "stackUpdateWorkflow": false, + "assertionStack": "cross-region-references/DefaultTest/DeployAssert", + "assertionStackName": "crossregionreferencesDefaultTestDeployAssertAB7415FD" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..251eda57fc9af --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/core-cross-region-references.integ.snapshot/manifest.json @@ -0,0 +1,327 @@ +{ + "version": "21.0.0", + "artifacts": { + "cross-region-producer.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cross-region-producer.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cross-region-producer": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-1", + "properties": { + "templateFile": "cross-region-producer.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1/70e0b1af0ea278adf024d80a32c2797554d527123f1dc1eb9f2a894a6d46bc7a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cross-region-producer.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "cross-region-producer.assets" + ], + "metadata": { + "/cross-region-producer/IntegNested/NestedIntegQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "NestedIntegQueue0DFF7C28" + } + ], + "/cross-region-producer/IntegNested/crossregionproducerIntegNestedNestedIntegQueueD686DB69QueueName": [ + { + "type": "aws:cdk:logicalId", + "data": "crossregionproducerIntegNestedNestedIntegQueueD686DB69QueueName" + } + ], + "/cross-region-producer/IntegNested.NestedStack/IntegNested.NestedStackResource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegNestedNestedStackIntegNestedNestedStackResource168C5881" + } + ], + "/cross-region-producer/IntegQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegQueue3A18718A" + } + ], + "/cross-region-producer/ExportsWriteruseast2828FA26B/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsWriteruseast2828FA26B86FBEFA7" + } + ], + "/cross-region-producer/Custom::CrossRegionExportWriterCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + } + ], + "/cross-region-producer/Custom::CrossRegionExportWriterCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A" + } + ], + "/cross-region-producer/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cross-region-producer/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cross-region-producer" + }, + "cross-region-consumer.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cross-region-consumer.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cross-region-consumer": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-2", + "properties": { + "templateFile": "cross-region-consumer.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-2", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-2", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2/33212ba7662e584fce97d4b64b2b7d157f5f1bac2b2ffe4e25e18545b514ec8b.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cross-region-consumer.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-2", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "cross-region-producer", + "cross-region-consumer.assets" + ], + "metadata": { + "/cross-region-consumer/IntegNested/IntegNestedParameter0/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegNestedParameter04B9B8A01" + } + ], + "/cross-region-consumer/IntegNested/IntegNestedParameter1/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegNestedParameter1DE6274D4" + } + ], + "/cross-region-consumer/IntegNested/reference-to-crossregionconsumerExportsReader5D0359E7--cdk--exports--cross-region-consumer--crossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B": [ + { + "type": "aws:cdk:logicalId", + "data": "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegQueue3A18718AQueueName8D8D3C9B" + } + ], + "/cross-region-consumer/IntegNested/reference-to-crossregionconsumerExportsReader5D0359E7--cdk--exports--cross-region-consumer--crossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E": [ + { + "type": "aws:cdk:logicalId", + "data": "referencetocrossregionconsumerExportsReader5D0359E7cdkexportscrossregionconsumercrossregionproduceruseast1FnGetAttIntegNestedNestedStackIntegNestedNestedStackResource168C5881OutputscrossregionproducerIntegNestedNestedIntegQueueD686DB69QueueNameC1C9C99E" + } + ], + "/cross-region-consumer/IntegNested.NestedStack/IntegNested.NestedStackResource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegNestedNestedStackIntegNestedNestedStackResource168C5881" + } + ], + "/cross-region-consumer/IntegParameter0/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegParameter02A1817A4" + } + ], + "/cross-region-consumer/IntegParameter1/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "IntegParameter1EDBEF1C6" + } + ], + "/cross-region-consumer/ExportsReader/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsReader8B249524" + } + ], + "/cross-region-consumer/Custom::CrossRegionExportReaderCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + } + ], + "/cross-region-consumer/Custom::CrossRegionExportReaderCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68" + } + ], + "/cross-region-consumer/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cross-region-consumer/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cross-region-consumer" + }, + "crossregionreferencesDefaultTestDeployAssertAB7415FD.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "crossregionreferencesDefaultTestDeployAssertAB7415FD.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "crossregionreferencesDefaultTestDeployAssertAB7415FD": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "crossregionreferencesDefaultTestDeployAssertAB7415FD.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e62b4ad819f8f21c0c8707091f053b8b322398afc1a04fd089b1be1436fb011a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "crossregionreferencesDefaultTestDeployAssertAB7415FD.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "crossregionreferencesDefaultTestDeployAssertAB7415FD.assets" + ], + "metadata": { + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdeleteStack/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdeleteStack" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdescribeStacks" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/WaitFor/IsCompleteProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdescribeStacksWaitForIsCompleteProviderInvokeD8EB59C7" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/WaitFor/TimeoutProvider/Invoke": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdescribeStacksWaitForTimeoutProviderInvokeA2598EF3" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/WaitFor/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdescribeStacksWaitForRoleEC9EDBA0" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/WaitFor/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallCloudFormationdescribeStacksWaitFor1D722558" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/AwsApiCallCloudFormationdescribeStacks/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallCloudFormationdescribeStacks" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Role918961BB" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction76b3e830a873425f8453eddd85c86925/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction76b3e830a873425f8453eddd85c86925Handler81461ECE" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aRoleB84BD8CE" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41a/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction5c1898e096fb4e3e95d5f6c67f3ce41aHandlerADF3E6EA" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cross-region-references/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cross-region-references/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.core-cross-region-references.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.core-cross-region-references.ts new file mode 100644 index 0000000000000..5e6408139889d --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.core-cross-region-references.ts @@ -0,0 +1,96 @@ +import { Queue, IQueue } from '@aws-cdk/aws-sqs'; +import { StringParameter } from '@aws-cdk/aws-ssm'; +import { App, Stack, StackProps, NestedStack } from '@aws-cdk/core'; +import { IntegTest, ExpectedResult, Match } from '@aws-cdk/integ-tests'; +import { Construct } from 'constructs'; + +// GIVEN +const app = new App({ + treeMetadata: false, +}); + +class ProducerStack extends Stack { + public readonly queue: IQueue; + public readonly nestedQueue: IQueue; + constructor(scope: Construct, id: string) { + super(scope, id, { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, + }); + const nested = new NestedStack(this, 'IntegNested'); + this.queue = new Queue(this, 'IntegQueue'); + this.nestedQueue = new Queue(nested, 'NestedIntegQueue'); + } +} + +interface ConsumerStackProps extends StackProps { + readonly queues: IQueue[]; +} +class ConsumerStack extends Stack { + constructor(scope: Construct, id: string, props: ConsumerStackProps) { + super(scope, id, { + ...props, + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, + }); + + const nested = new NestedStack(this, 'IntegNested'); + props.queues.forEach((queue, i) => { + new StringParameter(this, 'IntegParameter'+i, { + parameterName: 'integ-parameter'+i, + stringValue: queue.queueName, + }); + new StringParameter(nested, 'IntegNestedParameter'+i, { + parameterName: 'integ-nested-parameter'+i, + stringValue: queue.queueName, + }); + }); + } +} + +class TestCase extends Construct { + public readonly testCase: Stack; + public readonly producer: ProducerStack; + constructor(scope: Construct, id: string) { + super(scope, id); + this.producer = new ProducerStack(app, 'cross-region-producer'); + this.testCase = new ConsumerStack(app, 'cross-region-consumer', { + queues: [this.producer.queue, this.producer.nestedQueue], + }); + } +} +const testCase1 = new TestCase(app, 'TestCase1'); + +// THEN +const integ = new IntegTest(app, 'cross-region-references', { + testCases: [testCase1.testCase], + stackUpdateWorkflow: false, +}); + + +/** + * Test that if the references are still in use, deleting the producer + * stack will fail + * + * When the test cleans up it will delete the consumer then the producer, which should + * test that the parameters are cleaned up correctly. + */ + +integ.assertions.awsApiCall('CloudFormation', 'deleteStack', { + StackName: testCase1.producer.stackName, +}).next( + integ.assertions.awsApiCall('CloudFormation', 'describeStacks', { + StackName: testCase1.producer.stackName, + }).expect(ExpectedResult.objectLike({ + Stacks: Match.arrayWith([ + Match.objectLike({ + StackName: testCase1.producer.stackName, + StackStatus: 'DELETE_FAILED', + }), + ]), + })).waitForAssertions(), +); diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 223e0bf5f6a7d..4991464d5f1b1 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -125,6 +125,42 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` +#### Cross Region Certificates + +> **This feature is currently experimental** + +You can enable the Stack property `crossRegionReferences` +in order to access resources in a different stack _and_ region. With this feature flag +enabled it is possible to do something like creating a CloudFront distribution in `us-east-2` and +an ACM certificate in `us-east-1`. + +```ts +const stack1 = new Stack(app, 'Stack1', { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, +}); +const cert = new acm.Certificate(stack1, 'Cert', { + domainName: '*.example.com', + validation: acm.CertificateValidation.fromDns(route53.PublicHostedZone.fromHostedZoneId(stack1, 'Zone', 'Z0329774B51CGXTDQV3X')), +}); + +const stack2 = new Stack(app, 'Stack2', { + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, +}); +new cloudfront.Distribution(stack2, 'Distribution', { + defaultBehavior: { + origin: new origins.HttpOrigin('example.com'), + }, + domainNames: ['dev.example.com'], + certificate: cert, +}); +``` + ### Multiple Behaviors & Origins Each distribution has a default behavior which applies to all requests to that distribution; additional behaviors may be specified for a diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 3eb58a20b10c7..76829339fa3f2 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -84,6 +84,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/aws-route53": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/__entrypoint__.js b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/index.js b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/index.js new file mode 100644 index 0000000000000..77f7ee2324a9a --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741/index.js @@ -0,0 +1,100 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +const aws_sdk_1 = require("aws-sdk"); +async function handler(event) { + const props = event.ResourceProperties.ReaderProps; + const imports = props.imports; + const importNames = Object.keys(imports); + const keyName = `aws-cdk:strong-ref:${props.prefix}`; + const ssm = new aws_sdk_1.SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info('Tagging SSM Parameter imports'); + await addTags(ssm, importNames, keyName); + break; + case 'Update': + const oldProps = event.OldResourceProperties.ReaderProps; + const oldExports = oldProps.imports; + const newExports = except(importNames, Object.keys(oldExports)); + const paramsToRelease = except(Object.keys(oldExports), importNames); + console.info('Releasing unused SSM Parameter imports'); + if (Object.keys(paramsToRelease).length > 0) { + await removeTags(ssm, paramsToRelease, keyName); + } + console.info('Tagging new SSM Parameter imports'); + await addTags(ssm, newExports, keyName); + break; + case 'Delete': + console.info('Releasing all SSM Parameter exports by removing tags'); + await removeTags(ssm, importNames, keyName); + return; + } + } + catch (e) { + console.error('Error importing cross region stack exports: ', e); + throw e; + } + console.info('returning imports: ', JSON.stringify(imports)); + return { + Data: imports, + }; +} +exports.handler = handler; +; +/** + * Add tag to parameters for existing exports + */ +async function addTags(ssm, parameters, keyName) { + await Promise.all(parameters.map(async (name) => { + try { + return await ssm.addTagsToResource({ + ResourceId: name, + ResourceType: 'Parameter', + Tags: [{ + Key: keyName, + Value: 'true', + }], + }).promise(); + } + catch (e) { + throw new Error(`Error importing ${name}: ${e}`); + } + })); +} +/** + * Remove tags from parameters + */ +async function removeTags(ssm, parameters, keyName) { + await Promise.all(parameters.map(async (name) => { + try { + return await ssm.removeTagsFromResource({ + TagKeys: [keyName], + ResourceType: 'Parameter', + ResourceId: name, + }).promise(); + } + catch (e) { + switch (e.code) { + // if the parameter doesn't exist then there is nothing to release + case 'InvalidResourceId': + return; + default: + throw new Error(`Error releasing import ${name}: ${e}`); + } + } + })); +} +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + */ +function except(source, filter) { + return source.filter(key => !filter.includes(key)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFDN0Isc0RBQXNEO0FBQ3RELHFDQUE4QjtBQUd2QixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLE1BQU0sS0FBSyxHQUF3QixLQUFLLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUF1QixLQUFLLENBQUMsT0FBNkIsQ0FBQztJQUN4RSxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3pDLE1BQU0sT0FBTyxHQUFXLHNCQUFzQixLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7SUFFN0QsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDOUMsSUFBSTtRQUNGLFFBQVEsS0FBSyxDQUFDLFdBQVcsRUFBRTtZQUN6QixLQUFLLFFBQVE7Z0JBQ1gsT0FBTyxDQUFDLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxDQUFDO2dCQUM5QyxNQUFNLE9BQU8sQ0FBQyxHQUFHLEVBQUUsV0FBVyxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUN6QyxNQUFNO1lBQ1IsS0FBSyxRQUFRO2dCQUNYLE1BQU0sUUFBUSxHQUF3QixLQUFLLENBQUMscUJBQXFCLENBQUMsV0FBVyxDQUFDO2dCQUM5RSxNQUFNLFVBQVUsR0FBdUIsUUFBUSxDQUFDLE9BQTZCLENBQUM7Z0JBQzlFLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO2dCQUNoRSxNQUFNLGVBQWUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxXQUFXLENBQUMsQ0FBQztnQkFDckUsT0FBTyxDQUFDLElBQUksQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO2dCQUN2RCxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtvQkFDM0MsTUFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLGVBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQztpQkFDakQ7Z0JBQ0QsT0FBTyxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsQ0FBQyxDQUFDO2dCQUNsRCxNQUFNLE9BQU8sQ0FBQyxHQUFHLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUN4QyxNQUFNO1lBQ1IsS0FBSyxRQUFRO2dCQUNYLE9BQU8sQ0FBQyxJQUFJLENBQUMsc0RBQXNELENBQUMsQ0FBQztnQkFDckUsTUFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDNUMsT0FBTztTQUNWO0tBQ0Y7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLE9BQU8sQ0FBQyxLQUFLLENBQUMsOENBQThDLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDakUsTUFBTSxDQUFDLENBQUM7S0FDVDtJQUNELE9BQU8sQ0FBQyxJQUFJLENBQUMscUJBQXFCLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzdELE9BQU87UUFDTCxJQUFJLEVBQUUsT0FBTztLQUNkLENBQUM7QUFDSixDQUFDO0FBdENELDBCQXNDQztBQUFBLENBQUM7QUFFRjs7R0FFRztBQUNILEtBQUssVUFBVSxPQUFPLENBQUMsR0FBUSxFQUFFLFVBQW9CLEVBQUUsT0FBZTtJQUNwRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUMsSUFBSSxFQUFDLEVBQUU7UUFDNUMsSUFBSTtZQUNGLE9BQU8sTUFBTSxHQUFHLENBQUMsaUJBQWlCLENBQUM7Z0JBQ2pDLFVBQVUsRUFBRSxJQUFJO2dCQUNoQixZQUFZLEVBQUUsV0FBVztnQkFDekIsSUFBSSxFQUFFLENBQUM7d0JBQ0wsR0FBRyxFQUFFLE9BQU87d0JBQ1osS0FBSyxFQUFFLE1BQU07cUJBQ2QsQ0FBQzthQUNILENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztTQUNkO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLElBQUksS0FBSyxDQUFDLG1CQUFtQixJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztTQUNsRDtJQUNILENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxLQUFLLFVBQVUsVUFBVSxDQUFDLEdBQVEsRUFBRSxVQUFvQixFQUFFLE9BQWU7SUFDdkUsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFDLElBQUksRUFBQyxFQUFFO1FBQzVDLElBQUk7WUFDRixPQUFPLE1BQU0sR0FBRyxDQUFDLHNCQUFzQixDQUFDO2dCQUN0QyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUM7Z0JBQ2xCLFlBQVksRUFBRSxXQUFXO2dCQUN6QixVQUFVLEVBQUUsSUFBSTthQUNqQixDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7U0FDZDtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsUUFBUSxDQUFDLENBQUMsSUFBSSxFQUFFO2dCQUNkLGtFQUFrRTtnQkFDbEUsS0FBSyxtQkFBbUI7b0JBQ3RCLE9BQU87Z0JBQ1Q7b0JBQ0UsTUFBTSxJQUFJLEtBQUssQ0FBQywwQkFBMEIsSUFBSSxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDM0Q7U0FDRjtJQUNILENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLE1BQU0sQ0FBQyxNQUFnQixFQUFFLE1BQWdCO0lBQ2hELE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ3JELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKmVzbGludC1kaXNhYmxlIG5vLWNvbnNvbGUqL1xuLyogZXNsaW50LWRpc2FibGUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzICovXG5pbXBvcnQgeyBTU00gfSBmcm9tICdhd3Mtc2RrJztcbmltcG9ydCB7IEV4cG9ydFJlYWRlckNSUHJvcHMsIENyb3NzUmVnaW9uRXhwb3J0cyB9IGZyb20gJy4uL3R5cGVzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgY29uc3QgcHJvcHM6IEV4cG9ydFJlYWRlckNSUHJvcHMgPSBldmVudC5SZXNvdXJjZVByb3BlcnRpZXMuUmVhZGVyUHJvcHM7XG4gIGNvbnN0IGltcG9ydHM6IENyb3NzUmVnaW9uRXhwb3J0cyA9IHByb3BzLmltcG9ydHMgYXMgQ3Jvc3NSZWdpb25FeHBvcnRzO1xuICBjb25zdCBpbXBvcnROYW1lcyA9IE9iamVjdC5rZXlzKGltcG9ydHMpO1xuICBjb25zdCBrZXlOYW1lOiBzdHJpbmcgPSBgYXdzLWNkazpzdHJvbmctcmVmOiR7cHJvcHMucHJlZml4fWA7XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTSh7IHJlZ2lvbjogcHJvcHMucmVnaW9uIH0pO1xuICB0cnkge1xuICAgIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICAgIGNvbnNvbGUuaW5mbygnVGFnZ2luZyBTU00gUGFyYW1ldGVyIGltcG9ydHMnKTtcbiAgICAgICAgYXdhaXQgYWRkVGFncyhzc20sIGltcG9ydE5hbWVzLCBrZXlOYW1lKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdVcGRhdGUnOlxuICAgICAgICBjb25zdCBvbGRQcm9wczogRXhwb3J0UmVhZGVyQ1JQcm9wcyA9IGV2ZW50Lk9sZFJlc291cmNlUHJvcGVydGllcy5SZWFkZXJQcm9wcztcbiAgICAgICAgY29uc3Qgb2xkRXhwb3J0czogQ3Jvc3NSZWdpb25FeHBvcnRzID0gb2xkUHJvcHMuaW1wb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG4gICAgICAgIGNvbnN0IG5ld0V4cG9ydHMgPSBleGNlcHQoaW1wb3J0TmFtZXMsIE9iamVjdC5rZXlzKG9sZEV4cG9ydHMpKTtcbiAgICAgICAgY29uc3QgcGFyYW1zVG9SZWxlYXNlID0gZXhjZXB0KE9iamVjdC5rZXlzKG9sZEV4cG9ydHMpLCBpbXBvcnROYW1lcyk7XG4gICAgICAgIGNvbnNvbGUuaW5mbygnUmVsZWFzaW5nIHVudXNlZCBTU00gUGFyYW1ldGVyIGltcG9ydHMnKTtcbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKHBhcmFtc1RvUmVsZWFzZSkubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGF3YWl0IHJlbW92ZVRhZ3Moc3NtLCBwYXJhbXNUb1JlbGVhc2UsIGtleU5hbWUpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnNvbGUuaW5mbygnVGFnZ2luZyBuZXcgU1NNIFBhcmFtZXRlciBpbXBvcnRzJyk7XG4gICAgICAgIGF3YWl0IGFkZFRhZ3Moc3NtLCBuZXdFeHBvcnRzLCBrZXlOYW1lKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdEZWxldGUnOlxuICAgICAgICBjb25zb2xlLmluZm8oJ1JlbGVhc2luZyBhbGwgU1NNIFBhcmFtZXRlciBleHBvcnRzIGJ5IHJlbW92aW5nIHRhZ3MnKTtcbiAgICAgICAgYXdhaXQgcmVtb3ZlVGFncyhzc20sIGltcG9ydE5hbWVzLCBrZXlOYW1lKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoJ0Vycm9yIGltcG9ydGluZyBjcm9zcyByZWdpb24gc3RhY2sgZXhwb3J0czogJywgZSk7XG4gICAgdGhyb3cgZTtcbiAgfVxuICBjb25zb2xlLmluZm8oJ3JldHVybmluZyBpbXBvcnRzOiAnLCBKU09OLnN0cmluZ2lmeShpbXBvcnRzKSk7XG4gIHJldHVybiB7XG4gICAgRGF0YTogaW1wb3J0cyxcbiAgfTtcbn07XG5cbi8qKlxuICogQWRkIHRhZyB0byBwYXJhbWV0ZXJzIGZvciBleGlzdGluZyBleHBvcnRzXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGFkZFRhZ3Moc3NtOiBTU00sIHBhcmFtZXRlcnM6IHN0cmluZ1tdLCBrZXlOYW1lOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgYXdhaXQgUHJvbWlzZS5hbGwocGFyYW1ldGVycy5tYXAoYXN5bmMgbmFtZSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCBzc20uYWRkVGFnc1RvUmVzb3VyY2Uoe1xuICAgICAgICBSZXNvdXJjZUlkOiBuYW1lLFxuICAgICAgICBSZXNvdXJjZVR5cGU6ICdQYXJhbWV0ZXInLFxuICAgICAgICBUYWdzOiBbe1xuICAgICAgICAgIEtleToga2V5TmFtZSxcbiAgICAgICAgICBWYWx1ZTogJ3RydWUnLFxuICAgICAgICB9XSxcbiAgICAgIH0pLnByb21pc2UoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEVycm9yIGltcG9ydGluZyAke25hbWV9OiAke2V9YCk7XG4gICAgfVxuICB9KSk7XG59XG5cbi8qKlxuICogUmVtb3ZlIHRhZ3MgZnJvbSBwYXJhbWV0ZXJzXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlbW92ZVRhZ3Moc3NtOiBTU00sIHBhcmFtZXRlcnM6IHN0cmluZ1tdLCBrZXlOYW1lOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgYXdhaXQgUHJvbWlzZS5hbGwocGFyYW1ldGVycy5tYXAoYXN5bmMgbmFtZSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCBzc20ucmVtb3ZlVGFnc0Zyb21SZXNvdXJjZSh7XG4gICAgICAgIFRhZ0tleXM6IFtrZXlOYW1lXSxcbiAgICAgICAgUmVzb3VyY2VUeXBlOiAnUGFyYW1ldGVyJyxcbiAgICAgICAgUmVzb3VyY2VJZDogbmFtZSxcbiAgICAgIH0pLnByb21pc2UoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzd2l0Y2ggKGUuY29kZSkge1xuICAgICAgICAvLyBpZiB0aGUgcGFyYW1ldGVyIGRvZXNuJ3QgZXhpc3QgdGhlbiB0aGVyZSBpcyBub3RoaW5nIHRvIHJlbGVhc2VcbiAgICAgICAgY2FzZSAnSW52YWxpZFJlc291cmNlSWQnOlxuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEVycm9yIHJlbGVhc2luZyBpbXBvcnQgJHtuYW1lfTogJHtlfWApO1xuICAgICAgfVxuICAgIH1cbiAgfSkpO1xufVxuXG4vKipcbiAqIFJldHVybiBvbmx5IHRoZSBpdGVtcyBmcm9tIHNvdXJjZSB0aGF0IGRvIG5vdCBleGlzdCBpbiB0aGUgZmlsdGVyXG4gKlxuICogQHBhcmFtIHNvdXJjZSB0aGUgc291cmNlIG9iamVjdCB0byBwZXJmb3JtIHRoZSBmaWx0ZXIgb25cbiAqIEBwYXJhbSBmaWx0ZXIgZmlsdGVyIG91dCBpdGVtcyB0aGF0IGV4aXN0IGluIHRoaXMgb2JqZWN0XG4gKi9cbmZ1bmN0aW9uIGV4Y2VwdChzb3VyY2U6IHN0cmluZ1tdLCBmaWx0ZXI6IHN0cmluZ1tdKTogc3RyaW5nW10ge1xuICByZXR1cm4gc291cmNlLmZpbHRlcihrZXkgPT4gIWZpbHRlci5pbmNsdWRlcyhrZXkpKTtcbn1cbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js new file mode 100644 index 0000000000000..9f71f540e4994 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js @@ -0,0 +1,148 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +const aws_sdk_1 = require("aws-sdk"); +async function handler(event) { + const props = event.ResourceProperties.WriterProps; + const exports = props.exports; + const ssm = new aws_sdk_1.SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await throwIfAnyInUse(ssm, exports); + await putParameters(ssm, exports); + return; + case 'Update': + const oldProps = event.OldResourceProperties.WriterProps; + const oldExports = oldProps.exports; + const newExports = except(exports, oldExports); + // throw an error to fail the deployment if any export value is changing + const changedExports = changed(oldExports, exports); + if (changedExports.length > 0) { + throw new Error('Some exports have changed!\n' + changedExports.join('\n')); + } + // if we are removing any exports that are in use, then throw an + // error to fail the deployment + const removedExports = except(oldExports, exports); + await throwIfAnyInUse(ssm, removedExports); + // if the ones we are removing are not in use then delete them + await ssm.deleteParameters({ + Names: Object.keys(removedExports), + }).promise(); + // also throw an error if we are creating a new export that already exists for some reason + await throwIfAnyInUse(ssm, newExports); + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await putParameters(ssm, newExports); + return; + case 'Delete': + // if any of the exports are currently in use then throw an error to fail + // the stack deletion. + await throwIfAnyInUse(ssm, exports); + // if none are in use then delete all of them + await ssm.deleteParameters({ + Names: Object.keys(exports), + }).promise(); + return; + default: + return; + } + } + catch (e) { + console.error('Error processing event: ', e); + throw e; + } +} +exports.handler = handler; +; +/** + * Create parameters for existing exports + */ +async function putParameters(ssm, parameters) { + await Promise.all(Array.from(Object.entries(parameters), ([name, value]) => { + return ssm.putParameter({ + Name: name, + Value: value, + Type: 'String', + }).promise(); + })); +} +/** + * Query for existing parameters that are in use + */ +async function throwIfAnyInUse(ssm, parameters) { + const tagResults = new Map(); + await Promise.all(Object.keys(parameters).map(async (name) => { + const result = await isInUse(ssm, name); + if (result.size > 0) { + tagResults.set(name, result); + } + })); + if (tagResults.size > 0) { + const message = Object.entries(tagResults) + .map((result) => `${result[0]} is in use by stack(s) ${result[1].join(' ')}`) + .join('\n'); + throw new Error(`Exports cannot be updated: \n${message}`); + } +} +/** + * Check if a parameter is in use + */ +async function isInUse(ssm, parameterName) { + const tagResults = new Set(); + try { + const result = await ssm.listTagsForResource({ + ResourceId: parameterName, + ResourceType: 'Parameter', + }).promise(); + result.TagList?.forEach(tag => { + const tagParts = tag.Key.split(':'); + if (tagParts[0] === 'aws-cdk' && tagParts[1] === 'strong-ref') { + tagResults.add(tagParts[2]); + } + }); + } + catch (e) { + // an InvalidResourceId means that the parameter doesn't exist + // which we should ignore since that means it's not in use + if (e.code === 'InvalidResourceId') { + return new Set(); + } + throw e; + } + return tagResults; +} +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + * @returns any exports that don't exist in the filter + */ +function except(source, filter) { + return Object.keys(source) + .filter(key => (!filter.hasOwnProperty(key))) + .reduce((acc, curr) => { + acc[curr] = source[curr]; + return acc; + }, {}); +} +/** + * Return items that exist in both the the old parameters and the new parameters, + * but have different values + * + * @param oldParams the exports that existed previous to this execution + * @param newParams the exports for the current execution + * @returns any parameters that have different values + */ +function changed(oldParams, newParams) { + return Object.keys(oldParams) + .filter(key => (newParams.hasOwnProperty(key) && oldParams[key] !== newParams[key])) + .reduce((acc, curr) => { + acc.push(curr); + return acc; + }, []); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFDN0Isc0RBQXNEO0FBQ3RELHFDQUE4QjtBQUd2QixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLE1BQU0sS0FBSyxHQUF3QixLQUFLLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUE2QixDQUFDO0lBRXBELE1BQU0sR0FBRyxHQUFHLElBQUksYUFBRyxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLElBQUk7UUFDRixRQUFRLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDekIsS0FBSyxRQUFRO2dCQUNYLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0RBQWdELEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO2dCQUM3RSxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLE1BQU0sYUFBYSxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDbEMsT0FBTztZQUNULEtBQUssUUFBUTtnQkFDWCxNQUFNLFFBQVEsR0FBd0IsS0FBSyxDQUFDLHFCQUFxQixDQUFDLFdBQVcsQ0FBQztnQkFDOUUsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLE9BQTZCLENBQUM7Z0JBQzFELE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUM7Z0JBRS9DLHdFQUF3RTtnQkFDeEUsTUFBTSxjQUFjLEdBQUcsT0FBTyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDcEQsSUFBSSxjQUFjLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtvQkFDN0IsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsR0FBRSxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7aUJBQzVFO2dCQUNELGdFQUFnRTtnQkFDaEUsK0JBQStCO2dCQUMvQixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUNuRCxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDLENBQUM7Z0JBQzNDLDhEQUE4RDtnQkFDOUQsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQztpQkFDbkMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUViLDBGQUEwRjtnQkFDMUYsTUFBTSxlQUFlLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUN2QyxPQUFPLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztnQkFDN0UsTUFBTSxhQUFhLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUNyQyxPQUFPO1lBQ1QsS0FBSyxRQUFRO2dCQUNYLHlFQUF5RTtnQkFDekUsc0JBQXNCO2dCQUN0QixNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLDZDQUE2QztnQkFDN0MsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztpQkFDNUIsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNiLE9BQU87WUFDVDtnQkFDRSxPQUFPO1NBQ1Y7S0FDRjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsT0FBTyxDQUFDLEtBQUssQ0FBQywwQkFBMEIsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUM3QyxNQUFNLENBQUMsQ0FBQztLQUNUO0FBQ0gsQ0FBQztBQXBERCwwQkFvREM7QUFBQSxDQUFDO0FBRUY7O0dBRUc7QUFDSCxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQVEsRUFBRSxVQUE4QjtJQUNuRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRTtRQUN6RSxPQUFPLEdBQUcsQ0FBQyxZQUFZLENBQUM7WUFDdEIsSUFBSSxFQUFFLElBQUk7WUFDVixLQUFLLEVBQUUsS0FBSztZQUNaLElBQUksRUFBRSxRQUFRO1NBQ2YsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUM7QUFFRDs7R0FFRztBQUNILEtBQUssVUFBVSxlQUFlLENBQUMsR0FBUSxFQUFFLFVBQThCO0lBQ3JFLE1BQU0sVUFBVSxHQUE2QixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsSUFBWSxFQUFFLEVBQUU7UUFDbkUsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3hDLElBQUksTUFBTSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUU7WUFDbkIsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7U0FDOUI7SUFDSCxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRUosSUFBSSxVQUFVLENBQUMsSUFBSSxHQUFHLENBQUMsRUFBRTtRQUN2QixNQUFNLE9BQU8sR0FBVyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQzthQUMvQyxHQUFHLENBQUMsQ0FBQyxNQUEwQixFQUFFLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsMEJBQTBCLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQzthQUNoRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO0tBQzVEO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsS0FBSyxVQUFVLE9BQU8sQ0FBQyxHQUFRLEVBQUUsYUFBcUI7SUFDcEQsTUFBTSxVQUFVLEdBQWdCLElBQUksR0FBRyxFQUFFLENBQUM7SUFDMUMsSUFBSTtRQUNGLE1BQU0sTUFBTSxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDO1lBQzNDLFVBQVUsRUFBRSxhQUFhO1lBQ3pCLFlBQVksRUFBRSxXQUFXO1NBQzFCLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1lBQzVCLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3BDLElBQUksUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLFNBQVMsSUFBSSxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssWUFBWSxFQUFFO2dCQUM3RCxVQUFVLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQzdCO1FBQ0gsQ0FBQyxDQUFDLENBQUM7S0FDSjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsOERBQThEO1FBQzlELDBEQUEwRDtRQUMxRCxJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssbUJBQW1CLEVBQUU7WUFDbEMsT0FBTyxJQUFJLEdBQUcsRUFBRSxDQUFDO1NBQ2xCO1FBQ0QsTUFBTSxDQUFDLENBQUM7S0FDVDtJQUNELE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLE1BQU0sQ0FBQyxNQUEwQixFQUFFLE1BQTBCO0lBQ3BFLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7U0FDdkIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztTQUM1QyxNQUFNLENBQUMsQ0FBQyxHQUF1QixFQUFFLElBQVksRUFBRSxFQUFFO1FBQ2hELEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekIsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQVMsT0FBTyxDQUFDLFNBQTZCLEVBQUUsU0FBNkI7SUFDM0UsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztTQUMxQixNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLElBQUksU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1NBQ25GLE1BQU0sQ0FBQyxDQUFDLEdBQWEsRUFBRSxJQUFZLEVBQUUsRUFBRTtRQUN0QyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2YsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyplc2xpbnQtZGlzYWJsZSBuby1jb25zb2xlKi9cbi8qIGVzbGludC1kaXNhYmxlIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llcyAqL1xuaW1wb3J0IHsgU1NNIH0gZnJvbSAnYXdzLXNkayc7XG5pbXBvcnQgeyBDcm9zc1JlZ2lvbkV4cG9ydHMsIEV4cG9ydFdyaXRlckNSUHJvcHMgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICBjb25zdCBleHBvcnRzID0gcHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTSh7IHJlZ2lvbjogcHJvcHMucmVnaW9uIH0pO1xuICB0cnkge1xuICAgIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICAgIGNvbnNvbGUuaW5mbyhgQ3JlYXRpbmcgbmV3IFNTTSBQYXJhbWV0ZXIgZXhwb3J0cyBpbiByZWdpb24gJHtwcm9wcy5yZWdpb259YCk7XG4gICAgICAgIGF3YWl0IHRocm93SWZBbnlJblVzZShzc20sIGV4cG9ydHMpO1xuICAgICAgICBhd2FpdCBwdXRQYXJhbWV0ZXJzKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGNhc2UgJ1VwZGF0ZSc6XG4gICAgICAgIGNvbnN0IG9sZFByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICAgICAgICBjb25zdCBvbGRFeHBvcnRzID0gb2xkUHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG4gICAgICAgIGNvbnN0IG5ld0V4cG9ydHMgPSBleGNlcHQoZXhwb3J0cywgb2xkRXhwb3J0cyk7XG5cbiAgICAgICAgLy8gdGhyb3cgYW4gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudCBpZiBhbnkgZXhwb3J0IHZhbHVlIGlzIGNoYW5naW5nXG4gICAgICAgIGNvbnN0IGNoYW5nZWRFeHBvcnRzID0gY2hhbmdlZChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgaWYgKGNoYW5nZWRFeHBvcnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NvbWUgZXhwb3J0cyBoYXZlIGNoYW5nZWQhXFxuJysgY2hhbmdlZEV4cG9ydHMuam9pbignXFxuJykpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGlmIHdlIGFyZSByZW1vdmluZyBhbnkgZXhwb3J0cyB0aGF0IGFyZSBpbiB1c2UsIHRoZW4gdGhyb3cgYW5cbiAgICAgICAgLy8gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudFxuICAgICAgICBjb25zdCByZW1vdmVkRXhwb3J0cyA9IGV4Y2VwdChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgcmVtb3ZlZEV4cG9ydHMpO1xuICAgICAgICAvLyBpZiB0aGUgb25lcyB3ZSBhcmUgcmVtb3ZpbmcgYXJlIG5vdCBpbiB1c2UgdGhlbiBkZWxldGUgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKHJlbW92ZWRFeHBvcnRzKSxcbiAgICAgICAgfSkucHJvbWlzZSgpO1xuXG4gICAgICAgIC8vIGFsc28gdGhyb3cgYW4gZXJyb3IgaWYgd2UgYXJlIGNyZWF0aW5nIGEgbmV3IGV4cG9ydCB0aGF0IGFscmVhZHkgZXhpc3RzIGZvciBzb21lIHJlYXNvblxuICAgICAgICBhd2FpdCB0aHJvd0lmQW55SW5Vc2Uoc3NtLCBuZXdFeHBvcnRzKTtcbiAgICAgICAgY29uc29sZS5pbmZvKGBDcmVhdGluZyBuZXcgU1NNIFBhcmFtZXRlciBleHBvcnRzIGluIHJlZ2lvbiAke3Byb3BzLnJlZ2lvbn1gKTtcbiAgICAgICAgYXdhaXQgcHV0UGFyYW1ldGVycyhzc20sIG5ld0V4cG9ydHMpO1xuICAgICAgICByZXR1cm47XG4gICAgICBjYXNlICdEZWxldGUnOlxuICAgICAgICAvLyBpZiBhbnkgb2YgdGhlIGV4cG9ydHMgYXJlIGN1cnJlbnRseSBpbiB1c2UgdGhlbiB0aHJvdyBhbiBlcnJvciB0byBmYWlsXG4gICAgICAgIC8vIHRoZSBzdGFjayBkZWxldGlvbi5cbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIC8vIGlmIG5vbmUgYXJlIGluIHVzZSB0aGVuIGRlbGV0ZSBhbGwgb2YgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKGV4cG9ydHMpLFxuICAgICAgICB9KS5wcm9taXNlKCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIGV2ZW50OiAnLCBlKTtcbiAgICB0aHJvdyBlO1xuICB9XG59O1xuXG4vKipcbiAqIENyZWF0ZSBwYXJhbWV0ZXJzIGZvciBleGlzdGluZyBleHBvcnRzXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHB1dFBhcmFtZXRlcnMoc3NtOiBTU00sIHBhcmFtZXRlcnM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IFByb21pc2U8dm9pZD4ge1xuICBhd2FpdCBQcm9taXNlLmFsbChBcnJheS5mcm9tKE9iamVjdC5lbnRyaWVzKHBhcmFtZXRlcnMpLCAoW25hbWUsIHZhbHVlXSkgPT4ge1xuICAgIHJldHVybiBzc20ucHV0UGFyYW1ldGVyKHtcbiAgICAgIE5hbWU6IG5hbWUsXG4gICAgICBWYWx1ZTogdmFsdWUsXG4gICAgICBUeXBlOiAnU3RyaW5nJyxcbiAgICB9KS5wcm9taXNlKCk7XG4gIH0pKTtcbn1cblxuLyoqXG4gKiBRdWVyeSBmb3IgZXhpc3RpbmcgcGFyYW1ldGVycyB0aGF0IGFyZSBpbiB1c2VcbiAqL1xuYXN5bmMgZnVuY3Rpb24gdGhyb3dJZkFueUluVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJzOiBDcm9zc1JlZ2lvbkV4cG9ydHMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgdGFnUmVzdWx0czogTWFwPHN0cmluZywgU2V0PHN0cmluZz4+ID0gbmV3IE1hcCgpO1xuICBhd2FpdCBQcm9taXNlLmFsbChPYmplY3Qua2V5cyhwYXJhbWV0ZXJzKS5tYXAoYXN5bmMgKG5hbWU6IHN0cmluZykgPT4ge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGlzSW5Vc2Uoc3NtLCBuYW1lKTtcbiAgICBpZiAocmVzdWx0LnNpemUgPiAwKSB7XG4gICAgICB0YWdSZXN1bHRzLnNldChuYW1lLCByZXN1bHQpO1xuICAgIH1cbiAgfSkpO1xuXG4gIGlmICh0YWdSZXN1bHRzLnNpemUgPiAwKSB7XG4gICAgY29uc3QgbWVzc2FnZTogc3RyaW5nID0gT2JqZWN0LmVudHJpZXModGFnUmVzdWx0cylcbiAgICAgIC5tYXAoKHJlc3VsdDogW3N0cmluZywgc3RyaW5nW11dKSA9PiBgJHtyZXN1bHRbMF19IGlzIGluIHVzZSBieSBzdGFjayhzKSAke3Jlc3VsdFsxXS5qb2luKCcgJyl9YClcbiAgICAgIC5qb2luKCdcXG4nKTtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cG9ydHMgY2Fubm90IGJlIHVwZGF0ZWQ6IFxcbiR7bWVzc2FnZX1gKTtcbiAgfVxufVxuXG4vKipcbiAqIENoZWNrIGlmIGEgcGFyYW1ldGVyIGlzIGluIHVzZVxuICovXG5hc3luYyBmdW5jdGlvbiBpc0luVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJOYW1lOiBzdHJpbmcpOiBQcm9taXNlPFNldDxzdHJpbmc+PiB7XG4gIGNvbnN0IHRhZ1Jlc3VsdHM6IFNldDxzdHJpbmc+ID0gbmV3IFNldCgpO1xuICB0cnkge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHNzbS5saXN0VGFnc0ZvclJlc291cmNlKHtcbiAgICAgIFJlc291cmNlSWQ6IHBhcmFtZXRlck5hbWUsXG4gICAgICBSZXNvdXJjZVR5cGU6ICdQYXJhbWV0ZXInLFxuICAgIH0pLnByb21pc2UoKTtcbiAgICByZXN1bHQuVGFnTGlzdD8uZm9yRWFjaCh0YWcgPT4ge1xuICAgICAgY29uc3QgdGFnUGFydHMgPSB0YWcuS2V5LnNwbGl0KCc6Jyk7XG4gICAgICBpZiAodGFnUGFydHNbMF0gPT09ICdhd3MtY2RrJyAmJiB0YWdQYXJ0c1sxXSA9PT0gJ3N0cm9uZy1yZWYnKSB7XG4gICAgICAgIHRhZ1Jlc3VsdHMuYWRkKHRhZ1BhcnRzWzJdKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIGFuIEludmFsaWRSZXNvdXJjZUlkIG1lYW5zIHRoYXQgdGhlIHBhcmFtZXRlciBkb2Vzbid0IGV4aXN0XG4gICAgLy8gd2hpY2ggd2Ugc2hvdWxkIGlnbm9yZSBzaW5jZSB0aGF0IG1lYW5zIGl0J3Mgbm90IGluIHVzZVxuICAgIGlmIChlLmNvZGUgPT09ICdJbnZhbGlkUmVzb3VyY2VJZCcpIHtcbiAgICAgIHJldHVybiBuZXcgU2V0KCk7XG4gICAgfVxuICAgIHRocm93IGU7XG4gIH1cbiAgcmV0dXJuIHRhZ1Jlc3VsdHM7XG59XG5cbi8qKlxuICogUmV0dXJuIG9ubHkgdGhlIGl0ZW1zIGZyb20gc291cmNlIHRoYXQgZG8gbm90IGV4aXN0IGluIHRoZSBmaWx0ZXJcbiAqXG4gKiBAcGFyYW0gc291cmNlIHRoZSBzb3VyY2Ugb2JqZWN0IHRvIHBlcmZvcm0gdGhlIGZpbHRlciBvblxuICogQHBhcmFtIGZpbHRlciBmaWx0ZXIgb3V0IGl0ZW1zIHRoYXQgZXhpc3QgaW4gdGhpcyBvYmplY3RcbiAqIEByZXR1cm5zIGFueSBleHBvcnRzIHRoYXQgZG9uJ3QgZXhpc3QgaW4gdGhlIGZpbHRlclxuICovXG5mdW5jdGlvbiBleGNlcHQoc291cmNlOiBDcm9zc1JlZ2lvbkV4cG9ydHMsIGZpbHRlcjogQ3Jvc3NSZWdpb25FeHBvcnRzKTogQ3Jvc3NSZWdpb25FeHBvcnRzIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKHNvdXJjZSlcbiAgICAuZmlsdGVyKGtleSA9PiAoIWZpbHRlci5oYXNPd25Qcm9wZXJ0eShrZXkpKSlcbiAgICAucmVkdWNlKChhY2M6IENyb3NzUmVnaW9uRXhwb3J0cywgY3Vycjogc3RyaW5nKSA9PiB7XG4gICAgICBhY2NbY3Vycl0gPSBzb3VyY2VbY3Vycl07XG4gICAgICByZXR1cm4gYWNjO1xuICAgIH0sIHt9KTtcbn1cblxuLyoqXG4gKiBSZXR1cm4gaXRlbXMgdGhhdCBleGlzdCBpbiBib3RoIHRoZSB0aGUgb2xkIHBhcmFtZXRlcnMgYW5kIHRoZSBuZXcgcGFyYW1ldGVycyxcbiAqIGJ1dCBoYXZlIGRpZmZlcmVudCB2YWx1ZXNcbiAqXG4gKiBAcGFyYW0gb2xkUGFyYW1zIHRoZSBleHBvcnRzIHRoYXQgZXhpc3RlZCBwcmV2aW91cyB0byB0aGlzIGV4ZWN1dGlvblxuICogQHBhcmFtIG5ld1BhcmFtcyB0aGUgZXhwb3J0cyBmb3IgdGhlIGN1cnJlbnQgZXhlY3V0aW9uXG4gKiBAcmV0dXJucyBhbnkgcGFyYW1ldGVycyB0aGF0IGhhdmUgZGlmZmVyZW50IHZhbHVlc1xuICovXG5mdW5jdGlvbiBjaGFuZ2VkKG9sZFBhcmFtczogQ3Jvc3NSZWdpb25FeHBvcnRzLCBuZXdQYXJhbXM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IHN0cmluZ1tdIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKG9sZFBhcmFtcylcbiAgICAuZmlsdGVyKGtleSA9PiAobmV3UGFyYW1zLmhhc093blByb3BlcnR5KGtleSkgJiYgb2xkUGFyYW1zW2tleV0gIT09IG5ld1BhcmFtc1trZXldKSlcbiAgICAucmVkdWNlKChhY2M6IHN0cmluZ1tdLCBjdXJyOiBzdHJpbmcpID0+IHtcbiAgICAgIGFjYy5wdXNoKGN1cnIpO1xuICAgICAgcmV0dXJuIGFjYztcbiAgICB9LCBbXSk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.assets.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.assets.json new file mode 100644 index 0000000000000..84c977fbd60ae --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.assets.json @@ -0,0 +1,34 @@ +{ + "version": "21.0.0", + "files": { + "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3": { + "source": { + "path": "asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3", + "packaging": "zip" + }, + "destinations": { + "12345678-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-12345678-us-east-1", + "objectKey": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-us-east-1" + } + } + }, + "0b14fe35281a6e4200c148a24548f1b44d7a5accad61a70b660cc9d8ddd984b7": { + "source": { + "path": "integ-acm-stack.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-12345678-us-east-1", + "objectKey": "0b14fe35281a6e4200c148a24548f1b44d7a5accad61a70b660cc9d8ddd984b7.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.template.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.template.json new file mode 100644 index 0000000000000..98fe3105c3147 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-acm-stack.template.json @@ -0,0 +1,136 @@ +{ + "Resources": { + "Cert5C9FAEC1": { + "Type": "AWS::CertificateManager::Certificate", + "Properties": { + "DomainName": "*.example.com", + "DomainValidationOptions": [ + { + "DomainName": "*.example.com", + "HostedZoneId": "Z23ABC4XYZL05B" + } + ], + "ValidationMethod": "DNS" + } + }, + "ExportsWriteruseast2828FA26B86FBEFA7": { + "Type": "Custom::CrossRegionExportWriter", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A", + "Arn" + ] + }, + "WriterProps": { + "region": "us-east-2", + "exports": { + "/cdk/exports/integ-cloudfront-stack/integacmstackuseast1RefCert5C9FAEC18647F8A2": { + "Ref": "Cert5C9FAEC1" + } + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "arn:aws:ssm:us-east-2:12345678:parameter/cdk/exports/*", + "Action": [ + "ssm:DeleteParameters", + "ssm:ListTagsForResource", + "ssm:GetParameters", + "ssm:PutParameter" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-us-east-1", + "S3Key": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.assets.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.assets.json new file mode 100644 index 0000000000000..3946746155b52 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.assets.json @@ -0,0 +1,34 @@ +{ + "version": "21.0.0", + "files": { + "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741": { + "source": { + "path": "asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741", + "packaging": "zip" + }, + "destinations": { + "12345678-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-12345678-us-east-2", + "objectKey": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-us-east-2" + } + } + }, + "88d1a4132b2a5bcd2d72a458f2ad58efc59bdd316e41bbf4b8cacad3720e5d7d": { + "source": { + "path": "integ-cloudfront-stack.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-12345678-us-east-2", + "objectKey": "88d1a4132b2a5bcd2d72a458f2ad58efc59bdd316e41bbf4b8cacad3720e5d7d.json", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-us-east-2" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.template.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.template.json new file mode 100644 index 0000000000000..43aed077d4116 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ-cloudfront-stack.template.json @@ -0,0 +1,159 @@ +{ + "Resources": { + "Distro87EBE6BA": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Aliases": [ + "*.example.com" + ], + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "TargetOriginId": "integcloudfrontstackDistroOrigin1746AED30", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "*.example.com", + "Id": "integcloudfrontstackDistroOrigin1746AED30" + } + ], + "ViewerCertificate": { + "AcmCertificateArn": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/integ-cloudfront-stack/integacmstackuseast1RefCert5C9FAEC18647F8A2" + ] + }, + "MinimumProtocolVersion": "TLSv1.2_2021", + "SslSupportMethod": "sni-only" + } + } + } + }, + "ExportsReader8B249524": { + "Type": "Custom::CrossRegionExportReader", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68", + "Arn" + ] + }, + "ReaderProps": { + "region": "us-east-2", + "prefix": "integ-cloudfront-stack", + "imports": { + "/cdk/exports/integ-cloudfront-stack/integacmstackuseast1RefCert5C9FAEC18647F8A2": "{{resolve:ssm:/cdk/exports/integ-cloudfront-stack/integacmstackuseast1RefCert5C9FAEC18647F8A2}}" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "arn:aws:ssm:us-east-2:12345678:parameter/cdk/exports/integ-cloudfront-stack/*", + "Action": [ + "ssm:AddTagsToResource", + "ssm:RemoveTagsFromResource", + "ssm:GetParameters" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-us-east-2", + "S3Key": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ.json new file mode 100644 index 0000000000000..22501b4eb0504 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "enableLookups": true, + "version": "21.0.0", + "testCases": { + "integ-cloudfront-cross-region-acm/DefaultTest": { + "stacks": [ + "integ-cloudfront-stack" + ], + "diffAssets": false, + "assertionStack": "integ-cloudfront-cross-region-acm/DefaultTest/DeployAssert", + "assertionStackName": "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets.json new file mode 100644 index 0000000000000..34d217e8f2958 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.template.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..e1e2d290bb5f0 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cloudfront-cross-region-cert.integ.snapshot/manifest.json @@ -0,0 +1,195 @@ +{ + "version": "21.0.0", + "artifacts": { + "integ-acm-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-acm-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-acm-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/us-east-1", + "properties": { + "templateFile": "integ-acm-stack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-us-east-1/0b14fe35281a6e4200c148a24548f1b44d7a5accad61a70b660cc9d8ddd984b7.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-acm-stack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-acm-stack.assets" + ], + "metadata": { + "/integ-acm-stack/Cert/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Cert5C9FAEC1" + } + ], + "/integ-acm-stack/ExportsWriteruseast2828FA26B/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsWriteruseast2828FA26B86FBEFA7" + } + ], + "/integ-acm-stack/Custom::CrossRegionExportWriterCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + } + ], + "/integ-acm-stack/Custom::CrossRegionExportWriterCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A" + } + ], + "/integ-acm-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-acm-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-acm-stack" + }, + "integ-cloudfront-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-cloudfront-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-cloudfront-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/us-east-2", + "properties": { + "templateFile": "integ-cloudfront-stack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-us-east-2", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-us-east-2", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-us-east-2/88d1a4132b2a5bcd2d72a458f2ad58efc59bdd316e41bbf4b8cacad3720e5d7d.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-cloudfront-stack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-us-east-2", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-acm-stack", + "integ-cloudfront-stack.assets" + ], + "metadata": { + "/integ-cloudfront-stack/Distro/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Distro87EBE6BA" + } + ], + "/integ-cloudfront-stack/ExportsReader/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsReader8B249524" + } + ], + "/integ-cloudfront-stack/Custom::CrossRegionExportReaderCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + } + ], + "/integ-cloudfront-stack/Custom::CrossRegionExportReaderCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68" + } + ], + "/integ-cloudfront-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-cloudfront-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-cloudfront-stack" + }, + "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integcloudfrontcrossregionacmDefaultTestDeployAssertD48673AA.assets" + ], + "metadata": { + "/integ-cloudfront-cross-region-acm/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-cloudfront-cross-region-acm/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-cloudfront-cross-region-acm/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-cross-region-cert.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-cross-region-cert.ts new file mode 100644 index 0000000000000..546e9c0a97ccc --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-cross-region-cert.ts @@ -0,0 +1,56 @@ +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const account = process.env.CDK_INTEG_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT; +const hostedZoneId = process.env.CDK_INTEG_HOSTED_ZONE_ID ?? process.env.HOSTED_ZONE_ID; +if (!hostedZoneId) throw new Error('For this test you must provide your own HostedZoneId as an env var "HOSTED_ZONE_ID"'); +const hostedZoneName = process.env.CDK_INTEG_HOSTED_ZONE_NAME ?? process.env.HOSTED_ZONE_NAME; +if (!hostedZoneName) throw new Error('For this test you must provide your own HostedZoneName as an env var "HOSTED_ZONE_NAME"'); +const domainName = process.env.CDK_INTEG_DOMAIN_NAME ?? process.env.DOMAIN_NAME; +if (!domainName) throw new Error('For this test you must provide your own Domain Name as an env var "DOMAIN_NAME"'); + +const app = new cdk.App({ + treeMetadata: false, +}); +const acmStack = new cdk.Stack(app, 'integ-acm-stack', { + env: { + region: 'us-east-1', + account, + }, + crossRegionReferences: true, +}); + +const cloudFrontStack = new cdk.Stack(app, 'integ-cloudfront-stack', { + env: { + region: 'us-east-2', + account, + }, + crossRegionReferences: true, +}); + + +const hostedZone = route53.PublicHostedZone.fromHostedZoneAttributes(acmStack, 'HostedZone', { + hostedZoneId, + zoneName: hostedZoneName, +}); + +const cert = new acm.Certificate(acmStack, 'Cert', { + domainName, + validation: acm.CertificateValidation.fromDns(hostedZone), +}); + +new cloudfront.Distribution(cloudFrontStack, 'Distro', { + defaultBehavior: { origin: new TestOrigin(domainName) }, + certificate: cert, + domainNames: [domainName], +}); + +new IntegTest(app, 'integ-cloudfront-cross-region-acm', { + testCases: [cloudFrontStack], + diffAssets: false, + enableLookups: true, +}); diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index ec5449698294a..fda0f72ee5c69 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -11,6 +11,24 @@ +## Table of Contents + +- [Introduction](#introduction) +- Deploying to Amazon EC2 and on-premise instances + - [EC2/on-premise Applications](#ec2on-premise-applications) + - [EC2/on-premise Deployment Groups](#ec2on-premise-deployment-groups) + - [EC2/on-premise Deployment Configurations](#ec2on-premise-deployment-configurations) +- Deploying to AWS Lambda functions + - [Lambda Applications](#lambda-applications) + - [Lambda Deployment Groups](#lambda-deployment-groups) + - [Lambda Deployment Configurations](#lambda-deployment-configurations) +- Deploying to Amazon ECS services + - [ECS Applications](#ecs-applications) + - [ECS Deployment Groups](#ecs-deployment-groups) + - [ECS Deployment Configurations](#ecs-deployment-configurations) + +## Introduction + AWS CodeDeploy is a deployment service that automates application deployments to Amazon EC2 instances, on-premises instances, serverless Lambda functions, or Amazon ECS services. @@ -98,7 +116,7 @@ To import an already existing Deployment Group: ```ts declare const application: codedeploy.ServerApplication; const deploymentGroup = codedeploy.ServerDeploymentGroup.fromServerDeploymentGroupAttributes( - this, + this, 'ExistingCodeDeployDeploymentGroup', { application, deploymentGroupName: 'MyExistingDeploymentGroup', @@ -226,7 +244,7 @@ In order to deploy a new version of this function: 2. Re-deploy the stack (this will trigger a deployment). 3. Monitor the CodeDeploy deployment as traffic shifts between the versions. -### Rollbacks and Alarms +### Lambda Deployment Rollbacks and Alarms CodeDeploy will roll back if the deployment fails. You can optionally trigger a rollback when one or more alarms are in a failed state: @@ -281,7 +299,7 @@ const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDep deploymentGroup.addPostHook(endToEndValidation); ``` -### Import an existing Deployment Group +### Import an existing Lambda Deployment Group To import an already existing Deployment Group: @@ -373,6 +391,235 @@ const application = codedeploy.EcsApplication.fromEcsApplicationName( ); ``` +## ECS Deployment Groups + +CodeDeploy can be used to deploy to load-balanced ECS services. +CodeDeploy performs ECS blue-green deployments by managing ECS task sets and load balancer +target groups. During a blue-green deployment, one task set and target group runs the +original version of your ECS task definition ('blue') and another task set and target group +runs the new version of your ECS task definition ('green'). + +CodeDeploy orchestrates traffic shifting during ECS blue-green deployments by using +a load balancer listener to balance incoming traffic between the 'blue' and 'green' task sets/target groups +running two different versions of your ECS task definition. +Before deployment, the load balancer listener sends 100% of requests to the 'blue' target group. +When you publish a new version of the task definition and start a CodeDeploy deployment, +CodeDeploy can send a small percentage of traffic to the new 'green' task set behind the 'green' target group, +monitor, and validate before shifting 100% of traffic to the new version. + +To create a new CodeDeploy Deployment Group that deploys to an ECS service: + +```ts +declare const myApplication: codedeploy.EcsApplication; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.FargateTaskDefinition; +declare const blueTargetGroup: elbv2.ITargetGroup; +declare const greenTargetGroup: elbv2.ITargetGroup; +declare const listener: elbv2.IApplicationListener; + +const service = new ecs.FargateService(this, 'Service', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, +}); + +new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + service, + blueGreenDeploymentConfig: { + blueTargetGroup, + greenTargetGroup, + listener, + }, + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES, +}); +``` + +In order to deploy a new task definition version to the ECS service, +deploy the changes directly through CodeDeploy using the CodeDeploy APIs or console. +When the `CODE_DEPLOY` deployment controller is used, the ECS service cannot be +deployed with a new task definition version through CloudFormation. + +For more information on the behavior of CodeDeploy blue-green deployments for ECS, see +[What happens during an Amazon ECS deployment](https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-steps-ecs.html#deployment-steps-what-happens) +in the CodeDeploy user guide. + +Note: If you wish to deploy updates to your ECS service through CDK and CloudFormation instead of directly through CodeDeploy, +using the [`CfnCodeDeployBlueGreenHook`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.CfnCodeDeployBlueGreenHook.html) +construct is the recommended approach instead of using the `EcsDeploymentGroup` construct. For a comparison +of ECS blue-green deployments through CodeDeploy (using `EcsDeploymentGroup`) and through CloudFormation (using `CfnCodeDeployBlueGreenHook`), +see [Create an Amazon ECS blue/green deployment through AWS CloudFormation](https://docs.aws.amazon.com/codedeploy/latest/userguide/deployments-create-ecs-cfn.html#differences-ecs-bg-cfn) +in the CloudFormation user guide. + +### ECS Deployment Rollbacks and Alarms + +CodeDeploy will automatically roll back if a deployment fails. +You can optionally trigger an automatic rollback when one or more alarms are in a failed state during a deployment, or if the deployment stops. + +In this example, CodeDeploy will monitor and roll back on alarms set for the +number of unhealthy ECS tasks in each of the blue and green target groups, +as well as alarms set for the number HTTP 5xx responses seen in each of the blue +and green target groups. + +```ts +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +// Alarm on the number of unhealthy ECS tasks in each target group +const blueUnhealthyHosts = new cloudwatch.Alarm(stack, 'BlueUnhealthyHosts', { + alarmName: stack.stackName + '-Unhealthy-Hosts-Blue', + metric: blueTargetGroup.metricUnhealthyHostCount(), + threshold: 1, + evaluationPeriods: 2, +}); + +const greenUnhealthyHosts = new cloudwatch.Alarm(stack, 'GreenUnhealthyHosts', { + alarmName: stack.stackName + '-Unhealthy-Hosts-Green', + metric: greenTargetGroup.metricUnhealthyHostCount(), + threshold: 1, + evaluationPeriods: 2, +}); + +// Alarm on the number of HTTP 5xx responses returned by each target group +const blueApiFailure = new cloudwatch.Alarm(stack, 'Blue5xx', { + alarmName: stack.stackName + '-Http-5xx-Blue', + metric: blueTargetGroup.metricHttpCodeTarget( + elbv2.HttpCodeTarget.TARGET_5XX_COUNT, + { period: cdk.Duration.minutes(1) }, + ), + threshold: 1, + evaluationPeriods: 1, +}); + +const greenApiFailure = new cloudwatch.Alarm(stack, 'Green5xx', { + alarmName: stack.stackName + '-Http-5xx-Green', + metric: greenTargetGroup.metricHttpCodeTarget( + elbv2.HttpCodeTarget.TARGET_5XX_COUNT, + { period: cdk.Duration.minutes(1) }, + ), + threshold: 1, + evaluationPeriods: 1, +}); + +new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + // CodeDeploy will monitor these alarms during a deployment and automatically roll back + alarms: [blueUnhealthyHosts, greenUnhealthyHosts, blueApiFailure, greenApiFailure], + autoRollback: { + // CodeDeploy will automatically roll back if a deployment is stopped + stoppedDeployment: true, + }, + service, + blueGreenDeploymentConfig: { + blueTargetGroup, + greenTargetGroup, + listener, + }, + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES, +}); +``` + +### Deployment validation and manual deployment approval + +CodeDeploy blue-green deployments provide an opportunity to validate the new task definition version running on +the 'green' ECS task set prior to shifting any production traffic to the new version. A second 'test' listener +serving traffic on a different port be added to the load balancer. For example, the test listener can serve +test traffic on port 9001 while the main listener serves production traffic on port 443. +During a blue-green deployment, CodeDeploy can then shift 100% of test traffic over to the 'green' +task set/target group prior to shifting any production traffic during the deployment. + +```ts +declare const myApplication: codedeploy.EcsApplication; +declare const service: ecs.FargateService; +declare const blueTargetGroup: elbv2.ITargetGroup; +declare const greenTargetGroup: elbv2.ITargetGroup; +declare const listener: elbv2.IApplicationListener; +declare const testListener: elbv2.IApplicationListener; + +new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + service, + blueGreenDeploymentConfig: { + blueTargetGroup, + greenTargetGroup, + listener, + testListener, + }, + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES, +}); +``` + +Automated validation steps can run during the CodeDeploy deployment after shifting test traffic and before +shifting production traffic. CodeDeploy supports registering Lambda functions as lifecycle hooks for +an ECS deployment. These Lambda functions can run automated validation steps against the test traffic +port, for example in response to the `AfterAllowTestTraffic` lifecycle hook. For more information about +how to specify the Lambda functions to run for each CodeDeploy lifecycle hook in an ECS deployment, see the +[AppSpec 'hooks' for an Amazon ECS deployment](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-ecs) +section in the CodeDeploy user guide. + +After provisioning the 'green' ECS task set and re-routing test traffic during a blue-green deployment, +CodeDeploy can wait for approval before continuing the deployment and re-routing production traffic. +During this approval wait time, you can complete additional validation steps prior to exposing the new +'green' task set to production traffic, such as manual testing through the test listener port or +running automated integration test suites. + +To approve the deployment, validation steps use the CodeDeploy +[ContinueDeployment API(https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_ContinueDeployment.html). +If the ContinueDeployment API is not called within the approval wait time period, CodeDeploy will stop the +deployment and can automatically roll back the deployment. + +```ts +new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + // The deployment will wait for approval for up to 8 hours before stopping the deployment + deploymentApprovalWaitTime: Duration.hours(8), + autoRollback: { + // CodeDeploy will automatically roll back if the 8-hour approval period times out and the deployment stops + stoppedDeployment: true, + }, + service, + blueGreenDeploymentConfig: { + blueTargetGroup, + greenTargetGroup, + listener, + testListener, + }, + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES, +}); +``` + +### Deployment bake time + +You can specify how long CodeDeploy waits before it terminates the original 'blue' ECS task set when a blue-green deployment +is complete in order to let the deployment "bake" a while. During this bake time, CodeDeploy will continue to monitor any +CloudWatch alarms specified for the deployment group and will automatically roll back if those alarms go into a failed state. + +```ts +new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + service, + blueGreenDeploymentConfig: { + blueTargetGroup, + greenTargetGroup, + listener, + // CodeDeploy will wait for 30 minutes after completing the blue-green deployment before it terminates the blue tasks + terminationWaitTime: Duration.minutes(30), + }, + // CodeDeploy will continue to monitor these alarms during the 30-minute bake time and will automatically + // roll back if they go into a failed state at any point during the deployment. + alarms: [blueUnhealthyHosts, greenUnhealthyHosts, blueApiFailure, greenApiFailure], + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES, +}); +``` + +### Import an existing ECS Deployment Group + +To import an already existing Deployment Group: + +```ts +declare const application: codedeploy.EcsApplication; +const deploymentGroup = codedeploy.EcsDeploymentGroup.fromEcsDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', { + application, + deploymentGroupName: 'MyExistingDeploymentGroup', +}); +``` + ## ECS Deployment Configurations CodeDeploy for ECS comes with predefined configurations for traffic shifting. diff --git a/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts index 4a432631375e5..762f6210e51c3 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts @@ -1,7 +1,13 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { arnForDeploymentGroup } from '../utils'; -import { IEcsApplication } from './application'; +import { CfnDeploymentGroup } from '../codedeploy.generated'; +import { AutoRollbackConfig } from '../rollback-config'; +import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration, validateName } from '../utils'; +import { IEcsApplication, EcsApplication } from './application'; import { EcsDeploymentConfig, IEcsDeploymentConfig } from './deployment-config'; /** @@ -32,14 +38,151 @@ export interface IEcsDeploymentGroup extends cdk.IResource { } /** - * Note: This class currently stands as a namespaced container for importing an ECS - * Deployment Group defined outside the CDK app until CloudFormation supports provisioning - * ECS Deployment Groups. Until then it is closed (private constructor) and does not - * extend {@link Construct}. + * Specify how the deployment behaves and how traffic is routed to the ECS service during a blue-green ECS deployment. * + * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-steps-ecs.html#deployment-steps-what-happens + * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-ecs + */ +export interface EcsBlueGreenDeploymentConfig { + /** + * The target group that will be associated with the 'blue' ECS task set during a blue-green deployment. + */ + readonly blueTargetGroup: elbv2.ITargetGroup; + + /** + * The target group that will be associated with the 'green' ECS task set during a blue-green deployment. + */ + readonly greenTargetGroup: elbv2.ITargetGroup; + + /** + * The load balancer listener used to serve production traffic and to shift production traffic from the + * 'blue' ECS task set to the 'green' ECS task set during a blue-green deployment. + */ + readonly listener: elbv2.IListener; + + /** + * The load balancer listener used to route test traffic to the 'green' ECS task set during a blue-green deployment. + * + * During a blue-green deployment, validation can occur after test traffic has been re-routed and before production + * traffic has been re-routed to the 'green' ECS task set. You can specify one or more Lambda funtions in the + * deployment's AppSpec file that run during the AfterAllowTestTraffic hook. The functions can run validation tests. + * If a validation test fails, a deployment rollback is triggered. If the validation tests succeed, the next hook in + * the deployment lifecycle, BeforeAllowTraffic, is triggered. + * + * If a test listener is not specified, the deployment will proceed to routing the production listener to the 'green' ECS task set + * and will skip the AfterAllowTestTraffic hook. + * + * @default No test listener will be added + */ + readonly testListener?: elbv2.IListener; + + /** + * Specify how long CodeDeploy waits for approval to continue a blue-green deployment before it stops the deployment. + * + * After provisioning the 'green' ECS task set and re-routing test traffic, CodeDeploy can wait for approval before + * continuing the deployment and re-routing production traffic. During this wait time, validation such as manual + * testing or running integration tests can occur using the test traffic port, prior to exposing the new 'green' task + * set to production traffic. To approve the deployment, validation steps use the CodeDeploy + * [ContinueDeployment API(https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_ContinueDeployment.html). + * If the ContinueDeployment API is not called within the wait time period, CodeDeploy will stop the deployment. + * + * By default, CodeDeploy will not wait for deployment approval. After re-routing test traffic to the 'green' ECS task set + * and running any 'AfterAllowTestTraffic' and 'BeforeAllowTraffic' lifecycle hooks, the deployment will immediately + * re-route production traffic to the 'green' ECS task set. + * + * @default 0 + */ + readonly deploymentApprovalWaitTime?: cdk.Duration; + + /** + * Specify how long CodeDeploy waits before it terminates the original 'blue' ECS task set when a blue-green deployment is complete. + * + * During this wait time, CodeDeploy will continue to monitor any CloudWatch alarms specified for the deployment group, + * and the deployment group can be configured to automatically roll back if those alarms fire. Once CodeDeploy begins to + * terminate the 'blue' ECS task set, the deployment can no longer be rolled back, manually or automatically. + * + * By default, the deployment will immediately terminate the 'blue' ECS task set after production traffic is successfully + * routed to the 'green' ECS task set. + * + * @default 0 + */ + readonly terminationWaitTime?: cdk.Duration; +} + +/** + * Construction properties for {@link EcsDeploymentGroup}. + */ +export interface EcsDeploymentGroupProps { + /** + * The reference to the CodeDeploy ECS Application that this Deployment Group belongs to. + * + * @default One will be created for you. + */ + readonly application?: IEcsApplication; + + /** + * The physical, human-readable name of the CodeDeploy Deployment Group. + * + * @default An auto-generated name will be used. + */ + readonly deploymentGroupName?: string; + + /** + * The Deployment Configuration this Deployment Group uses. + * + * @default EcsDeploymentConfig.ALL_AT_ONCE + */ + readonly deploymentConfig?: IEcsDeploymentConfig; + + /** + * The CloudWatch alarms associated with this Deployment Group. + * CodeDeploy will stop (and optionally roll back) + * a deployment if during it any of the alarms trigger. + * + * Alarms can also be added after the Deployment Group is created using the {@link #addAlarm} method. + * + * @default [] + * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html + */ + readonly alarms?: cloudwatch.IAlarm[]; + + /** + * The service Role of this Deployment Group. + * + * @default - A new Role will be created. + */ + readonly role?: iam.IRole; + + /** + * The ECS service to deploy with this Deployment Group. + */ + readonly service: ecs.IBaseService; + + /** + * The configuration options for blue-green ECS deployments + */ + readonly blueGreenDeploymentConfig: EcsBlueGreenDeploymentConfig; + + /** + * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. + * + * @default false + */ + readonly ignorePollAlarmsFailure?: boolean; + + /** + * The auto-rollback configuration for this Deployment Group. + * + * @default - default AutoRollbackConfig. + */ + readonly autoRollback?: AutoRollbackConfig; +} + +/** + * A CodeDeploy deployment group that orchestrates ECS blue-green deployments. * @resource AWS::CodeDeploy::DeploymentGroup */ -export class EcsDeploymentGroup { +export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGroup { /** * Import an ECS Deployment Group defined outside the CDK app. * @@ -55,8 +198,134 @@ export class EcsDeploymentGroup { return new ImportedEcsDeploymentGroup(scope, id, attrs); } - private constructor() { - // nothing to do until CFN supports ECS deployment groups + public readonly application: IEcsApplication; + public readonly deploymentGroupName: string; + public readonly deploymentGroupArn: string; + public readonly deploymentConfig: IEcsDeploymentConfig; + /** + * The service Role of this Deployment Group. + */ + public readonly role: iam.IRole; + + private readonly alarms: cloudwatch.IAlarm[]; + + constructor(scope: Construct, id: string, props: EcsDeploymentGroupProps) { + super(scope, id, { + physicalName: props.deploymentGroupName, + }); + + this.application = props.application || new EcsApplication(this, 'Application'); + this.alarms = props.alarms || []; + + this.role = props.role || new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.ServicePrincipal('codedeploy.amazonaws.com'), + }); + + this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS')); + this.deploymentConfig = props.deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE; + + if (cdk.Resource.isOwnedResource(props.service)) { + const cfnSvc = (props.service as ecs.BaseService).node.defaultChild as ecs.CfnService; + if (cfnSvc.deploymentController === undefined || + (cfnSvc.deploymentController! as ecs.CfnService.DeploymentControllerProperty).type !== ecs.DeploymentControllerType.CODE_DEPLOY) { + throw new Error( + 'The ECS service associated with the deployment group must use the CODE_DEPLOY deployment controller type', + ); + } + + if (cfnSvc.taskDefinition !== (props.service as ecs.BaseService).taskDefinition.family) { + throw new Error( + 'The ECS service associated with the deployment group must specify the task definition using the task definition family name only. Otherwise, the task definition cannot be updated in the stack', + ); + } + } + + const resource = new CfnDeploymentGroup(this, 'Resource', { + applicationName: this.application.applicationName, + serviceRoleArn: this.role.roleArn, + deploymentGroupName: this.physicalName, + deploymentConfigName: this.deploymentConfig.deploymentConfigName, + deploymentStyle: { + deploymentType: 'BLUE_GREEN', + deploymentOption: 'WITH_TRAFFIC_CONTROL', + }, + ecsServices: [{ + clusterName: props.service.cluster.clusterName, + serviceName: props.service.serviceName, + }], + blueGreenDeploymentConfiguration: cdk.Lazy.any({ + produce: () => this.renderBlueGreenDeploymentConfiguration(props.blueGreenDeploymentConfig), + }), + loadBalancerInfo: cdk.Lazy.any({ produce: () => this.renderLoadBalancerInfo(props.blueGreenDeploymentConfig) }), + alarmConfiguration: cdk.Lazy.any({ produce: () => renderAlarmConfiguration(this.alarms, props.ignorePollAlarmsFailure) }), + autoRollbackConfiguration: cdk.Lazy.any({ produce: () => renderAutoRollbackConfiguration(this.alarms, props.autoRollback) }), + }); + + this.deploymentGroupName = this.getResourceNameAttribute(resource.ref); + this.deploymentGroupArn = this.getResourceArnAttribute(arnForDeploymentGroup(this.application.applicationName, resource.ref), { + service: 'codedeploy', + resource: 'deploymentgroup', + resourceName: `${this.application.applicationName}/${this.physicalName}`, + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, + }); + + // If the deployment config is a construct, add a dependency to ensure the deployment config + // is created before the deployment group is. + if (Construct.isConstruct(this.deploymentConfig)) { + this.node.addDependency(this.deploymentConfig); + } + + this.node.addValidation({ validate: () => validateName('Deployment group', this.physicalName) }); + } + + /** + * Associates an additional alarm with this Deployment Group. + * + * @param alarm the alarm to associate with this Deployment Group + */ + public addAlarm(alarm: cloudwatch.IAlarm): void { + this.alarms.push(alarm); + } + + private renderBlueGreenDeploymentConfiguration(options: EcsBlueGreenDeploymentConfig): + CfnDeploymentGroup.BlueGreenDeploymentConfigurationProperty { + return { + deploymentReadyOption: { + actionOnTimeout: options.deploymentApprovalWaitTime ? 'STOP_DEPLOYMENT' : 'CONTINUE_DEPLOYMENT', + waitTimeInMinutes: options.deploymentApprovalWaitTime?.toMinutes() ?? 0, + }, + terminateBlueInstancesOnDeploymentSuccess: { + action: 'TERMINATE', + terminationWaitTimeInMinutes: options.terminationWaitTime?.toMinutes() ?? 0, + }, + }; + } + + private renderLoadBalancerInfo(options: EcsBlueGreenDeploymentConfig): CfnDeploymentGroup.LoadBalancerInfoProperty { + return { + targetGroupPairInfoList: [ + { + targetGroups: [ + { + name: options.blueTargetGroup.targetGroupName, + }, + { + name: options.greenTargetGroup.targetGroupName, + }, + ], + prodTrafficRoute: { + listenerArns: [ + options.listener.listenerArn, + ], + }, + testTrafficRoute: options.testListener ? { + listenerArns: [ + options.testListener.listenerArn, + ], + } : undefined, + }, + ], + }; } } diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 4cb2301eb5303..ad4f31f642035 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -96,6 +96,7 @@ "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", "@aws-cdk/aws-elasticloadbalancing": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -110,6 +111,7 @@ "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", "@aws-cdk/aws-elasticloadbalancing": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets.json new file mode 100644 index 0000000000000..0f418df24db57 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.template.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.assets.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.assets.json new file mode 100644 index 0000000000000..264618b304aa6 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "b345fcd928aa6573be6176063244174d3eba0846591860ef06360cba511c70f4": { + "source": { + "path": "aws-cdk-codedeploy-ecs-dg.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b345fcd928aa6573be6176063244174d3eba0846591860ef06360cba511c70f4.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.template.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.template.json new file mode 100644 index 0000000000000..9e21c911a2d42 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/aws-cdk-codedeploy-ecs-dg.template.json @@ -0,0 +1,1427 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VPCPublicSubnet1DefaultRoute91CEF279", + "VPCPublicSubnet1RouteTableAssociation0B0896DC" + ] + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "VPCPublicSubnet2DefaultRouteB7481BBA", + "VPCPublicSubnet2RouteTableAssociation5A808732" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codedeploy-ecs-dg/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "EcsCluster97242B84": { + "Type": "AWS::ECS::Cluster" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "Name": "Container", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "Family": "awscdkcodedeployecsdgTaskDef25A5A14D", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "FargateServiceAC2B3B85": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "EcsCluster97242B84" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DeploymentController": { + "Type": "CODE_DEPLOY" + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "Container", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "ServiceLBProdListenerBlueTGGroupB47699CD" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "TaskDefinition": "awscdkcodedeployecsdgTaskDef25A5A14D" + }, + "DependsOn": [ + "GreenTG71A27F2F", + "ServiceLBProdListenerBlueTGGroupB47699CD", + "ServiceLBProdListener0E7627EE", + "ServiceLBTestListener3EA49939", + "TaskDef54694570", + "TaskDefTaskRole1EDB4A67", + "TaskDef2C6A35A16", + "TaskDef2TaskRole5A51C717" + ] + }, + "FargateServiceSecurityGroup0A0E79CB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + }, + "DependsOn": [ + "GreenTG71A27F2F", + "ServiceLBTestListener3EA49939", + "TaskDef54694570", + "TaskDefTaskRole1EDB4A67", + "TaskDef2C6A35A16", + "TaskDef2TaskRole5A51C717" + ] + }, + "FargateServiceSecurityGroupfromawscdkcodedeployecsdgServiceLBSecurityGroupEC967688803C3B1119": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + }, + "ToPort": 80 + }, + "DependsOn": [ + "GreenTG71A27F2F", + "ServiceLBTestListener3EA49939", + "TaskDef54694570", + "TaskDefTaskRole1EDB4A67", + "TaskDef2C6A35A16", + "TaskDef2TaskRole5A51C717" + ] + }, + "TaskDef2TaskRole5A51C717": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef2C6A35A16": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "Name": "Container", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "Family": "awscdkcodedeployecsdgTaskDef22B5CE8FC", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDef2TaskRole5A51C717", + "Arn" + ] + } + } + }, + "ServiceLBBDAD0B9B": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internal", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "Type": "application" + } + }, + "ServiceLBSecurityGroup2EA7EDA1": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awscdkcodedeployecsdgServiceLB2A9D4A45", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + }, + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 9002", + "FromPort": 9002, + "IpProtocol": "tcp", + "ToPort": 9002 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "ServiceLBSecurityGrouptoawscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E8071D2502F": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "ServiceLBProdListener0E7627EE": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "ServiceLBProdListenerBlueTGGroupB47699CD" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ServiceLBBDAD0B9B" + }, + "Port": 80, + "Protocol": "HTTP" + }, + "DependsOn": [ + "GreenTG71A27F2F" + ] + }, + "ServiceLBProdListenerBlueTGGroupB47699CD": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "HealthCheckIntervalSeconds": 5, + "HealthCheckTimeoutSeconds": 4, + "HealthyThresholdCount": 2, + "Matcher": { + "HttpCode": "200" + }, + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "30" + }, + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], + "TargetType": "ip", + "UnhealthyThresholdCount": 3, + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + }, + "DependsOn": [ + "GreenTG71A27F2F" + ] + }, + "ServiceLBTestListener3EA49939": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "GreenTG71A27F2F" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ServiceLBBDAD0B9B" + }, + "Port": 9002, + "Protocol": "HTTP" + }, + "DependsOn": [ + "ServiceLBProdListenerBlueTGGroupB47699CD" + ] + }, + "GreenTG71A27F2F": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "HealthCheckIntervalSeconds": 5, + "HealthCheckTimeoutSeconds": 4, + "HealthyThresholdCount": 2, + "Matcher": { + "HttpCode": "200" + }, + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "30" + }, + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], + "TargetType": "ip", + "UnhealthyThresholdCount": 3, + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "BlueUnhealthyHosts48919A97": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 2, + "AlarmName": "aws-cdk-codedeploy-ecs-dg-Unhealthy-Hosts-Blue", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + } + ] + ] + } + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupFullName" + ] + } + } + ], + "MetricName": "UnHealthyHostCount", + "Namespace": "AWS/ApplicationELB", + "Period": 300, + "Statistic": "Average", + "Threshold": 1 + } + }, + "Blue5xx7E9798A6": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmName": "aws-cdk-codedeploy-ecs-dg-Http-500-Blue", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + } + ] + ] + } + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupFullName" + ] + } + } + ], + "MetricName": "HTTPCode_Target_5XX_Count", + "Namespace": "AWS/ApplicationELB", + "Period": 60, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "GreenUnhealthyHosts8D9D09C1": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 2, + "AlarmName": "aws-cdk-codedeploy-ecs-dg-Unhealthy-Hosts-Green", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + } + ] + ] + } + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupFullName" + ] + } + } + ], + "MetricName": "UnHealthyHostCount", + "Namespace": "AWS/ApplicationELB", + "Period": 300, + "Statistic": "Average", + "Threshold": 1 + } + }, + "Green5xx1A511A06": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmName": "aws-cdk-codedeploy-ecs-dg-Http-500-Green", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + } + ] + ] + } + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupFullName" + ] + } + } + ], + "MetricName": "HTTPCode_Target_5XX_Count", + "Namespace": "AWS/ApplicationELB", + "Period": 60, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "CanaryConfig039778DD": { + "Type": "AWS::CodeDeploy::DeploymentConfig", + "Properties": { + "ComputePlatform": "ECS", + "TrafficRoutingConfig": { + "TimeBasedCanary": { + "CanaryInterval": 1, + "CanaryPercentage": 20 + }, + "Type": "TimeBasedCanary" + } + } + }, + "BlueGreenDGApplication3649479D": { + "Type": "AWS::CodeDeploy::Application", + "Properties": { + "ComputePlatform": "ECS" + }, + "DependsOn": [ + "CanaryConfig039778DD" + ] + }, + "BlueGreenDGServiceRole33E3BCAC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "codedeploy" + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSCodeDeployRoleForECS" + ] + ] + } + ] + }, + "DependsOn": [ + "CanaryConfig039778DD" + ] + }, + "BlueGreenDG373AB9B0": { + "Type": "AWS::CodeDeploy::DeploymentGroup", + "Properties": { + "ApplicationName": { + "Ref": "BlueGreenDGApplication3649479D" + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "BlueGreenDGServiceRole33E3BCAC", + "Arn" + ] + }, + "AlarmConfiguration": { + "Alarms": [ + { + "Name": { + "Ref": "BlueUnhealthyHosts48919A97" + } + }, + { + "Name": { + "Ref": "Blue5xx7E9798A6" + } + }, + { + "Name": { + "Ref": "GreenUnhealthyHosts8D9D09C1" + } + }, + { + "Name": { + "Ref": "Green5xx1A511A06" + } + } + ], + "Enabled": true + }, + "AutoRollbackConfiguration": { + "Enabled": true, + "Events": [ + "DEPLOYMENT_FAILURE", + "DEPLOYMENT_STOP_ON_REQUEST", + "DEPLOYMENT_STOP_ON_ALARM" + ] + }, + "BlueGreenDeploymentConfiguration": { + "DeploymentReadyOption": { + "ActionOnTimeout": "CONTINUE_DEPLOYMENT", + "WaitTimeInMinutes": 0 + }, + "TerminateBlueInstancesOnDeploymentSuccess": { + "Action": "TERMINATE", + "TerminationWaitTimeInMinutes": 1 + } + }, + "DeploymentConfigName": { + "Ref": "CanaryConfig039778DD" + }, + "DeploymentStyle": { + "DeploymentOption": "WITH_TRAFFIC_CONTROL", + "DeploymentType": "BLUE_GREEN" + }, + "ECSServices": [ + { + "ClusterName": { + "Ref": "EcsCluster97242B84" + }, + "ServiceName": { + "Fn::GetAtt": [ + "FargateServiceAC2B3B85", + "Name" + ] + } + } + ], + "LoadBalancerInfo": { + "TargetGroupPairInfoList": [ + { + "ProdTrafficRoute": { + "ListenerArns": [ + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + }, + "TargetGroups": [ + { + "Name": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupName" + ] + } + }, + { + "Name": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupName" + ] + } + } + ], + "TestTrafficRoute": { + "ListenerArns": [ + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + } + ] + } + }, + "DependsOn": [ + "CanaryConfig039778DD" + ] + } + }, + "Outputs": { + "NewTaskDefinition": { + "Value": { + "Ref": "TaskDef2C6A35A16" + } + }, + "Subnet1Id": { + "Value": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + }, + "Subnet2Id": { + "Value": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + }, + "SecurityGroupId": { + "Value": { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + }, + "CodeDeployApplicationName": { + "Value": { + "Ref": "BlueGreenDGApplication3649479D" + } + }, + "CodeDeployDeploymentGroupName": { + "Value": { + "Ref": "BlueGreenDG373AB9B0" + } + } + }, + "Mappings": { + "ServiceprincipalMap": { + "af-south-1": { + "codedeploy": "codedeploy.af-south-1.amazonaws.com" + }, + "ap-east-1": { + "codedeploy": "codedeploy.ap-east-1.amazonaws.com" + }, + "ap-northeast-1": { + "codedeploy": "codedeploy.ap-northeast-1.amazonaws.com" + }, + "ap-northeast-2": { + "codedeploy": "codedeploy.ap-northeast-2.amazonaws.com" + }, + "ap-northeast-3": { + "codedeploy": "codedeploy.ap-northeast-3.amazonaws.com" + }, + "ap-south-1": { + "codedeploy": "codedeploy.ap-south-1.amazonaws.com" + }, + "ap-southeast-1": { + "codedeploy": "codedeploy.ap-southeast-1.amazonaws.com" + }, + "ap-southeast-2": { + "codedeploy": "codedeploy.ap-southeast-2.amazonaws.com" + }, + "ap-southeast-3": { + "codedeploy": "codedeploy.ap-southeast-3.amazonaws.com" + }, + "ca-central-1": { + "codedeploy": "codedeploy.ca-central-1.amazonaws.com" + }, + "cn-north-1": { + "codedeploy": "codedeploy.cn-north-1.amazonaws.com.cn" + }, + "cn-northwest-1": { + "codedeploy": "codedeploy.cn-northwest-1.amazonaws.com.cn" + }, + "eu-central-1": { + "codedeploy": "codedeploy.eu-central-1.amazonaws.com" + }, + "eu-north-1": { + "codedeploy": "codedeploy.eu-north-1.amazonaws.com" + }, + "eu-south-1": { + "codedeploy": "codedeploy.eu-south-1.amazonaws.com" + }, + "eu-south-2": { + "codedeploy": "codedeploy.eu-south-2.amazonaws.com" + }, + "eu-west-1": { + "codedeploy": "codedeploy.eu-west-1.amazonaws.com" + }, + "eu-west-2": { + "codedeploy": "codedeploy.eu-west-2.amazonaws.com" + }, + "eu-west-3": { + "codedeploy": "codedeploy.eu-west-3.amazonaws.com" + }, + "me-south-1": { + "codedeploy": "codedeploy.me-south-1.amazonaws.com" + }, + "sa-east-1": { + "codedeploy": "codedeploy.sa-east-1.amazonaws.com" + }, + "us-east-1": { + "codedeploy": "codedeploy.us-east-1.amazonaws.com" + }, + "us-east-2": { + "codedeploy": "codedeploy.us-east-2.amazonaws.com" + }, + "us-gov-east-1": { + "codedeploy": "codedeploy.us-gov-east-1.amazonaws.com" + }, + "us-gov-west-1": { + "codedeploy": "codedeploy.us-gov-west-1.amazonaws.com" + }, + "us-iso-east-1": { + "codedeploy": "codedeploy.amazonaws.com" + }, + "us-iso-west-1": { + "codedeploy": "codedeploy.amazonaws.com" + }, + "us-isob-east-1": { + "codedeploy": "codedeploy.amazonaws.com" + }, + "us-west-1": { + "codedeploy": "codedeploy.us-west-1.amazonaws.com" + }, + "us-west-2": { + "codedeploy": "codedeploy.us-west-2.amazonaws.com" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/integ.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/integ.json new file mode 100644 index 0000000000000..96bd46ecd60d1 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "EcsDeploymentGroupTest/DefaultTest": { + "stacks": [ + "aws-cdk-codedeploy-ecs-dg" + ], + "assertionStack": "EcsDeploymentGroupTest/DefaultTest/DeployAssert", + "assertionStackName": "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..41094d47e9452 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/manifest.json @@ -0,0 +1,423 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-codedeploy-ecs-dg.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-codedeploy-ecs-dg.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-codedeploy-ecs-dg": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-codedeploy-ecs-dg.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b345fcd928aa6573be6176063244174d3eba0846591860ef06360cba511c70f4.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-codedeploy-ecs-dg.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-codedeploy-ecs-dg.assets" + ], + "metadata": { + "/aws-cdk-codedeploy-ecs-dg/VPC/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCB9E5F0B4" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1SubnetB4246D30" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableFEE4B781" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableAssociation0B0896DC" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1DefaultRoute91CEF279" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1EIP6AD938E8" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1NATGatewayE0556630" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2Subnet74179F39" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTable6F1A15F1" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTableAssociation5A808732" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2DefaultRouteB7481BBA" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2EIP4947BC00" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2NATGateway3C070193" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1Subnet8BCA10E0" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableBE8A6027" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableAssociation347902D1" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1DefaultRouteAE1D6490" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTable0A19E10E" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTableAssociation0C73D413" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2DefaultRouteF4F5CFD2" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCIGWB7E252D3" + } + ], + "/aws-cdk-codedeploy-ecs-dg/VPC/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCVPCGW99B986DC" + } + ], + "/aws-cdk-codedeploy-ecs-dg/EcsCluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "EcsCluster97242B84" + } + ], + "/aws-cdk-codedeploy-ecs-dg/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDefTaskRole1EDB4A67" + } + ], + "/aws-cdk-codedeploy-ecs-dg/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef54694570" + } + ], + "/aws-cdk-codedeploy-ecs-dg/FargateService/Service": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateServiceAC2B3B85" + } + ], + "/aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateServiceSecurityGroup0A0E79CB" + } + ], + "/aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup/from awscdkcodedeployecsdgServiceLBSecurityGroupEC967688:80": [ + { + "type": "aws:cdk:logicalId", + "data": "FargateServiceSecurityGroupfromawscdkcodedeployecsdgServiceLBSecurityGroupEC967688803C3B1119" + } + ], + "/aws-cdk-codedeploy-ecs-dg/TaskDef2/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef2TaskRole5A51C717" + } + ], + "/aws-cdk-codedeploy-ecs-dg/TaskDef2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef2C6A35A16" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBBDAD0B9B" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBSecurityGroup2EA7EDA1" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/SecurityGroup/to awscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E:80": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBSecurityGrouptoawscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E8071D2502F" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBProdListener0E7627EE" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener/BlueTGGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBProdListenerBlueTGGroupB47699CD" + } + ], + "/aws-cdk-codedeploy-ecs-dg/ServiceLB/TestListener/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBTestListener3EA49939" + } + ], + "/aws-cdk-codedeploy-ecs-dg/GreenTG/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GreenTG71A27F2F" + } + ], + "/aws-cdk-codedeploy-ecs-dg/BlueUnhealthyHosts/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BlueUnhealthyHosts48919A97" + } + ], + "/aws-cdk-codedeploy-ecs-dg/Blue5xx/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Blue5xx7E9798A6" + } + ], + "/aws-cdk-codedeploy-ecs-dg/GreenUnhealthyHosts/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GreenUnhealthyHosts8D9D09C1" + } + ], + "/aws-cdk-codedeploy-ecs-dg/Green5xx/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Green5xx1A511A06" + } + ], + "/aws-cdk-codedeploy-ecs-dg/CanaryConfig/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CanaryConfig039778DD" + } + ], + "/aws-cdk-codedeploy-ecs-dg/BlueGreenDG/Application/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BlueGreenDGApplication3649479D" + } + ], + "/aws-cdk-codedeploy-ecs-dg/BlueGreenDG/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BlueGreenDGServiceRole33E3BCAC" + } + ], + "/aws-cdk-codedeploy-ecs-dg/BlueGreenDG/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BlueGreenDG373AB9B0" + } + ], + "/aws-cdk-codedeploy-ecs-dg/NewTaskDefinition": [ + { + "type": "aws:cdk:logicalId", + "data": "NewTaskDefinition" + } + ], + "/aws-cdk-codedeploy-ecs-dg/Subnet1Id": [ + { + "type": "aws:cdk:logicalId", + "data": "Subnet1Id" + } + ], + "/aws-cdk-codedeploy-ecs-dg/Subnet2Id": [ + { + "type": "aws:cdk:logicalId", + "data": "Subnet2Id" + } + ], + "/aws-cdk-codedeploy-ecs-dg/SecurityGroupId": [ + { + "type": "aws:cdk:logicalId", + "data": "SecurityGroupId" + } + ], + "/aws-cdk-codedeploy-ecs-dg/CodeDeployApplicationName": [ + { + "type": "aws:cdk:logicalId", + "data": "CodeDeployApplicationName" + } + ], + "/aws-cdk-codedeploy-ecs-dg/CodeDeployDeploymentGroupName": [ + { + "type": "aws:cdk:logicalId", + "data": "CodeDeployDeploymentGroupName" + } + ], + "/aws-cdk-codedeploy-ecs-dg/Service-principalMap": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceprincipalMap" + } + ], + "/aws-cdk-codedeploy-ecs-dg/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-codedeploy-ecs-dg/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-codedeploy-ecs-dg" + }, + "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "EcsDeploymentGroupTestDefaultTestDeployAssert60AABFA0.assets" + ], + "metadata": { + "/EcsDeploymentGroupTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/EcsDeploymentGroupTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "EcsDeploymentGroupTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/tree.json b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/tree.json new file mode 100644 index 0000000000000..ee435489dbcf4 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.integ.snapshot/tree.json @@ -0,0 +1,2002 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.95" + } + }, + "aws-cdk-codedeploy-ecs-dg": { + "id": "aws-cdk-codedeploy-ecs-dg", + "path": "aws-cdk-codedeploy-ecs-dg", + "children": { + "VPC": { + "id": "VPC", + "path": "aws-cdk-codedeploy-ecs-dg/VPC", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "allocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "subnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "allocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "subnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "subnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "aws-cdk-codedeploy-ecs-dg/VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "aws-cdk-codedeploy-ecs-dg/VPC/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "internetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + }, + "EcsCluster": { + "id": "EcsCluster", + "path": "aws-cdk-codedeploy-ecs-dg/EcsCluster", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/EcsCluster/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Cluster", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnCluster", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.Cluster", + "version": "0.0.0" + } + }, + "TaskDef": { + "id": "TaskDef", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef/TaskRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "essential": true, + "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "name": "Container", + "portMappings": [ + { + "containerPort": 80, + "protocol": "tcp" + } + ] + } + ], + "cpu": "256", + "family": "awscdkcodedeployecsdgTaskDef25A5A14D", + "memory": "512", + "networkMode": "awsvpc", + "requiresCompatibilities": [ + "FARGATE" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "Container": { + "id": "Container", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef/Container", + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.FargateTaskDefinition", + "version": "0.0.0" + } + }, + "FargateService": { + "id": "FargateService", + "path": "aws-cdk-codedeploy-ecs-dg/FargateService", + "children": { + "Service": { + "id": "Service", + "path": "aws-cdk-codedeploy-ecs-dg/FargateService/Service", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Service", + "aws:cdk:cloudformation:props": { + "cluster": { + "Ref": "EcsCluster97242B84" + }, + "deploymentConfiguration": { + "maximumPercent": 200, + "minimumHealthyPercent": 50 + }, + "deploymentController": { + "type": "CODE_DEPLOY" + }, + "enableEcsManagedTags": false, + "healthCheckGracePeriodSeconds": 60, + "launchType": "FARGATE", + "loadBalancers": [ + { + "targetGroupArn": { + "Ref": "ServiceLBProdListenerBlueTGGroupB47699CD" + }, + "containerName": "Container", + "containerPort": 80 + } + ], + "networkConfiguration": { + "awsvpcConfiguration": { + "assignPublicIp": "DISABLED", + "subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "securityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ] + } + }, + "taskDefinition": "awscdkcodedeployecsdgTaskDef25A5A14D" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnService", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + }, + "from awscdkcodedeployecsdgServiceLBSecurityGroupEC967688:80": { + "id": "from awscdkcodedeployecsdgServiceLBSecurityGroupEC967688:80", + "path": "aws-cdk-codedeploy-ecs-dg/FargateService/SecurityGroup/from awscdkcodedeployecsdgServiceLBSecurityGroupEC967688:80", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroupIngress", + "aws:cdk:cloudformation:props": { + "ipProtocol": "tcp", + "description": "Load balancer to target", + "fromPort": 80, + "groupId": { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + }, + "sourceSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + }, + "toPort": 80 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroupIngress", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.FargateService", + "version": "0.0.0" + } + }, + "TaskDef2": { + "id": "TaskDef2", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef2", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef2/TaskRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef2/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "essential": true, + "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "name": "Container", + "portMappings": [ + { + "containerPort": 80, + "protocol": "tcp" + } + ] + } + ], + "cpu": "256", + "family": "awscdkcodedeployecsdgTaskDef22B5CE8FC", + "memory": "512", + "networkMode": "awsvpc", + "requiresCompatibilities": [ + "FARGATE" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "TaskDef2TaskRole5A51C717", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "Container": { + "id": "Container", + "path": "aws-cdk-codedeploy-ecs-dg/TaskDef2/Container", + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.FargateTaskDefinition", + "version": "0.0.0" + } + }, + "ServiceLB": { + "id": "ServiceLB", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "aws:cdk:cloudformation:props": { + "loadBalancerAttributes": [ + { + "key": "deletion_protection.enabled", + "value": "false" + } + ], + "scheme": "internal", + "securityGroups": [ + { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + } + ], + "subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "type": "application" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnLoadBalancer", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "Automatically created Security Group for ELB awscdkcodedeployecsdgServiceLB2A9D4A45", + "securityGroupIngress": [ + { + "cidrIp": "0.0.0.0/0", + "ipProtocol": "tcp", + "fromPort": 80, + "toPort": 80, + "description": "Allow from anyone on port 80" + }, + { + "cidrIp": "0.0.0.0/0", + "ipProtocol": "tcp", + "fromPort": 9002, + "toPort": 9002, + "description": "Allow from anyone on port 9002" + } + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + }, + "to awscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E:80": { + "id": "to awscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E:80", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/SecurityGroup/to awscdkcodedeployecsdgFargateServiceSecurityGroup64C2B62E:80", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroupEgress", + "aws:cdk:cloudformation:props": { + "groupId": { + "Fn::GetAtt": [ + "ServiceLBSecurityGroup2EA7EDA1", + "GroupId" + ] + }, + "ipProtocol": "tcp", + "description": "Load balancer to target", + "destinationSecurityGroupId": { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + }, + "fromPort": 80, + "toPort": 80 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroupEgress", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "ProdListener": { + "id": "ProdListener", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::Listener", + "aws:cdk:cloudformation:props": { + "defaultActions": [ + { + "type": "forward", + "targetGroupArn": { + "Ref": "ServiceLBProdListenerBlueTGGroupB47699CD" + } + } + ], + "loadBalancerArn": { + "Ref": "ServiceLBBDAD0B9B" + }, + "port": 80, + "protocol": "HTTP" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnListener", + "version": "0.0.0" + } + }, + "BlueTGGroup": { + "id": "BlueTGGroup", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener/BlueTGGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/ProdListener/BlueTGGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "aws:cdk:cloudformation:props": { + "healthCheckIntervalSeconds": 5, + "healthCheckTimeoutSeconds": 4, + "healthyThresholdCount": 2, + "matcher": { + "httpCode": "200" + }, + "port": 80, + "protocol": "HTTP", + "targetGroupAttributes": [ + { + "key": "deregistration_delay.timeout_seconds", + "value": "30" + }, + { + "key": "stickiness.enabled", + "value": "false" + } + ], + "targetType": "ip", + "unhealthyThresholdCount": 3, + "vpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener", + "version": "0.0.0" + } + }, + "TestListener": { + "id": "TestListener", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/TestListener", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/ServiceLB/TestListener/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::Listener", + "aws:cdk:cloudformation:props": { + "defaultActions": [ + { + "type": "forward", + "targetGroupArn": { + "Ref": "GreenTG71A27F2F" + } + } + ], + "loadBalancerArn": { + "Ref": "ServiceLBBDAD0B9B" + }, + "port": 9002, + "protocol": "HTTP" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnListener", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationLoadBalancer", + "version": "0.0.0" + } + }, + "GreenTG": { + "id": "GreenTG", + "path": "aws-cdk-codedeploy-ecs-dg/GreenTG", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/GreenTG/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "aws:cdk:cloudformation:props": { + "healthCheckIntervalSeconds": 5, + "healthCheckTimeoutSeconds": 4, + "healthyThresholdCount": 2, + "matcher": { + "httpCode": "200" + }, + "port": 80, + "protocol": "HTTP", + "targetGroupAttributes": [ + { + "key": "deregistration_delay.timeout_seconds", + "value": "30" + }, + { + "key": "stickiness.enabled", + "value": "false" + } + ], + "targetType": "ip", + "unhealthyThresholdCount": 3, + "vpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationTargetGroup", + "version": "0.0.0" + } + }, + "BlueUnhealthyHosts": { + "id": "BlueUnhealthyHosts", + "path": "aws-cdk-codedeploy-ecs-dg/BlueUnhealthyHosts", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/BlueUnhealthyHosts/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 2, + "alarmName": "aws-cdk-codedeploy-ecs-dg-Unhealthy-Hosts-Blue", + "dimensions": [ + { + "name": "LoadBalancer", + "value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + } + ] + ] + } + }, + { + "name": "TargetGroup", + "value": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupFullName" + ] + } + } + ], + "metricName": "UnHealthyHostCount", + "namespace": "AWS/ApplicationELB", + "period": 300, + "statistic": "Average", + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + }, + "Blue5xx": { + "id": "Blue5xx", + "path": "aws-cdk-codedeploy-ecs-dg/Blue5xx", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/Blue5xx/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 1, + "alarmName": "aws-cdk-codedeploy-ecs-dg-Http-500-Blue", + "dimensions": [ + { + "name": "LoadBalancer", + "value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + } + ] + } + ] + ] + } + }, + { + "name": "TargetGroup", + "value": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupFullName" + ] + } + } + ], + "metricName": "HTTPCode_Target_5XX_Count", + "namespace": "AWS/ApplicationELB", + "period": 60, + "statistic": "Sum", + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + }, + "GreenUnhealthyHosts": { + "id": "GreenUnhealthyHosts", + "path": "aws-cdk-codedeploy-ecs-dg/GreenUnhealthyHosts", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/GreenUnhealthyHosts/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 2, + "alarmName": "aws-cdk-codedeploy-ecs-dg-Unhealthy-Hosts-Green", + "dimensions": [ + { + "name": "LoadBalancer", + "value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + } + ] + ] + } + }, + { + "name": "TargetGroup", + "value": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupFullName" + ] + } + } + ], + "metricName": "UnHealthyHostCount", + "namespace": "AWS/ApplicationELB", + "period": 300, + "statistic": "Average", + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + }, + "Green5xx": { + "id": "Green5xx", + "path": "aws-cdk-codedeploy-ecs-dg/Green5xx", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/Green5xx/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 1, + "alarmName": "aws-cdk-codedeploy-ecs-dg-Http-500-Green", + "dimensions": [ + { + "name": "LoadBalancer", + "value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + ] + } + ] + ] + } + }, + { + "name": "TargetGroup", + "value": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupFullName" + ] + } + } + ], + "metricName": "HTTPCode_Target_5XX_Count", + "namespace": "AWS/ApplicationELB", + "period": 60, + "statistic": "Sum", + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudwatch.Alarm", + "version": "0.0.0" + } + }, + "CanaryConfig": { + "id": "CanaryConfig", + "path": "aws-cdk-codedeploy-ecs-dg/CanaryConfig", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/CanaryConfig/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeDeploy::DeploymentConfig", + "aws:cdk:cloudformation:props": { + "computePlatform": "ECS", + "trafficRoutingConfig": { + "type": "TimeBasedCanary", + "timeBasedCanary": { + "canaryInterval": 1, + "canaryPercentage": 20 + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.CfnDeploymentConfig", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.EcsDeploymentConfig", + "version": "0.0.0" + } + }, + "BlueGreenDG": { + "id": "BlueGreenDG", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG", + "children": { + "Application": { + "id": "Application", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG/Application", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG/Application/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeDeploy::Application", + "aws:cdk:cloudformation:props": { + "computePlatform": "ECS" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.CfnApplication", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.EcsApplication", + "version": "0.0.0" + } + }, + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "codedeploy" + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSCodeDeployRoleForECS" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-codedeploy-ecs-dg/BlueGreenDG/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeDeploy::DeploymentGroup", + "aws:cdk:cloudformation:props": { + "applicationName": { + "Ref": "BlueGreenDGApplication3649479D" + }, + "serviceRoleArn": { + "Fn::GetAtt": [ + "BlueGreenDGServiceRole33E3BCAC", + "Arn" + ] + }, + "alarmConfiguration": { + "alarms": [ + { + "name": { + "Ref": "BlueUnhealthyHosts48919A97" + } + }, + { + "name": { + "Ref": "Blue5xx7E9798A6" + } + }, + { + "name": { + "Ref": "GreenUnhealthyHosts8D9D09C1" + } + }, + { + "name": { + "Ref": "Green5xx1A511A06" + } + } + ], + "enabled": true + }, + "autoRollbackConfiguration": { + "enabled": true, + "events": [ + "DEPLOYMENT_FAILURE", + "DEPLOYMENT_STOP_ON_REQUEST", + "DEPLOYMENT_STOP_ON_ALARM" + ] + }, + "blueGreenDeploymentConfiguration": { + "deploymentReadyOption": { + "actionOnTimeout": "CONTINUE_DEPLOYMENT", + "waitTimeInMinutes": 0 + }, + "terminateBlueInstancesOnDeploymentSuccess": { + "action": "TERMINATE", + "terminationWaitTimeInMinutes": 1 + } + }, + "deploymentConfigName": { + "Ref": "CanaryConfig039778DD" + }, + "deploymentStyle": { + "deploymentType": "BLUE_GREEN", + "deploymentOption": "WITH_TRAFFIC_CONTROL" + }, + "ecsServices": [ + { + "clusterName": { + "Ref": "EcsCluster97242B84" + }, + "serviceName": { + "Fn::GetAtt": [ + "FargateServiceAC2B3B85", + "Name" + ] + } + } + ], + "loadBalancerInfo": { + "targetGroupPairInfoList": [ + { + "targetGroups": [ + { + "name": { + "Fn::GetAtt": [ + "ServiceLBProdListenerBlueTGGroupB47699CD", + "TargetGroupName" + ] + } + }, + { + "name": { + "Fn::GetAtt": [ + "GreenTG71A27F2F", + "TargetGroupName" + ] + } + } + ], + "prodTrafficRoute": { + "listenerArns": [ + { + "Ref": "ServiceLBProdListener0E7627EE" + } + ] + }, + "testTrafficRoute": { + "listenerArns": [ + { + "Ref": "ServiceLBTestListener3EA49939" + } + ] + } + } + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.CfnDeploymentGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codedeploy.EcsDeploymentGroup", + "version": "0.0.0" + } + }, + "NewTaskDefinition": { + "id": "NewTaskDefinition", + "path": "aws-cdk-codedeploy-ecs-dg/NewTaskDefinition", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Subnet1Id": { + "id": "Subnet1Id", + "path": "aws-cdk-codedeploy-ecs-dg/Subnet1Id", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Subnet2Id": { + "id": "Subnet2Id", + "path": "aws-cdk-codedeploy-ecs-dg/Subnet2Id", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "SecurityGroupId": { + "id": "SecurityGroupId", + "path": "aws-cdk-codedeploy-ecs-dg/SecurityGroupId", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "CodeDeployApplicationName": { + "id": "CodeDeployApplicationName", + "path": "aws-cdk-codedeploy-ecs-dg/CodeDeployApplicationName", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "CodeDeployDeploymentGroupName": { + "id": "CodeDeployDeploymentGroupName", + "path": "aws-cdk-codedeploy-ecs-dg/CodeDeployDeploymentGroupName", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Service-principalMap": { + "id": "Service-principalMap", + "path": "aws-cdk-codedeploy-ecs-dg/Service-principalMap", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "EcsDeploymentGroupTest": { + "id": "EcsDeploymentGroupTest", + "path": "EcsDeploymentGroupTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "EcsDeploymentGroupTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "EcsDeploymentGroupTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.95" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "EcsDeploymentGroupTest/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.test.ts b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.test.ts index 70750e3406e44..62f2b4402c009 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.test.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/deployment-group.test.ts @@ -1,6 +1,39 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import * as codedeploy from '../../lib'; +const mockCluster = 'my-cluster'; +const mockService = 'my-service'; +const mockRegion = 'my-region'; +const mockAccount = 'my-account'; + +function mockEcsService(stack: cdk.Stack): ecs.IBaseService { + const serviceArn = `arn:aws:ecs:${mockRegion}:${mockAccount}:service/${mockCluster}/${mockService}`; + return ecs.BaseService.fromServiceArnWithCluster(stack, 'Service', serviceArn); +} + +function mockTargetGroup(stack: cdk.Stack, id: string): elbv2.ITargetGroup { + const targetGroupArn = `arn:aws:elasticloadbalancing:${mockRegion}:${mockAccount}:targetgroup/${id}/f7a80aba5edd5980`; + return elbv2.ApplicationTargetGroup.fromTargetGroupAttributes(stack, id, { + targetGroupArn, + }); +} + +function mockListener(stack: cdk.Stack, id: string): elbv2.IListener { + const listenerArn = `arn:aws:elasticloadbalancing:${mockRegion}:${mockAccount}:listener/app/myloadbalancer/lb-12345/${id}`; + const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(stack, 'MySecurityGroup' + id, 'sg-12345678'); + return elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener' + id, { + listenerArn, + securityGroup, + }); +} + describe('CodeDeploy ECS DeploymentGroup', () => { describe('imported with fromEcsDeploymentGroupAttributes', () => { test('defaults the Deployment Config to AllAtOnce', () => { @@ -15,4 +48,804 @@ describe('CodeDeploy ECS DeploymentGroup', () => { expect(importedGroup.deploymentConfig).toEqual(codedeploy.EcsDeploymentConfig.ALL_AT_ONCE); }); }); + + test('can be created with default configuration', () => { + const stack = new cdk.Stack(); + + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResource('AWS::CodeDeploy::DeploymentGroup', { + Type: 'AWS::CodeDeploy::DeploymentGroup', + Properties: { + ApplicationName: { + Ref: 'MyDGApplication57B1E402', + }, + ServiceRoleArn: { + 'Fn::GetAtt': [ + 'MyDGServiceRole5E94FD88', + 'Arn', + ], + }, + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + ], + }, + BlueGreenDeploymentConfiguration: { + DeploymentReadyOption: { + ActionOnTimeout: 'CONTINUE_DEPLOYMENT', + WaitTimeInMinutes: 0, + }, + TerminateBlueInstancesOnDeploymentSuccess: { + Action: 'TERMINATE', + TerminationWaitTimeInMinutes: 0, + }, + }, + DeploymentConfigName: 'CodeDeployDefault.ECSAllAtOnce', + DeploymentStyle: { + DeploymentOption: 'WITH_TRAFFIC_CONTROL', + DeploymentType: 'BLUE_GREEN', + }, + ECSServices: [ + { + ClusterName: 'my-cluster', + ServiceName: 'my-service', + }, + ], + LoadBalancerInfo: { + TargetGroupPairInfoList: [ + { + ProdTrafficRoute: { + ListenerArns: [ + 'arn:aws:elasticloadbalancing:my-region:my-account:listener/app/myloadbalancer/lb-12345/prod', + ], + }, + TargetGroups: [ + { + Name: 'blue', + }, + { + Name: 'green', + }, + ], + }, + ], + }, + }, + }); + + Template.fromStack(stack).hasResource('AWS::CodeDeploy::Application', { + Type: 'AWS::CodeDeploy::Application', + Properties: { + ComputePlatform: 'ECS', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: { + 'Fn::FindInMap': [ + 'ServiceprincipalMap', + { + Ref: 'AWS::Region', + }, + 'codedeploy', + ], + }, + }, + }], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/AWSCodeDeployRoleForECS', + ], + ], + }, + ], + }); + }); + + test('can be created with explicit name', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentGroupName: 'test', + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + DeploymentGroupName: 'test', + }); + }); + + test('can be created with explicit application', () => { + const stack = new cdk.Stack(); + const application = codedeploy.EcsApplication.fromEcsApplicationName(stack, 'A', 'myapp'); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + application, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + ApplicationName: 'myapp', + }); + }); + + test('can be created with explicit deployment config', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_15MINUTES, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + DeploymentConfigName: 'CodeDeployDefault.ECSCanary10Percent15Minutes', + }); + }); + + test('fail with more than 100 characters in name', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentGroupName: 'a'.repeat(101), + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + expect(() => app.synth()).toThrow(`Deployment group name: "${'a'.repeat(101)}" can be a max of 100 characters.`); + }); + + test('fail with unallowed characters in name', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentGroupName: 'my name', + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + expect(() => app.synth()).toThrow('Deployment group name: "my name" can only contain letters (a-z, A-Z), numbers (0-9), periods (.), underscores (_), + (plus signs), = (equals signs), , (commas), @ (at signs), - (minus signs).'); + }); + + test('fail when ECS service does not use CODE_DEPLOY deployment controller', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.ECS, + }, + }); + + expect(() => new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentGroupName: 'a'.repeat(101), + service, + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + })).toThrow('The ECS service associated with the deployment group must use the CODE_DEPLOY deployment controller type'); + }); + + test('fail when ECS service uses CODE_DEPLOY deployment controller, but did not strip the revision ID from the task definition', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + (service.node.defaultChild as ecs.CfnService).taskDefinition = 'arn:aws:ecs:us-west-2:123456789012:task-definition/hello_world:8'; + + expect(() => new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + deploymentGroupName: 'a'.repeat(101), + service, + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + })).toThrow('The ECS service associated with the deployment group must specify the task definition using the task definition family name only. Otherwise, the task definition cannot be updated in the stack'); + }); + + test('can be created with explicit role', () => { + const stack = new cdk.Stack(); + const serviceRole = new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('not-codedeploy.test'), + }); + + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + role: serviceRole, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'not-codedeploy.test', + }, + }], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/AWSCodeDeployRoleForECS', + ], + ], + }, + ], + }); + }); + + test('can rollback on alarm', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + alarms: [ + new cloudwatch.Alarm(stack, 'BlueTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'blue/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + new cloudwatch.Alarm(stack, 'GreenTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'green/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + ], + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + AlarmConfiguration: { + Alarms: [ + { + Name: { + Ref: 'BlueTGUnHealthyHostsE5A415E0', + }, + }, + { + Name: { + Ref: 'GreenTGUnHealthyHosts49873ED5', + }, + }, + ], + Enabled: true, + }, + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + 'DEPLOYMENT_STOP_ON_ALARM', + ], + }, + }); + }); + + test('can add alarms after construction', () => { + const stack = new cdk.Stack(); + const dg = new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + alarms: [ + new cloudwatch.Alarm(stack, 'BlueTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'blue/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + ], + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + dg.addAlarm(new cloudwatch.Alarm(stack, 'GreenTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'green/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + })); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + AlarmConfiguration: { + Alarms: [ + { + Name: { + Ref: 'BlueTGUnHealthyHostsE5A415E0', + }, + }, + { + Name: { + Ref: 'GreenTGUnHealthyHosts49873ED5', + }, + }, + ], + Enabled: true, + }, + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + 'DEPLOYMENT_STOP_ON_ALARM', + ], + }, + }); + }); + + test('fail if alarm rollbacks are enabled, but no alarms provided', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + autoRollback: { + deploymentInAlarm: true, + }, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + expect(() => app.synth()).toThrow('The auto-rollback setting \'deploymentInAlarm\' does not have any effect unless you associate at least one CloudWatch alarm with the Deployment Group.'); + }); + + test('can disable rollback when alarm polling fails', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + ignorePollAlarmsFailure: true, + alarms: [ + new cloudwatch.Alarm(stack, 'BlueTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'blue/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + new cloudwatch.Alarm(stack, 'GreenTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'green/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + ], + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + AlarmConfiguration: { + Alarms: [ + { + Name: { + Ref: 'BlueTGUnHealthyHostsE5A415E0', + }, + }, + { + Name: { + Ref: 'GreenTGUnHealthyHosts49873ED5', + }, + }, + ], + Enabled: true, + }, + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + 'DEPLOYMENT_STOP_ON_ALARM', + ], + }, + }); + }); + + test('can disable rollback when deployment fails', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + autoRollback: { + failedDeployment: false, + }, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResource('AWS::CodeDeploy::DeploymentGroup', { + Type: 'AWS::CodeDeploy::DeploymentGroup', + Properties: { + ApplicationName: { + Ref: 'MyDGApplication57B1E402', + }, + ServiceRoleArn: { + 'Fn::GetAtt': [ + 'MyDGServiceRole5E94FD88', + 'Arn', + ], + }, + BlueGreenDeploymentConfiguration: { + DeploymentReadyOption: { + ActionOnTimeout: 'CONTINUE_DEPLOYMENT', + WaitTimeInMinutes: 0, + }, + TerminateBlueInstancesOnDeploymentSuccess: { + Action: 'TERMINATE', + TerminationWaitTimeInMinutes: 0, + }, + }, + DeploymentConfigName: 'CodeDeployDefault.ECSAllAtOnce', + DeploymentStyle: { + DeploymentOption: 'WITH_TRAFFIC_CONTROL', + DeploymentType: 'BLUE_GREEN', + }, + ECSServices: [ + { + ClusterName: 'my-cluster', + ServiceName: 'my-service', + }, + ], + LoadBalancerInfo: { + TargetGroupPairInfoList: [ + { + ProdTrafficRoute: { + ListenerArns: [ + 'arn:aws:elasticloadbalancing:my-region:my-account:listener/app/myloadbalancer/lb-12345/prod', + ], + }, + TargetGroups: [ + { + Name: 'blue', + }, + { + Name: 'green', + }, + ], + }, + ], + }, + }, + }); + }); + + test('can enable rollback when deployment stops', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + autoRollback: { + stoppedDeployment: true, + }, + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + 'DEPLOYMENT_STOP_ON_REQUEST', + ], + }, + }); + }); + + test('can disable rollback when alarm in failure state', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + autoRollback: { + deploymentInAlarm: false, + }, + alarms: [ + new cloudwatch.Alarm(stack, 'BlueTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'blue/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + new cloudwatch.Alarm(stack, 'GreenTGUnHealthyHosts', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName: 'UnHealthyHostCount', + dimensionsMap: { + TargetGroup: 'green/f7a80aba5edd5980', + LoadBalancer: 'app/myloadbalancer/lb-12345', + }, + }), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + }), + ], + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + AlarmConfiguration: { + Alarms: [ + { + Name: { + Ref: 'BlueTGUnHealthyHostsE5A415E0', + }, + }, + { + Name: { + Ref: 'GreenTGUnHealthyHosts49873ED5', + }, + }, + ], + Enabled: true, + }, + AutoRollbackConfiguration: { + Enabled: true, + Events: [ + 'DEPLOYMENT_FAILURE', + ], + }, + }); + }); + + test('can specify a test traffic route', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + testListener: mockListener(stack, 'test'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + LoadBalancerInfo: { + TargetGroupPairInfoList: [ + { + ProdTrafficRoute: { + ListenerArns: [ + 'arn:aws:elasticloadbalancing:my-region:my-account:listener/app/myloadbalancer/lb-12345/prod', + ], + }, + TestTrafficRoute: { + ListenerArns: [ + 'arn:aws:elasticloadbalancing:my-region:my-account:listener/app/myloadbalancer/lb-12345/test', + ], + }, + TargetGroups: [ + { + Name: 'blue', + }, + { + Name: 'green', + }, + ], + }, + ], + }, + }); + }); + + test('can require manual deployment approval', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + deploymentApprovalWaitTime: Duration.hours(8), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + BlueGreenDeploymentConfiguration: { + DeploymentReadyOption: { + ActionOnTimeout: 'STOP_DEPLOYMENT', + WaitTimeInMinutes: 480, + }, + TerminateBlueInstancesOnDeploymentSuccess: { + Action: 'TERMINATE', + TerminationWaitTimeInMinutes: 0, + }, + }, + }); + }); + + test('can add deployment bake time', () => { + const stack = new cdk.Stack(); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + terminationWaitTime: Duration.hours(1), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CodeDeploy::DeploymentGroup', { + BlueGreenDeploymentConfiguration: { + DeploymentReadyOption: { + ActionOnTimeout: 'CONTINUE_DEPLOYMENT', + WaitTimeInMinutes: 0, + }, + TerminateBlueInstancesOnDeploymentSuccess: { + Action: 'TERMINATE', + TerminationWaitTimeInMinutes: 60, + }, + }, + }); + }); + + test('uses the correct Service Principal in the us-isob-east-1 region', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'CodeDeployLambdaStack', { + env: { region: 'us-isob-east-1' }, + }); + new codedeploy.EcsDeploymentGroup(stack, 'MyDG', { + service: mockEcsService(stack), + blueGreenDeploymentConfig: { + blueTargetGroup: mockTargetGroup(stack, 'blue'), + greenTargetGroup: mockTargetGroup(stack, 'green'), + listener: mockListener(stack, 'prod'), + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'codedeploy.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-codedeploy/test/ecs/integ.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/ecs/integ.deployment-group.ts new file mode 100644 index 0000000000000..96bc428c7e283 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/ecs/integ.deployment-group.ts @@ -0,0 +1,239 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as codedeploy from '../../lib'; + +/** + * Follow these instructions to manually test running a CodeDeploy deployment with the resources provisioned in this stack: + * + * 1. Deploy the stack: +``` +$ cdk deploy --app 'node integ.deployment-group.js' aws-cdk-codedeploy-ecs-dg +``` + * + * 2. Create a file called `appspec.json` with the following contents, replacing the placeholders with output values from the deployed stack: +``` +{ + "version": 0.0, + "Resources": [ + { + "TargetService": { + "Type": "AWS::ECS::Service", + "Properties": { + "TaskDefinition": "", + "LoadBalancerInfo": { + "ContainerName": "Container", + "ContainerPort": 80 + }, + "PlatformVersion": "LATEST", + "NetworkConfiguration": { + "awsvpcConfiguration": { + "subnets": [ + "", + "", + ], + "securityGroups": [ + "" + ], + "assignPublicIp": "DISABLED" + } + } + } + } + } + ] +} +``` + * + * 3. Start the deployment: +``` +$ appspec=$(jq -R -s '.' < appspec.json | sed 's/\\n//g') +$ aws deploy create-deployment \ + --application-name \ + --deployment-group-name \ + --description "AWS CDK integ test" \ + --revision revisionType=AppSpecContent,appSpecContent={content="$appspec"} +``` + * + * 4. Wait for the deployment to complete successfully, providing the deployment ID from the previous step: +``` +$ aws deploy wait deployment-successful --deployment-id +``` + * + * 5. Destroy the stack: +``` +$ cdk destroy --app 'node integ.deployment-group.js' aws-cdk-codedeploy-ecs-dg +``` + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-codedeploy-ecs-dg'); + +// Network infrastructure +const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); + +// ECS service +const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, +}); +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); +taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), + portMappings: [{ containerPort: 80 }], +}); +const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, +}); + +// A second task definition for testing a CodeDeploy deployment of the ECS service to a new task definition +const taskDefinition2 = new ecs.FargateTaskDefinition(stack, 'TaskDef2'); +taskDefinition2.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), + portMappings: [{ containerPort: 80 }], +}); +service.node.addDependency(taskDefinition2); + +// Load balancer +const loadBalancer = new elbv2.ApplicationLoadBalancer(stack, 'ServiceLB', { + vpc, + internetFacing: false, +}); + +// Listeners +const prodListener = loadBalancer.addListener('ProdListener', { + port: 80, // port for production traffic + protocol: elbv2.ApplicationProtocol.HTTP, +}); +const testListener = loadBalancer.addListener('TestListener', { + port: 9002, // port for testing + protocol: elbv2.ApplicationProtocol.HTTP, +}); + +// Target groups +const blueTG = prodListener.addTargets('BlueTG', { + port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [ + service.loadBalancerTarget({ + containerName: 'Container', + containerPort: 80, + }), + ], + deregistrationDelay: cdk.Duration.seconds(30), + healthCheck: { + interval: cdk.Duration.seconds(5), + healthyHttpCodes: '200', + healthyThresholdCount: 2, + unhealthyThresholdCount: 3, + timeout: cdk.Duration.seconds(4), + }, +}); + +const greenTG = new elbv2.ApplicationTargetGroup(stack, 'GreenTG', { + vpc, + port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, + targetType: elbv2.TargetType.IP, + deregistrationDelay: cdk.Duration.seconds(30), + healthCheck: { + interval: cdk.Duration.seconds(5), + healthyHttpCodes: '200', + healthyThresholdCount: 2, + unhealthyThresholdCount: 3, + timeout: cdk.Duration.seconds(4), + }, +}); + +testListener.addTargetGroups('GreenTGTest', { + targetGroups: [greenTG], +}); + +prodListener.node.addDependency(greenTG); +testListener.node.addDependency(blueTG); +service.node.addDependency(testListener); +service.node.addDependency(greenTG); + +// Alarms: monitor 500s and unhealthy hosts on target groups +const blueUnhealthyHosts = new cloudwatch.Alarm(stack, 'BlueUnhealthyHosts', { + alarmName: stack.stackName + '-Unhealthy-Hosts-Blue', + metric: blueTG.metricUnhealthyHostCount(), + threshold: 1, + evaluationPeriods: 2, +}); + +const blueApiFailure = new cloudwatch.Alarm(stack, 'Blue5xx', { + alarmName: stack.stackName + '-Http-500-Blue', + metric: blueTG.metricHttpCodeTarget( + elbv2.HttpCodeTarget.TARGET_5XX_COUNT, + { period: cdk.Duration.minutes(1) }, + ), + threshold: 1, + evaluationPeriods: 1, +}); + +const greenUnhealthyHosts = new cloudwatch.Alarm(stack, 'GreenUnhealthyHosts', { + alarmName: stack.stackName + '-Unhealthy-Hosts-Green', + metric: greenTG.metricUnhealthyHostCount(), + threshold: 1, + evaluationPeriods: 2, +}); + +const greenApiFailure = new cloudwatch.Alarm(stack, 'Green5xx', { + alarmName: stack.stackName + '-Http-500-Green', + metric: greenTG.metricHttpCodeTarget( + elbv2.HttpCodeTarget.TARGET_5XX_COUNT, + { period: cdk.Duration.minutes(1) }, + ), + threshold: 1, + evaluationPeriods: 1, +}); + +// Deployment group +const deploymentConfig = new codedeploy.EcsDeploymentConfig(stack, 'CanaryConfig', { + trafficRouting: codedeploy.TrafficRouting.timeBasedCanary({ + interval: cdk.Duration.minutes(1), + percentage: 20, + }), +}); + +const dg = new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', { + alarms: [ + blueUnhealthyHosts, + blueApiFailure, + greenUnhealthyHosts, + greenApiFailure, + ], + service, + blueGreenDeploymentConfig: { + blueTargetGroup: blueTG, + greenTargetGroup: greenTG, + listener: prodListener, + testListener, + terminationWaitTime: cdk.Duration.minutes(1), + }, + deploymentConfig, + autoRollback: { + stoppedDeployment: true, + }, +}); + +// Outputs to use for manual testing +new cdk.CfnOutput(stack, 'NewTaskDefinition', { value: taskDefinition2.taskDefinitionArn }); +new cdk.CfnOutput(stack, 'Subnet1Id', { value: vpc.privateSubnets[0].subnetId }); +new cdk.CfnOutput(stack, 'Subnet2Id', { value: vpc.privateSubnets[1].subnetId }); +new cdk.CfnOutput(stack, 'SecurityGroupId', { value: service.connections.securityGroups[0].securityGroupId }); +new cdk.CfnOutput(stack, 'CodeDeployApplicationName', { value: dg.application.applicationName }); +new cdk.CfnOutput(stack, 'CodeDeployDeploymentGroupName', { value: dg.deploymentGroupName }); + +new integ.IntegTest(app, 'EcsDeploymentGroupTest', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-with-replication.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-with-replication.ts new file mode 100644 index 0000000000000..045f7bda8ef06 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-with-replication.ts @@ -0,0 +1,65 @@ +import { PipelineProject } from '@aws-cdk/aws-codebuild'; +import { Pipeline, Artifact } from '@aws-cdk/aws-codepipeline'; +import { Key } from '@aws-cdk/aws-kms'; +import { Bucket } from '@aws-cdk/aws-s3'; +import { App, Stack, RemovalPolicy } from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import { S3SourceAction, CodeBuildAction } from '../lib'; + + +const app = new App({ + treeMetadata: false, +}); +const stack1 = new Stack(app, 'integ-pipeline-producer-stack', { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, +}); +const stack2 = new Stack(app, 'integ-pipeline-consumer-stack', { + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, +}); + + +const key = new Key(stack1, 'ReplicationKey'); +const bucket = new Bucket(stack1, 'ReplicationBucket', { + encryptionKey: key, + autoDeleteObjects: true, + removalPolicy: RemovalPolicy.DESTROY, +}); + +const artifact = new Artifact(); +const pipeline = new Pipeline(stack2, 'Pipeline', { + crossRegionReplicationBuckets: { + 'us-east-1': bucket, + }, +}); +const sourceBucket = new Bucket(stack2, 'SourceBucket', { + autoDeleteObjects: true, + removalPolicy: RemovalPolicy.DESTROY, +}); +pipeline.addStage({ + stageName: 'source', + actions: [new S3SourceAction({ + bucket: sourceBucket, + output: artifact, + bucketKey: '/somepath', + actionName: 'Source', + })], +}); +pipeline.addStage({ + stageName: 'stage2', + actions: [new CodeBuildAction({ + input: artifact, + actionName: 'Build', + project: new PipelineProject(stack2, 'Build'), + })], +}); + +new IntegTest(app, 'codepipeline-integ-test', { + testCases: [stack2], + stackUpdateWorkflow: false, +}); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js new file mode 100644 index 0000000000000..9f71f540e4994 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3/index.js @@ -0,0 +1,148 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +const aws_sdk_1 = require("aws-sdk"); +async function handler(event) { + const props = event.ResourceProperties.WriterProps; + const exports = props.exports; + const ssm = new aws_sdk_1.SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await throwIfAnyInUse(ssm, exports); + await putParameters(ssm, exports); + return; + case 'Update': + const oldProps = event.OldResourceProperties.WriterProps; + const oldExports = oldProps.exports; + const newExports = except(exports, oldExports); + // throw an error to fail the deployment if any export value is changing + const changedExports = changed(oldExports, exports); + if (changedExports.length > 0) { + throw new Error('Some exports have changed!\n' + changedExports.join('\n')); + } + // if we are removing any exports that are in use, then throw an + // error to fail the deployment + const removedExports = except(oldExports, exports); + await throwIfAnyInUse(ssm, removedExports); + // if the ones we are removing are not in use then delete them + await ssm.deleteParameters({ + Names: Object.keys(removedExports), + }).promise(); + // also throw an error if we are creating a new export that already exists for some reason + await throwIfAnyInUse(ssm, newExports); + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await putParameters(ssm, newExports); + return; + case 'Delete': + // if any of the exports are currently in use then throw an error to fail + // the stack deletion. + await throwIfAnyInUse(ssm, exports); + // if none are in use then delete all of them + await ssm.deleteParameters({ + Names: Object.keys(exports), + }).promise(); + return; + default: + return; + } + } + catch (e) { + console.error('Error processing event: ', e); + throw e; + } +} +exports.handler = handler; +; +/** + * Create parameters for existing exports + */ +async function putParameters(ssm, parameters) { + await Promise.all(Array.from(Object.entries(parameters), ([name, value]) => { + return ssm.putParameter({ + Name: name, + Value: value, + Type: 'String', + }).promise(); + })); +} +/** + * Query for existing parameters that are in use + */ +async function throwIfAnyInUse(ssm, parameters) { + const tagResults = new Map(); + await Promise.all(Object.keys(parameters).map(async (name) => { + const result = await isInUse(ssm, name); + if (result.size > 0) { + tagResults.set(name, result); + } + })); + if (tagResults.size > 0) { + const message = Object.entries(tagResults) + .map((result) => `${result[0]} is in use by stack(s) ${result[1].join(' ')}`) + .join('\n'); + throw new Error(`Exports cannot be updated: \n${message}`); + } +} +/** + * Check if a parameter is in use + */ +async function isInUse(ssm, parameterName) { + const tagResults = new Set(); + try { + const result = await ssm.listTagsForResource({ + ResourceId: parameterName, + ResourceType: 'Parameter', + }).promise(); + result.TagList?.forEach(tag => { + const tagParts = tag.Key.split(':'); + if (tagParts[0] === 'aws-cdk' && tagParts[1] === 'strong-ref') { + tagResults.add(tagParts[2]); + } + }); + } + catch (e) { + // an InvalidResourceId means that the parameter doesn't exist + // which we should ignore since that means it's not in use + if (e.code === 'InvalidResourceId') { + return new Set(); + } + throw e; + } + return tagResults; +} +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + * @returns any exports that don't exist in the filter + */ +function except(source, filter) { + return Object.keys(source) + .filter(key => (!filter.hasOwnProperty(key))) + .reduce((acc, curr) => { + acc[curr] = source[curr]; + return acc; + }, {}); +} +/** + * Return items that exist in both the the old parameters and the new parameters, + * but have different values + * + * @param oldParams the exports that existed previous to this execution + * @param newParams the exports for the current execution + * @returns any parameters that have different values + */ +function changed(oldParams, newParams) { + return Object.keys(oldParams) + .filter(key => (newParams.hasOwnProperty(key) && oldParams[key] !== newParams[key])) + .reduce((acc, curr) => { + acc.push(curr); + return acc; + }, []); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFDN0Isc0RBQXNEO0FBQ3RELHFDQUE4QjtBQUd2QixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLE1BQU0sS0FBSyxHQUF3QixLQUFLLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUE2QixDQUFDO0lBRXBELE1BQU0sR0FBRyxHQUFHLElBQUksYUFBRyxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLElBQUk7UUFDRixRQUFRLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDekIsS0FBSyxRQUFRO2dCQUNYLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0RBQWdELEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO2dCQUM3RSxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLE1BQU0sYUFBYSxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDbEMsT0FBTztZQUNULEtBQUssUUFBUTtnQkFDWCxNQUFNLFFBQVEsR0FBd0IsS0FBSyxDQUFDLHFCQUFxQixDQUFDLFdBQVcsQ0FBQztnQkFDOUUsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLE9BQTZCLENBQUM7Z0JBQzFELE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUM7Z0JBRS9DLHdFQUF3RTtnQkFDeEUsTUFBTSxjQUFjLEdBQUcsT0FBTyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDcEQsSUFBSSxjQUFjLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtvQkFDN0IsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsR0FBRSxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7aUJBQzVFO2dCQUNELGdFQUFnRTtnQkFDaEUsK0JBQStCO2dCQUMvQixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUNuRCxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDLENBQUM7Z0JBQzNDLDhEQUE4RDtnQkFDOUQsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQztpQkFDbkMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUViLDBGQUEwRjtnQkFDMUYsTUFBTSxlQUFlLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUN2QyxPQUFPLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztnQkFDN0UsTUFBTSxhQUFhLENBQUMsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO2dCQUNyQyxPQUFPO1lBQ1QsS0FBSyxRQUFRO2dCQUNYLHlFQUF5RTtnQkFDekUsc0JBQXNCO2dCQUN0QixNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBQ3BDLDZDQUE2QztnQkFDN0MsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLENBQUM7b0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztpQkFDNUIsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNiLE9BQU87WUFDVDtnQkFDRSxPQUFPO1NBQ1Y7S0FDRjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsT0FBTyxDQUFDLEtBQUssQ0FBQywwQkFBMEIsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUM3QyxNQUFNLENBQUMsQ0FBQztLQUNUO0FBQ0gsQ0FBQztBQXBERCwwQkFvREM7QUFBQSxDQUFDO0FBRUY7O0dBRUc7QUFDSCxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQVEsRUFBRSxVQUE4QjtJQUNuRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRTtRQUN6RSxPQUFPLEdBQUcsQ0FBQyxZQUFZLENBQUM7WUFDdEIsSUFBSSxFQUFFLElBQUk7WUFDVixLQUFLLEVBQUUsS0FBSztZQUNaLElBQUksRUFBRSxRQUFRO1NBQ2YsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNOLENBQUM7QUFFRDs7R0FFRztBQUNILEtBQUssVUFBVSxlQUFlLENBQUMsR0FBUSxFQUFFLFVBQThCO0lBQ3JFLE1BQU0sVUFBVSxHQUE2QixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsSUFBWSxFQUFFLEVBQUU7UUFDbkUsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3hDLElBQUksTUFBTSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUU7WUFDbkIsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7U0FDOUI7SUFDSCxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRUosSUFBSSxVQUFVLENBQUMsSUFBSSxHQUFHLENBQUMsRUFBRTtRQUN2QixNQUFNLE9BQU8sR0FBVyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQzthQUMvQyxHQUFHLENBQUMsQ0FBQyxNQUEwQixFQUFFLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsMEJBQTBCLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQzthQUNoRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO0tBQzVEO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsS0FBSyxVQUFVLE9BQU8sQ0FBQyxHQUFRLEVBQUUsYUFBcUI7SUFDcEQsTUFBTSxVQUFVLEdBQWdCLElBQUksR0FBRyxFQUFFLENBQUM7SUFDMUMsSUFBSTtRQUNGLE1BQU0sTUFBTSxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDO1lBQzNDLFVBQVUsRUFBRSxhQUFhO1lBQ3pCLFlBQVksRUFBRSxXQUFXO1NBQzFCLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1lBQzVCLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3BDLElBQUksUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLFNBQVMsSUFBSSxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssWUFBWSxFQUFFO2dCQUM3RCxVQUFVLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQzdCO1FBQ0gsQ0FBQyxDQUFDLENBQUM7S0FDSjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsOERBQThEO1FBQzlELDBEQUEwRDtRQUMxRCxJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssbUJBQW1CLEVBQUU7WUFDbEMsT0FBTyxJQUFJLEdBQUcsRUFBRSxDQUFDO1NBQ2xCO1FBQ0QsTUFBTSxDQUFDLENBQUM7S0FDVDtJQUNELE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLE1BQU0sQ0FBQyxNQUEwQixFQUFFLE1BQTBCO0lBQ3BFLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7U0FDdkIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztTQUM1QyxNQUFNLENBQUMsQ0FBQyxHQUF1QixFQUFFLElBQVksRUFBRSxFQUFFO1FBQ2hELEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekIsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQVMsT0FBTyxDQUFDLFNBQTZCLEVBQUUsU0FBNkI7SUFDM0UsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztTQUMxQixNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLElBQUksU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1NBQ25GLE1BQU0sQ0FBQyxDQUFDLEdBQWEsRUFBRSxJQUFZLEVBQUUsRUFBRTtRQUN0QyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2YsT0FBTyxHQUFHLENBQUM7SUFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDWCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyplc2xpbnQtZGlzYWJsZSBuby1jb25zb2xlKi9cbi8qIGVzbGludC1kaXNhYmxlIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llcyAqL1xuaW1wb3J0IHsgU1NNIH0gZnJvbSAnYXdzLXNkayc7XG5pbXBvcnQgeyBDcm9zc1JlZ2lvbkV4cG9ydHMsIEV4cG9ydFdyaXRlckNSUHJvcHMgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICBjb25zdCBleHBvcnRzID0gcHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTSh7IHJlZ2lvbjogcHJvcHMucmVnaW9uIH0pO1xuICB0cnkge1xuICAgIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICAgIGNvbnNvbGUuaW5mbyhgQ3JlYXRpbmcgbmV3IFNTTSBQYXJhbWV0ZXIgZXhwb3J0cyBpbiByZWdpb24gJHtwcm9wcy5yZWdpb259YCk7XG4gICAgICAgIGF3YWl0IHRocm93SWZBbnlJblVzZShzc20sIGV4cG9ydHMpO1xuICAgICAgICBhd2FpdCBwdXRQYXJhbWV0ZXJzKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGNhc2UgJ1VwZGF0ZSc6XG4gICAgICAgIGNvbnN0IG9sZFByb3BzOiBFeHBvcnRXcml0ZXJDUlByb3BzID0gZXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzLldyaXRlclByb3BzO1xuICAgICAgICBjb25zdCBvbGRFeHBvcnRzID0gb2xkUHJvcHMuZXhwb3J0cyBhcyBDcm9zc1JlZ2lvbkV4cG9ydHM7XG4gICAgICAgIGNvbnN0IG5ld0V4cG9ydHMgPSBleGNlcHQoZXhwb3J0cywgb2xkRXhwb3J0cyk7XG5cbiAgICAgICAgLy8gdGhyb3cgYW4gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudCBpZiBhbnkgZXhwb3J0IHZhbHVlIGlzIGNoYW5naW5nXG4gICAgICAgIGNvbnN0IGNoYW5nZWRFeHBvcnRzID0gY2hhbmdlZChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgaWYgKGNoYW5nZWRFeHBvcnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NvbWUgZXhwb3J0cyBoYXZlIGNoYW5nZWQhXFxuJysgY2hhbmdlZEV4cG9ydHMuam9pbignXFxuJykpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGlmIHdlIGFyZSByZW1vdmluZyBhbnkgZXhwb3J0cyB0aGF0IGFyZSBpbiB1c2UsIHRoZW4gdGhyb3cgYW5cbiAgICAgICAgLy8gZXJyb3IgdG8gZmFpbCB0aGUgZGVwbG95bWVudFxuICAgICAgICBjb25zdCByZW1vdmVkRXhwb3J0cyA9IGV4Y2VwdChvbGRFeHBvcnRzLCBleHBvcnRzKTtcbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgcmVtb3ZlZEV4cG9ydHMpO1xuICAgICAgICAvLyBpZiB0aGUgb25lcyB3ZSBhcmUgcmVtb3ZpbmcgYXJlIG5vdCBpbiB1c2UgdGhlbiBkZWxldGUgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKHJlbW92ZWRFeHBvcnRzKSxcbiAgICAgICAgfSkucHJvbWlzZSgpO1xuXG4gICAgICAgIC8vIGFsc28gdGhyb3cgYW4gZXJyb3IgaWYgd2UgYXJlIGNyZWF0aW5nIGEgbmV3IGV4cG9ydCB0aGF0IGFscmVhZHkgZXhpc3RzIGZvciBzb21lIHJlYXNvblxuICAgICAgICBhd2FpdCB0aHJvd0lmQW55SW5Vc2Uoc3NtLCBuZXdFeHBvcnRzKTtcbiAgICAgICAgY29uc29sZS5pbmZvKGBDcmVhdGluZyBuZXcgU1NNIFBhcmFtZXRlciBleHBvcnRzIGluIHJlZ2lvbiAke3Byb3BzLnJlZ2lvbn1gKTtcbiAgICAgICAgYXdhaXQgcHV0UGFyYW1ldGVycyhzc20sIG5ld0V4cG9ydHMpO1xuICAgICAgICByZXR1cm47XG4gICAgICBjYXNlICdEZWxldGUnOlxuICAgICAgICAvLyBpZiBhbnkgb2YgdGhlIGV4cG9ydHMgYXJlIGN1cnJlbnRseSBpbiB1c2UgdGhlbiB0aHJvdyBhbiBlcnJvciB0byBmYWlsXG4gICAgICAgIC8vIHRoZSBzdGFjayBkZWxldGlvbi5cbiAgICAgICAgYXdhaXQgdGhyb3dJZkFueUluVXNlKHNzbSwgZXhwb3J0cyk7XG4gICAgICAgIC8vIGlmIG5vbmUgYXJlIGluIHVzZSB0aGVuIGRlbGV0ZSBhbGwgb2YgdGhlbVxuICAgICAgICBhd2FpdCBzc20uZGVsZXRlUGFyYW1ldGVycyh7XG4gICAgICAgICAgTmFtZXM6IE9iamVjdC5rZXlzKGV4cG9ydHMpLFxuICAgICAgICB9KS5wcm9taXNlKCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIGV2ZW50OiAnLCBlKTtcbiAgICB0aHJvdyBlO1xuICB9XG59O1xuXG4vKipcbiAqIENyZWF0ZSBwYXJhbWV0ZXJzIGZvciBleGlzdGluZyBleHBvcnRzXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHB1dFBhcmFtZXRlcnMoc3NtOiBTU00sIHBhcmFtZXRlcnM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IFByb21pc2U8dm9pZD4ge1xuICBhd2FpdCBQcm9taXNlLmFsbChBcnJheS5mcm9tKE9iamVjdC5lbnRyaWVzKHBhcmFtZXRlcnMpLCAoW25hbWUsIHZhbHVlXSkgPT4ge1xuICAgIHJldHVybiBzc20ucHV0UGFyYW1ldGVyKHtcbiAgICAgIE5hbWU6IG5hbWUsXG4gICAgICBWYWx1ZTogdmFsdWUsXG4gICAgICBUeXBlOiAnU3RyaW5nJyxcbiAgICB9KS5wcm9taXNlKCk7XG4gIH0pKTtcbn1cblxuLyoqXG4gKiBRdWVyeSBmb3IgZXhpc3RpbmcgcGFyYW1ldGVycyB0aGF0IGFyZSBpbiB1c2VcbiAqL1xuYXN5bmMgZnVuY3Rpb24gdGhyb3dJZkFueUluVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJzOiBDcm9zc1JlZ2lvbkV4cG9ydHMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgdGFnUmVzdWx0czogTWFwPHN0cmluZywgU2V0PHN0cmluZz4+ID0gbmV3IE1hcCgpO1xuICBhd2FpdCBQcm9taXNlLmFsbChPYmplY3Qua2V5cyhwYXJhbWV0ZXJzKS5tYXAoYXN5bmMgKG5hbWU6IHN0cmluZykgPT4ge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGlzSW5Vc2Uoc3NtLCBuYW1lKTtcbiAgICBpZiAocmVzdWx0LnNpemUgPiAwKSB7XG4gICAgICB0YWdSZXN1bHRzLnNldChuYW1lLCByZXN1bHQpO1xuICAgIH1cbiAgfSkpO1xuXG4gIGlmICh0YWdSZXN1bHRzLnNpemUgPiAwKSB7XG4gICAgY29uc3QgbWVzc2FnZTogc3RyaW5nID0gT2JqZWN0LmVudHJpZXModGFnUmVzdWx0cylcbiAgICAgIC5tYXAoKHJlc3VsdDogW3N0cmluZywgc3RyaW5nW11dKSA9PiBgJHtyZXN1bHRbMF19IGlzIGluIHVzZSBieSBzdGFjayhzKSAke3Jlc3VsdFsxXS5qb2luKCcgJyl9YClcbiAgICAgIC5qb2luKCdcXG4nKTtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cG9ydHMgY2Fubm90IGJlIHVwZGF0ZWQ6IFxcbiR7bWVzc2FnZX1gKTtcbiAgfVxufVxuXG4vKipcbiAqIENoZWNrIGlmIGEgcGFyYW1ldGVyIGlzIGluIHVzZVxuICovXG5hc3luYyBmdW5jdGlvbiBpc0luVXNlKHNzbTogU1NNLCBwYXJhbWV0ZXJOYW1lOiBzdHJpbmcpOiBQcm9taXNlPFNldDxzdHJpbmc+PiB7XG4gIGNvbnN0IHRhZ1Jlc3VsdHM6IFNldDxzdHJpbmc+ID0gbmV3IFNldCgpO1xuICB0cnkge1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHNzbS5saXN0VGFnc0ZvclJlc291cmNlKHtcbiAgICAgIFJlc291cmNlSWQ6IHBhcmFtZXRlck5hbWUsXG4gICAgICBSZXNvdXJjZVR5cGU6ICdQYXJhbWV0ZXInLFxuICAgIH0pLnByb21pc2UoKTtcbiAgICByZXN1bHQuVGFnTGlzdD8uZm9yRWFjaCh0YWcgPT4ge1xuICAgICAgY29uc3QgdGFnUGFydHMgPSB0YWcuS2V5LnNwbGl0KCc6Jyk7XG4gICAgICBpZiAodGFnUGFydHNbMF0gPT09ICdhd3MtY2RrJyAmJiB0YWdQYXJ0c1sxXSA9PT0gJ3N0cm9uZy1yZWYnKSB7XG4gICAgICAgIHRhZ1Jlc3VsdHMuYWRkKHRhZ1BhcnRzWzJdKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIGFuIEludmFsaWRSZXNvdXJjZUlkIG1lYW5zIHRoYXQgdGhlIHBhcmFtZXRlciBkb2Vzbid0IGV4aXN0XG4gICAgLy8gd2hpY2ggd2Ugc2hvdWxkIGlnbm9yZSBzaW5jZSB0aGF0IG1lYW5zIGl0J3Mgbm90IGluIHVzZVxuICAgIGlmIChlLmNvZGUgPT09ICdJbnZhbGlkUmVzb3VyY2VJZCcpIHtcbiAgICAgIHJldHVybiBuZXcgU2V0KCk7XG4gICAgfVxuICAgIHRocm93IGU7XG4gIH1cbiAgcmV0dXJuIHRhZ1Jlc3VsdHM7XG59XG5cbi8qKlxuICogUmV0dXJuIG9ubHkgdGhlIGl0ZW1zIGZyb20gc291cmNlIHRoYXQgZG8gbm90IGV4aXN0IGluIHRoZSBmaWx0ZXJcbiAqXG4gKiBAcGFyYW0gc291cmNlIHRoZSBzb3VyY2Ugb2JqZWN0IHRvIHBlcmZvcm0gdGhlIGZpbHRlciBvblxuICogQHBhcmFtIGZpbHRlciBmaWx0ZXIgb3V0IGl0ZW1zIHRoYXQgZXhpc3QgaW4gdGhpcyBvYmplY3RcbiAqIEByZXR1cm5zIGFueSBleHBvcnRzIHRoYXQgZG9uJ3QgZXhpc3QgaW4gdGhlIGZpbHRlclxuICovXG5mdW5jdGlvbiBleGNlcHQoc291cmNlOiBDcm9zc1JlZ2lvbkV4cG9ydHMsIGZpbHRlcjogQ3Jvc3NSZWdpb25FeHBvcnRzKTogQ3Jvc3NSZWdpb25FeHBvcnRzIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKHNvdXJjZSlcbiAgICAuZmlsdGVyKGtleSA9PiAoIWZpbHRlci5oYXNPd25Qcm9wZXJ0eShrZXkpKSlcbiAgICAucmVkdWNlKChhY2M6IENyb3NzUmVnaW9uRXhwb3J0cywgY3Vycjogc3RyaW5nKSA9PiB7XG4gICAgICBhY2NbY3Vycl0gPSBzb3VyY2VbY3Vycl07XG4gICAgICByZXR1cm4gYWNjO1xuICAgIH0sIHt9KTtcbn1cblxuLyoqXG4gKiBSZXR1cm4gaXRlbXMgdGhhdCBleGlzdCBpbiBib3RoIHRoZSB0aGUgb2xkIHBhcmFtZXRlcnMgYW5kIHRoZSBuZXcgcGFyYW1ldGVycyxcbiAqIGJ1dCBoYXZlIGRpZmZlcmVudCB2YWx1ZXNcbiAqXG4gKiBAcGFyYW0gb2xkUGFyYW1zIHRoZSBleHBvcnRzIHRoYXQgZXhpc3RlZCBwcmV2aW91cyB0byB0aGlzIGV4ZWN1dGlvblxuICogQHBhcmFtIG5ld1BhcmFtcyB0aGUgZXhwb3J0cyBmb3IgdGhlIGN1cnJlbnQgZXhlY3V0aW9uXG4gKiBAcmV0dXJucyBhbnkgcGFyYW1ldGVycyB0aGF0IGhhdmUgZGlmZmVyZW50IHZhbHVlc1xuICovXG5mdW5jdGlvbiBjaGFuZ2VkKG9sZFBhcmFtczogQ3Jvc3NSZWdpb25FeHBvcnRzLCBuZXdQYXJhbXM6IENyb3NzUmVnaW9uRXhwb3J0cyk6IHN0cmluZ1tdIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKG9sZFBhcmFtcylcbiAgICAuZmlsdGVyKGtleSA9PiAobmV3UGFyYW1zLmhhc093blByb3BlcnR5KGtleSkgJiYgb2xkUGFyYW1zW2tleV0gIT09IG5ld1BhcmFtc1trZXldKSlcbiAgICAucmVkdWNlKChhY2M6IHN0cmluZ1tdLCBjdXJyOiBzdHJpbmcpID0+IHtcbiAgICAgIGFjYy5wdXNoKGN1cnIpO1xuICAgICAgcmV0dXJuIGFjYztcbiAgICB9LCBbXSk7XG59XG4iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets.json new file mode 100644 index 0000000000000..94406e02ec986 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "codepipelineintegtestDefaultTestDeployAssert88EAAC45.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/codepipelineintegtestDefaultTestDeployAssert88EAAC45.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.assets.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.assets.json new file mode 100644 index 0000000000000..8469a55817166 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.assets.json @@ -0,0 +1,48 @@ +{ + "version": "21.0.0", + "files": { + "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c": { + "source": { + "path": "asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + }, + "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741": { + "source": { + "path": "asset.4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + }, + "143ebf10248652fdf40e751a3fce16c5f63478c655577efc66530b02f4fb647a": { + "source": { + "path": "integ-pipeline-consumer-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-2": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2", + "objectKey": "143ebf10248652fdf40e751a3fce16c5f63478c655577efc66530b02f4fb647a.json", + "region": "us-east-2", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-2" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.template.json new file mode 100644 index 0000000000000..854af79e34bf1 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-consumer-stack.template.json @@ -0,0 +1,981 @@ +{ + "Resources": { + "PipelineArtifactsBucketEncryptionKey01D58D69": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-integ-pipeline-consumer-stack-pipeline-9f1db34e", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineArtifactsBucketPolicyD4F9712A": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelinesourceSourceCodePipelineActionRoleC03B7ECA", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "Pipelinestage2BuildCodePipelineActionRole6D7E5309", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "S3ObjectKey": "/somepath" + }, + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "Artifact_source_Source" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelinesourceSourceCodePipelineActionRoleC03B7ECA", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "Build45A36621" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_source_Source" + } + ], + "Name": "Build", + "RoleArn": { + "Fn::GetAtt": [ + "Pipelinestage2BuildCodePipelineActionRole6D7E5309", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "stage2" + } + ], + "ArtifactStores": [ + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1FnGetAttReplicationKeyFCE40BF4ArnFA0E5A73" + ] + }, + "Type": "KMS" + }, + "Location": { + "Fn::GetAtt": [ + "ExportsReader8B249524", + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1RefReplicationBucket70D68737DB32483D" + ] + }, + "Type": "S3" + }, + "Region": "us-east-1" + }, + { + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "Region": "us-east-2" + } + ] + }, + "DependsOn": [ + "PipelineRoleDefaultPolicyC7A05455", + "PipelineRoleD68726F7" + ] + }, + "PipelinesourceSourceCodePipelineActionRoleC03B7ECA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelinesourceSourceCodePipelineActionRoleDefaultPolicy6B296460": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "//somepath" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelinesourceSourceCodePipelineActionRoleDefaultPolicy6B296460", + "Roles": [ + { + "Ref": "PipelinesourceSourceCodePipelineActionRoleC03B7ECA" + } + ] + } + }, + "Pipelinestage2BuildCodePipelineActionRole6D7E5309": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Pipelinestage2BuildCodePipelineActionRoleDefaultPolicy4431A4F5": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Build45A36621", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Pipelinestage2BuildCodePipelineActionRoleDefaultPolicy4431A4F5", + "Roles": [ + { + "Ref": "Pipelinestage2BuildCodePipelineActionRole6D7E5309" + } + ] + } + }, + "SourceBucketDDD2130A": { + "Type": "AWS::S3::Bucket", + "Properties": { + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SourceBucketPolicy703DFBF9": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "SourceBucketDDD2130A" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "SourceBucketDDD2130A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "SourceBucketAutoDeleteObjectsCustomResourceC68FC040": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "SourceBucketDDD2130A" + } + }, + "DependsOn": [ + "SourceBucketPolicy703DFBF9" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2" + }, + "S3Key": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "SourceBucketDDD2130A" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, + "BuildRoleB7C66CB2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "BuildRoleDefaultPolicyEAC4E6D6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "Build45A36621" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "Build45A36621" + } + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:codebuild:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "Build45A36621" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BuildRoleDefaultPolicyEAC4E6D6", + "Roles": [ + { + "Ref": "BuildRoleB7C66CB2" + } + ] + } + }, + "Build45A36621": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "BuildRoleB7C66CB2", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE" + }, + "Cache": { + "Type": "NO_CACHE" + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + }, + "ExportsReader8B249524": { + "Type": "Custom::CrossRegionExportReader", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68", + "Arn" + ] + }, + "ReaderProps": { + "region": "us-east-2", + "prefix": "integ-pipeline-consumer-stack", + "imports": { + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1RefReplicationBucket70D68737DB32483D": "{{resolve:ssm:/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1RefReplicationBucket70D68737DB32483D}}", + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1FnGetAttReplicationKeyFCE40BF4ArnFA0E5A73": "{{resolve:ssm:/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1FnGetAttReplicationKeyFCE40BF4ArnFA0E5A73}}" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/cdk/exports/integ-pipeline-consumer-stack/*" + ] + ] + }, + "Action": [ + "ssm:AddTagsToResource", + "ssm:RemoveTagsFromResource", + "ssm:GetParameters" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2" + }, + "S3Key": "4607a5d8b4a4167cb2ff3c986ec884e3eb44c22626b7bce07ac9807730147741.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.assets.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.assets.json new file mode 100644 index 0000000000000..f3f2e22967613 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.assets.json @@ -0,0 +1,48 @@ +{ + "version": "21.0.0", + "files": { + "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c": { + "source": { + "path": "asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + }, + "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3": { + "source": { + "path": "asset.f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3", + "packaging": "zip" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + }, + "0eb69c88f002c044720b8030b26cd2250aa898c42cf4749d5f3f8c125876fa9a": { + "source": { + "path": "integ-pipeline-producer-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "0eb69c88f002c044720b8030b26cd2250aa898c42cf4749d5f3f8c125876fa9a.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.template.json new file mode 100644 index 0000000000000..09a244715f430 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ-pipeline-producer-stack.template.json @@ -0,0 +1,329 @@ +{ + "Resources": { + "ReplicationKeyFCE40BF4": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "ReplicationBucket70D68737": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "ReplicationKeyFCE40BF4", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ReplicationBucketPolicyADD8A584": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "ReplicationBucket70D68737" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "ReplicationBucket70D68737", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ReplicationBucket70D68737", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "ReplicationBucketAutoDeleteObjectsCustomResourceF7D32567": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "ReplicationBucket70D68737" + } + }, + "DependsOn": [ + "ReplicationBucketPolicyADD8A584" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1" + }, + "S3Key": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "ReplicationBucket70D68737" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, + "ExportsWriteruseast2828FA26B86FBEFA7": { + "Type": "Custom::CrossRegionExportWriter", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A", + "Arn" + ] + }, + "WriterProps": { + "region": "us-east-2", + "exports": { + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1RefReplicationBucket70D68737DB32483D": { + "Ref": "ReplicationBucket70D68737" + }, + "/cdk/exports/integ-pipeline-consumer-stack/integpipelineproducerstackuseast1FnGetAttReplicationKeyFCE40BF4ArnFA0E5A73": { + "Fn::GetAtt": [ + "ReplicationKeyFCE40BF4", + "Arn" + ] + } + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:us-east-2:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/cdk/exports/*" + ] + ] + }, + "Action": [ + "ssm:DeleteParameters", + "ssm:ListTagsForResource", + "ssm:GetParameters", + "ssm:PutParameter" + ] + } + ] + } + } + ] + } + }, + "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1" + }, + "S3Key": "f86f86755c3b2013542fc4f9405bfe145a86c0bec508dd0b37baabe2055d33f3.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1", + "Arn" + ] + }, + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ.json new file mode 100644 index 0000000000000..84aa7d89d58a9 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "21.0.0", + "testCases": { + "codepipeline-integ-test/DefaultTest": { + "stacks": [ + "integ-pipeline-consumer-stack" + ], + "stackUpdateWorkflow": false, + "assertionStack": "codepipeline-integ-test/DefaultTest/DeployAssert", + "assertionStackName": "codepipelineintegtestDefaultTestDeployAssert88EAAC45" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..c56c5756613f5 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-with-replication.integ.snapshot/manifest.json @@ -0,0 +1,333 @@ +{ + "version": "21.0.0", + "artifacts": { + "integ-pipeline-producer-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-pipeline-producer-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-pipeline-producer-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-1", + "properties": { + "templateFile": "integ-pipeline-producer-stack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1/0eb69c88f002c044720b8030b26cd2250aa898c42cf4749d5f3f8c125876fa9a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-pipeline-producer-stack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-pipeline-producer-stack.assets" + ], + "metadata": { + "/integ-pipeline-producer-stack/ReplicationKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ReplicationKeyFCE40BF4" + } + ], + "/integ-pipeline-producer-stack/ReplicationBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ReplicationBucket70D68737" + } + ], + "/integ-pipeline-producer-stack/ReplicationBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ReplicationBucketPolicyADD8A584" + } + ], + "/integ-pipeline-producer-stack/ReplicationBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ReplicationBucketAutoDeleteObjectsCustomResourceF7D32567" + } + ], + "/integ-pipeline-producer-stack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + } + ], + "/integ-pipeline-producer-stack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" + } + ], + "/integ-pipeline-producer-stack/ExportsWriteruseast2828FA26B/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsWriteruseast2828FA26B86FBEFA7" + } + ], + "/integ-pipeline-producer-stack/Custom::CrossRegionExportWriterCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1" + } + ], + "/integ-pipeline-producer-stack/Custom::CrossRegionExportWriterCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A" + } + ], + "/integ-pipeline-producer-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-pipeline-producer-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-pipeline-producer-stack" + }, + "integ-pipeline-consumer-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-pipeline-consumer-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-pipeline-consumer-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-2", + "properties": { + "templateFile": "integ-pipeline-consumer-stack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-2", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-2", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-2/143ebf10248652fdf40e751a3fce16c5f63478c655577efc66530b02f4fb647a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-pipeline-consumer-stack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-2", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-pipeline-producer-stack", + "integ-pipeline-consumer-stack.assets" + ], + "metadata": { + "/integ-pipeline-consumer-stack/Pipeline/ArtifactsBucketEncryptionKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketEncryptionKey01D58D69" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/ArtifactsBucketEncryptionKeyAlias/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucket22248F97" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketPolicyD4F9712A" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleD68726F7" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleDefaultPolicyC7A05455" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineC660917D" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/source/Source/CodePipelineActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelinesourceSourceCodePipelineActionRoleC03B7ECA" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/source/Source/CodePipelineActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelinesourceSourceCodePipelineActionRoleDefaultPolicy6B296460" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/stage2/Build/CodePipelineActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Pipelinestage2BuildCodePipelineActionRole6D7E5309" + } + ], + "/integ-pipeline-consumer-stack/Pipeline/stage2/Build/CodePipelineActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Pipelinestage2BuildCodePipelineActionRoleDefaultPolicy4431A4F5" + } + ], + "/integ-pipeline-consumer-stack/SourceBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketDDD2130A" + } + ], + "/integ-pipeline-consumer-stack/SourceBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketPolicy703DFBF9" + } + ], + "/integ-pipeline-consumer-stack/SourceBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "SourceBucketAutoDeleteObjectsCustomResourceC68FC040" + } + ], + "/integ-pipeline-consumer-stack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + } + ], + "/integ-pipeline-consumer-stack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" + } + ], + "/integ-pipeline-consumer-stack/Build/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildRoleB7C66CB2" + } + ], + "/integ-pipeline-consumer-stack/Build/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BuildRoleDefaultPolicyEAC4E6D6" + } + ], + "/integ-pipeline-consumer-stack/Build/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Build45A36621" + } + ], + "/integ-pipeline-consumer-stack/ExportsReader/Resource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsReader8B249524" + } + ], + "/integ-pipeline-consumer-stack/Custom::CrossRegionExportReaderCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD" + } + ], + "/integ-pipeline-consumer-stack/Custom::CrossRegionExportReaderCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68" + } + ], + "/integ-pipeline-consumer-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-pipeline-consumer-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-pipeline-consumer-stack" + }, + "codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "codepipelineintegtestDefaultTestDeployAssert88EAAC45": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "codepipelineintegtestDefaultTestDeployAssert88EAAC45.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "codepipelineintegtestDefaultTestDeployAssert88EAAC45.assets" + ], + "metadata": { + "/codepipeline-integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/codepipeline-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "codepipeline-integ-test/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index e8766a08b56cd..4ec510e5c6028 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -66,9 +66,15 @@ the `userPoolName` to give your own identifier to the user pool. If not, CloudFo ```ts new cognito.UserPool(this, 'myuserpool', { userPoolName: 'myawesomeapp-userpool', + signInCaseSensitive: false, // case insensitive is preferred in most situations }); ``` +By default, usernames and email addresses in user pools are case sensitive, which means `user@example.com` and `User@example.com` +are considered different. In most situations it is prefered to have usernames and email addresses be case insensitive so that +capitalization differences are ignored. As shown above, you can make a user pool case insensitive by setting `signInCaseSensitive` +to `false`. The case sensitivity cannot be changed once a user pool is created. + The default set up for the user pool is configured such that only administrators will be allowed to create users. Features such as Multi-factor authentication (MFAs) and Lambda Triggers are not configured by default. diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 43048983fd705..a689705ee0b14 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -273,6 +273,31 @@ new ec2.Vpc(stack, 'TheVPC', { With this method of IP address management, no attempt is made to guess at subnet group sizes or to exhaustively allocate the IP range. All subnet groups must have an explicit `cidrMask` set as part of their subnet configuration, or `defaultSubnetIpv4NetmaskLength` must be set for a default size. If not, synthesis will fail and you must provide one or the other. +### Reserving availability zones + +There are situations where the IP space for availability zones will +need to be reserved. This is useful in situations where availability +zones would need to be added after the vpc is originally deployed, +without causing IP renumbering for availability zones subnets. The IP +space for reserving `n` availability zones can be done by setting the +`reservedAzs` to `n` in vpc props, as shown below: + +```ts +const vpc = new ec2.Vpc(this, 'TheVPC', { + cidr: '10.0.0.0/21', + maxAzs: 3, + reservedAzs: 1, +}); +``` + +In the example above, the subnets for reserved availability zones is not +actually provisioned but its IP space is still reserved. If, in the future, +new availability zones needs to be provisioned, then we would decrement +the value of `reservedAzs` and increment the `maxAzs` or `availabilityZones` +accordingly. This action would not cause the IP address of subnets to get +renumbered, but rather the IP space that was previously reserved will be +used for the new availability zones subnets. + ### Advanced Subnet Configuration If the default VPC configuration (public and private subnets spanning the diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 719c1f9df4bdf..e2e4485fe43a1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -21,6 +21,7 @@ import { VpcLookupOptions } from './vpc-lookup'; import { EnableVpnGatewayOptions, VpnConnection, VpnConnectionOptions, VpnConnectionType, VpnGateway } from './vpn'; const VPC_SUBNET_SYMBOL = Symbol.for('@aws-cdk/aws-ec2.VpcSubnet'); +const FAKE_AZ_NAME = 'fake-az'; export interface ISubnet extends IResource { /** @@ -888,6 +889,16 @@ export interface VpcProps { */ readonly maxAzs?: number; + /** + * Define the number of AZs to reserve. + * + * When specified, the IP space is reserved for the azs but no actual + * resources are provisioned. + * + * @default 0 + */ + readonly reservedAzs?: number; + /** * Availability zones this VPC spans. * @@ -1396,6 +1407,9 @@ export class Vpc extends VpcBase { const maxAZs = props.maxAzs ?? 3; this.availabilityZones = stack.availabilityZones.slice(0, maxAZs); } + for (let i = 0; props.reservedAzs && i < props.reservedAzs; i++) { + this.availabilityZones.push(FAKE_AZ_NAME); + } this.vpcId = this.resource.ref; @@ -1561,6 +1575,10 @@ export class Vpc extends VpcBase { // For reserved subnets, do not create any resources return; } + if (availabilityZone === FAKE_AZ_NAME) { + // For reserved azs, do not create any resources + return; + } // mapPublicIpOnLaunch true in Subnet.Public, false in Subnet.Private or Subnet.Isolated. let mapPublicIpOnLaunch = false; diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-reserved-azs.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-reserved-azs.ts new file mode 100644 index 0000000000000..f434db8a52527 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-reserved-azs.ts @@ -0,0 +1,15 @@ +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integtest-vpc-reserved-azs'); + +new ec2.Vpc(stack, 'MyVpc', { + reservedAzs: 2, + maxAzs: 3, +}); + +new IntegTest(app, 'vpc-reserved-azs', { + testCases: [stack], +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integ.json new file mode 100644 index 0000000000000..401d95f9eade9 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "vpc-reserved-azs/DefaultTest": { + "stacks": [ + "integtest-vpc-reserved-azs" + ], + "assertionStack": "vpc-reserved-azs/DefaultTest/DeployAssert", + "assertionStackName": "vpcreservedazsDefaultTestDeployAssertE48D2C6D" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.assets.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.assets.json new file mode 100644 index 0000000000000..b1bf2c2192276 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "2316c1d0bd29529a3dc0b6ffcefa3aa88c8d79ea1b90aff8056d49f0de23e53b": { + "source": { + "path": "integtest-vpc-reserved-azs.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2316c1d0bd29529a3dc0b6ffcefa3aa88c8d79ea1b90aff8056d49f0de23e53b.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.template.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.template.json new file mode 100644 index 0000000000000..98924cc702938 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/integtest-vpc-reserved-azs.template.json @@ -0,0 +1,429 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "MyVpcPublicSubnet1DefaultRoute95FDF9EB", + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB" + ] + }, + "MyVpcPublicSubnet2Subnet492B6BFB": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.32.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTable1DF17386": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTableAssociation227DE78D": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "MyVpcPublicSubnet2DefaultRoute052936F6", + "MyVpcPublicSubnet2RouteTableAssociation227DE78D" + ] + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcPrivateSubnet2Subnet0040C983": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.160.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableAssociation86A610DA": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integtest-vpc-reserved-azs/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..711d45862b95b --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/manifest.json @@ -0,0 +1,243 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integtest-vpc-reserved-azs.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtest-vpc-reserved-azs.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtest-vpc-reserved-azs": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtest-vpc-reserved-azs.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2316c1d0bd29529a3dc0b6ffcefa3aa88c8d79ea1b90aff8056d49f0de23e53b.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integtest-vpc-reserved-azs.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integtest-vpc-reserved-azs.assets" + ], + "metadata": { + "/integtest-vpc-reserved-azs/MyVpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcF9F0CA6F" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1SubnetF6608456" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1RouteTableC46AB2F4" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1DefaultRoute95FDF9EB" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1EIP096967CB" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2Subnet492B6BFB" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2RouteTable1DF17386" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2RouteTableAssociation227DE78D" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2DefaultRoute052936F6" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2EIP8CCBA239" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1RouteTable8819E6E2" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet2Subnet0040C983" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet2RouteTableAssociation86A610DA" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet2DefaultRoute9CE96294" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcIGW5C4A4F63" + } + ], + "/integtest-vpc-reserved-azs/MyVpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcVPCGW488ACE0D" + } + ], + "/integtest-vpc-reserved-azs/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integtest-vpc-reserved-azs/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integtest-vpc-reserved-azs" + }, + "vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "vpcreservedazsDefaultTestDeployAssertE48D2C6D": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "vpcreservedazsDefaultTestDeployAssertE48D2C6D.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets" + ], + "metadata": { + "/vpc-reserved-azs/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/vpc-reserved-azs/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "vpc-reserved-azs/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/tree.json new file mode 100644 index 0000000000000..f72f174341639 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/tree.json @@ -0,0 +1,710 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "integtest-vpc-reserved-azs": { + "id": "integtest-vpc-reserved-azs", + "path": "integtest-vpc-reserved-azs", + "children": { + "MyVpc": { + "id": "MyVpc", + "path": "integtest-vpc-reserved-azs/MyVpc", + "children": { + "Resource": { + "id": "Resource", + "path": "integtest-vpc-reserved-azs/MyVpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/19", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "subnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "allocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.32.0/19", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "subnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "allocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/19", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "subnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.160.0/19", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "subnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "integtest-vpc-reserved-azs/MyVpc/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "integtest-vpc-reserved-azs/MyVpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "integtest-vpc-reserved-azs/MyVpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "integtest-vpc-reserved-azs/MyVpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "internetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "vpc-reserved-azs": { + "id": "vpc-reserved-azs", + "path": "vpc-reserved-azs", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "vpc-reserved-azs/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "vpc-reserved-azs/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "vpc-reserved-azs/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets.json new file mode 100644 index 0000000000000..79939c1deec2c --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "vpcreservedazsDefaultTestDeployAssertE48D2C6D.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.template.json b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-reserved-azs.integ.snapshot/vpcreservedazsDefaultTestDeployAssertE48D2C6D.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index e350b1a45b0c8..afe61c6e0a320 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -2086,6 +2086,35 @@ describe('vpc', () => { }); }); }); + + describe('Using reserved azs', () => { + test.each([ + [{ maxAzs: 2, reservedAzs: 1 }, { maxAzs: 3 }], + [{ maxAzs: 2, reservedAzs: 2 }, { maxAzs: 3, reservedAzs: 1 }], + [{ maxAzs: 2, reservedAzs: 1, subnetConfiguration: [{ cidrMask: 22, name: 'Public', subnetType: SubnetType.PUBLIC }, { cidrMask: 23, name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS }] }, + { maxAzs: 3, subnetConfiguration: [{ cidrMask: 22, name: 'Public', subnetType: SubnetType.PUBLIC }, { cidrMask: 23, name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS }] }], + [{ maxAzs: 2, reservedAzs: 1, subnetConfiguration: [{ cidrMask: 22, name: 'Public', subnetType: SubnetType.PUBLIC }, { cidrMask: 23, name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS, reserved: true }] }, + { maxAzs: 3, subnetConfiguration: [{ cidrMask: 22, name: 'Public', subnetType: SubnetType.PUBLIC }, { cidrMask: 23, name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS, reserved: true }] }], + [{ maxAzs: 2, reservedAzs: 1, ipAddresses: IpAddresses.cidr('192.168.0.0/16') }, { maxAzs: 3, ipAddresses: IpAddresses.cidr('192.168.0.0/16') }], + [{ availabilityZones: ['dummy1a', 'dummy1b'], reservedAzs: 1 }, { availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'] }], + ])('subnets should remain the same going from %p to %p', (propsWithReservedAz, propsWithUsedReservedAz) => { + const stackWithReservedAz = getTestStack(); + const stackWithUsedReservedAz = getTestStack(); + + new Vpc(stackWithReservedAz, 'Vpc', propsWithReservedAz); + new Vpc(stackWithUsedReservedAz, 'Vpc', propsWithUsedReservedAz); + + const templateWithReservedAz = Template.fromStack(stackWithReservedAz); + const templateWithUsedReservedAz = Template.fromStack(stackWithUsedReservedAz); + + const subnetsOfTemplateWithReservedAz = templateWithReservedAz.findResources('AWS::EC2::Subnet'); + const subnetsOfTemplateWithUsedReservedAz = templateWithUsedReservedAz.findResources('AWS::EC2::Subnet'); + for (const [logicalId, subnetOfTemplateWithReservedAz] of Object.entries(subnetsOfTemplateWithReservedAz)) { + const subnetOfTemplateWithUsedReservedAz = subnetsOfTemplateWithUsedReservedAz[logicalId]; + expect(subnetOfTemplateWithUsedReservedAz).toEqual(subnetOfTemplateWithReservedAz); + } + }); + }); }); function getTestStack(): Stack { diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index ee5777b42eecc..a95b2a6cf0803 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -34,6 +34,8 @@ const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value", }, + command: ['command'], + entryPoint: ['entry', 'point'], }, desiredCount: 2, }); @@ -49,6 +51,8 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat cpu: 512, taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + command: ['command'], + entryPoint: ['entry', 'point'], }, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 049dcbd4c00bf..207c101fe969b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -365,6 +365,32 @@ export interface ApplicationLoadBalancedTaskImageOptions { * @default - No labels. */ readonly dockerLabels?: { [key: string]: string }; + + /** + * The entry point that's passed to the container. + * + * This parameter maps to `Entrypoint` in the [Create a container](https://docs.docker.com/engine/api/v1.38/#operation/ContainerCreate) section + * of the [Docker Remote API](https://docs.docker.com/engine/api/v1.38/) and the `--entrypoint` option to + * [docker run](https://docs.docker.com/engine/reference/commandline/run/). + * + * For more information about the Docker `ENTRYPOINT` parameter, see https://docs.docker.com/engine/reference/builder/#entrypoint. + * + * @default none + */ + readonly entryPoint?: string[]; + + /** + * The command that's passed to the container. If there are multiple arguments, make sure that each argument is a separated string in the array. + * + * This parameter maps to `Cmd` in the [Create a container](https://docs.docker.com/engine/api/v1.38/#operation/ContainerCreate) section + * of the [Docker Remote API](https://docs.docker.com/engine/api/v1.38/) and the `COMMAND` parameter to + * [docker run](https://docs.docker.com/engine/reference/commandline/run/). + * + * For more information about the Docker `CMD` parameter, see https://docs.docker.com/engine/reference/builder/#cmd. + * + * @default none + */ + readonly command?: string[]; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 5ace0e212909d..0d35a6c8a2271 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -128,6 +128,8 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe secrets: taskImageOptions.secrets, logging: logDriver, dockerLabels: taskImageOptions.dockerLabels, + command: taskImageOptions.command, + entryPoint: taskImageOptions.entryPoint, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index b440f393a859b..d3f6bc7760c05 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -86,6 +86,8 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, dockerLabels: taskImageOptions.dockerLabels, + command: taskImageOptions.command, + entryPoint: taskImageOptions.entryPoint, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets.json new file mode 100644 index 0000000000000..b9f7e6a02ec63 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.assets.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.assets.json new file mode 100644 index 0000000000000..6a25741e874b0 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "5cad8ed71307fba8ab47a28a3d59925addf1d7a820bca9988c56a087e0476599": { + "source": { + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "5cad8ed71307fba8ab47a28a3d59925addf1d7a820bca9988c56a087e0476599.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.template.json new file mode 100644 index 0000000000000..d06f26010e3c4 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/aws-ecs-integ-alb-ec2-cmd-entrypoint.template.json @@ -0,0 +1,949 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677" + ] + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTableAssociationDD5762D8" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "Ec2ClusterEE43E89D": { + "Type": "AWS::ECS::Cluster" + }, + "Ec2Cluster56240A3A": { + "Type": "AWS::ECS::ClusterCapacityProviderAssociations", + "Properties": { + "CapacityProviders": [ + { + "Ref": "CapacityProvier480DE32F" + } + ], + "Cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "DefaultCapacityProviderStrategy": [] + } + }, + "AutoScalingGroupInstanceSecurityGroup9D2E0C5E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "AutoScalingGroupInstanceRoleDC70D128": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup" + } + ] + } + }, + "AutoScalingGroupInstanceRoleDefaultPolicy3DF09528": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:DeregisterContainerInstance", + "ecs:RegisterContainerInstance", + "ecs:Submit*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:Poll", + "ecs:StartTelemetrySession" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecs:DiscoverPollEndpoint", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AutoScalingGroupInstanceRoleDefaultPolicy3DF09528", + "Roles": [ + { + "Ref": "AutoScalingGroupInstanceRoleDC70D128" + } + ] + } + }, + "AutoScalingGroupInstanceProfile342FAC7C": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "AutoScalingGroupInstanceRoleDC70D128" + } + ] + } + }, + "AutoScalingGroupLaunchConfigDEEB160C": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "AutoScalingGroupInstanceProfile342FAC7C" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "AutoScalingGroupInstanceSecurityGroup9D2E0C5E", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "Ec2ClusterEE43E89D" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "AutoScalingGroupInstanceRoleDefaultPolicy3DF09528", + "AutoScalingGroupInstanceRoleDC70D128" + ] + }, + "AutoScalingGroupASG804C35BE": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "LaunchConfigurationName": { + "Ref": "AutoScalingGroupLaunchConfigDEEB160C" + }, + "NewInstancesProtectedFromScaleIn": true, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "CapacityProvier480DE32F": { + "Type": "AWS::ECS::CapacityProvider", + "Properties": { + "AutoScalingGroupProvider": { + "AutoScalingGroupArn": { + "Ref": "AutoScalingGroupASG804C35BE" + }, + "ManagedScaling": { + "Status": "ENABLED", + "TargetCapacity": 100 + }, + "ManagedTerminationProtection": "ENABLED" + }, + "Name": "test-capacity-provider" + } + }, + "ALBECSServiceWithCommandEntryPointLB2FFE407F": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointLBSecurityGroupBA7F6FB5", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "Type": "application" + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTableAssociationDD5762D8" + ] + }, + "ALBECSServiceWithCommandEntryPointLBSecurityGroupBA7F6FB5": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awsecsintegalbec2cmdentrypointALBECSServiceWithCommandEntryPointLB36B044C3", + "SecurityGroupEgress": [ + { + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ALBECSServiceWithCommandEntryPointLBPublicListener1DCF0F84": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLB2FFE407F" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], + "TargetType": "instance", + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ALBECSServiceWithCommandEntryPointTaskDefTaskRoleD0EE621C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ALBECSServiceWithCommandEntryPointTaskDef63271EC5": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Command": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "Cpu": 256, + "EntryPoint": [ + "/bin/bash", + "-l", + "-c" + ], + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D" + }, + "awslogs-stream-prefix": "ALBECSServiceWithCommandEntryPoint", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 512, + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "HostPort": 0, + "Protocol": "tcp" + } + ] + } + ], + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196", + "Arn" + ] + }, + "Family": "awsecsintegalbec2cmdentrypointALBECSServiceWithCommandEntryPointTaskDef8556E5D8", + "NetworkMode": "bridge", + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefTaskRoleD0EE621C", + "Arn" + ] + } + } + }, + "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleDefaultPolicyD8110F3F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleDefaultPolicyD8110F3F", + "Roles": [ + { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196" + } + ] + } + }, + "ALBECSServiceWithCommandEntryPointService6335A932": { + "Type": "AWS::ECS::Service", + "Properties": { + "CapacityProviderStrategy": [ + { + "Base": 1, + "CapacityProvider": { + "Ref": "CapacityProvier480DE32F" + }, + "Weight": 1 + } + ], + "Cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LoadBalancers": [ + { + "ContainerName": "web", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D" + } + } + ], + "SchedulingStrategy": "REPLICA", + "TaskDefinition": { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDef63271EC5" + } + }, + "DependsOn": [ + "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D", + "ALBECSServiceWithCommandEntryPointLBPublicListener1DCF0F84" + ] + } + }, + "Parameters": { + "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" + }, + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Outputs": { + "ALBECSServiceWithCommandEntryPointLoadBalancerDNS4794C277": { + "Value": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointLB2FFE407F", + "DNSName" + ] + } + }, + "ALBECSServiceWithCommandEntryPointServiceURLA5FAF4D9": { + "Value": { + "Fn::Join": [ + "", + [ + "http://", + { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointLB2FFE407F", + "DNSName" + ] + } + ] + ] + } + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/integ.json new file mode 100644 index 0000000000000..f08dde867f553 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest": { + "stacks": [ + "aws-ecs-integ-alb-ec2-cmd-entrypoint" + ], + "assertionStack": "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert", + "assertionStackName": "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..092f2b0f5cf0b --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/manifest.json @@ -0,0 +1,375 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-ecs-integ-alb-ec2-cmd-entrypoint.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-ecs-integ-alb-ec2-cmd-entrypoint.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-ecs-integ-alb-ec2-cmd-entrypoint": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-ecs-integ-alb-ec2-cmd-entrypoint.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/5cad8ed71307fba8ab47a28a3d59925addf1d7a820bca9988c56a087e0476599.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-ecs-integ-alb-ec2-cmd-entrypoint.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-ecs-integ-alb-ec2-cmd-entrypoint.assets" + ], + "metadata": { + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpc8378EB38" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTable6C95E38E" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTableAssociation97140677" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute3DA9E72A" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1EIPD7E02669" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1NATGateway4D7517AA" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTable94F7E489" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTableAssociationDD5762D8" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2DefaultRoute97F91067" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2EIP3C605A87" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2NATGateway9182C01D" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1Subnet536B997A" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableB2C5B500" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableAssociation70C59FA6" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1DefaultRouteBE02A9ED" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2Subnet3788AAA1" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableA678073B" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableAssociationA89CAD56" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2DefaultRoute060D2087" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcIGWD7BA715C" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcVPCGWBF912B6E" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Ec2Cluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Ec2ClusterEE43E89D" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/Ec2Cluster/Ec2Cluster": [ + { + "type": "aws:cdk:logicalId", + "data": "Ec2Cluster56240A3A" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceSecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupInstanceSecurityGroup9D2E0C5E" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupInstanceRoleDC70D128" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupInstanceRoleDefaultPolicy3DF09528" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceProfile": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupInstanceProfile342FAC7C" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/LaunchConfig": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupLaunchConfigDEEB160C" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/ASG": [ + { + "type": "aws:cdk:logicalId", + "data": "AutoScalingGroupASG804C35BE" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": [ + { + "type": "aws:cdk:logicalId", + "data": "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/CapacityProvier/CapacityProvier": [ + { + "type": "aws:cdk:logicalId", + "data": "CapacityProvier480DE32F" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointLB2FFE407F" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointLBSecurityGroupBA7F6FB5" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointLBPublicListener1DCF0F84" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener/ECSGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LoadBalancerDNS": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointLoadBalancerDNS4794C277" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/ServiceURL": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointServiceURLA5FAF4D9" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointTaskDefTaskRoleD0EE621C" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointTaskDef63271EC5" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/web/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleDefaultPolicyD8110F3F" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/Service/Service": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBECSServiceWithCommandEntryPointService6335A932" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-integ-alb-ec2-cmd-entrypoint/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-integ-alb-ec2-cmd-entrypoint" + }, + "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "AlbEc2ServiceWithCommandAndEntryPointDefaultTestDeployAssert91EF33D6.assets" + ], + "metadata": { + "/AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/tree.json new file mode 100644 index 0000000000000..8a0cb8cb75ff0 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/alb-ecs-service-command-entry-point.integ.snapshot/tree.json @@ -0,0 +1,1545 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.133" + } + }, + "aws-ecs-integ-alb-ec2-cmd-entrypoint": { + "id": "aws-ecs-integ-alb-ec2-cmd-entrypoint", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint", + "children": { + "Vpc": { + "id": "Vpc", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Vpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "internetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + }, + "Ec2Cluster": { + "id": "Ec2Cluster", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Ec2Cluster", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Ec2Cluster/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Cluster", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnCluster", + "version": "0.0.0" + } + }, + "Ec2Cluster": { + "id": "Ec2Cluster", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/Ec2Cluster/Ec2Cluster", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::ClusterCapacityProviderAssociations", + "aws:cdk:cloudformation:props": { + "capacityProviders": [ + { + "Ref": "CapacityProvier480DE32F" + } + ], + "cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "defaultCapacityProviderStrategy": [] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnClusterCapacityProviderAssociations", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.Cluster", + "version": "0.0.0" + } + }, + "AutoScalingGroup": { + "id": "AutoScalingGroup", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup", + "children": { + "InstanceSecurityGroup": { + "id": "InstanceSecurityGroup", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceSecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceSecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceSecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "InstanceRole": { + "id": "InstanceRole", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "ecs:DeregisterContainerInstance", + "ecs:RegisterContainerInstance", + "ecs:Submit*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:Poll", + "ecs:StartTelemetrySession" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecs:DiscoverPollEndpoint", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "policyName": "AutoScalingGroupInstanceRoleDefaultPolicy3DF09528", + "roles": [ + { + "Ref": "AutoScalingGroupInstanceRoleDC70D128" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "InstanceProfile": { + "id": "InstanceProfile", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/InstanceProfile", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::InstanceProfile", + "aws:cdk:cloudformation:props": { + "roles": [ + { + "Ref": "AutoScalingGroupInstanceRoleDC70D128" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnInstanceProfile", + "version": "0.0.0" + } + }, + "LaunchConfig": { + "id": "LaunchConfig", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/LaunchConfig", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::LaunchConfiguration", + "aws:cdk:cloudformation:props": { + "imageId": { + "Ref": "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "instanceType": "t2.micro", + "iamInstanceProfile": { + "Ref": "AutoScalingGroupInstanceProfile342FAC7C" + }, + "securityGroups": [ + { + "Fn::GetAtt": [ + "AutoScalingGroupInstanceSecurityGroup9D2E0C5E", + "GroupId" + ] + } + ], + "userData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "Ec2ClusterEE43E89D" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnLaunchConfiguration", + "version": "0.0.0" + } + }, + "ASG": { + "id": "ASG", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup/ASG", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::AutoScalingGroup", + "aws:cdk:cloudformation:props": { + "maxSize": "1", + "minSize": "1", + "launchConfigurationName": { + "Ref": "AutoScalingGroupLaunchConfigDEEB160C" + }, + "newInstancesProtectedFromScaleIn": true, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-alb-ec2-cmd-entrypoint/AutoScalingGroup", + "propagateAtLaunch": true + } + ], + "vpcZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnAutoScalingGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.AutoScalingGroup", + "version": "0.0.0" + } + }, + "SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": { + "id": "SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118": { + "id": "SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/SsmParameterValue:--aws--service--ecs--optimized-ami--amazon-linux-2--recommended--image_id:C96584B6-F00A-464E-AD19-53AFF4B05118", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "CapacityProvier": { + "id": "CapacityProvier", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/CapacityProvier", + "children": { + "CapacityProvier": { + "id": "CapacityProvier", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/CapacityProvier/CapacityProvier", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::CapacityProvider", + "aws:cdk:cloudformation:props": { + "autoScalingGroupProvider": { + "autoScalingGroupArn": { + "Ref": "AutoScalingGroupASG804C35BE" + }, + "managedScaling": { + "status": "ENABLED", + "targetCapacity": 100 + }, + "managedTerminationProtection": "ENABLED" + }, + "name": "test-capacity-provider" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnCapacityProvider", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.AsgCapacityProvider", + "version": "0.0.0" + } + }, + "ALBECSServiceWithCommandEntryPoint": { + "id": "ALBECSServiceWithCommandEntryPoint", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint", + "children": { + "LB": { + "id": "LB", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "aws:cdk:cloudformation:props": { + "loadBalancerAttributes": [ + { + "key": "deletion_protection.enabled", + "value": "false" + } + ], + "scheme": "internet-facing", + "securityGroups": [ + { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointLBSecurityGroupBA7F6FB5", + "GroupId" + ] + } + ], + "subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "type": "application" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnLoadBalancer", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "Automatically created Security Group for ELB awsecsintegalbec2cmdentrypointALBECSServiceWithCommandEntryPointLB36B044C3", + "securityGroupEgress": [ + { + "cidrIp": "255.255.255.255/32", + "description": "Disallow all traffic", + "ipProtocol": "icmp", + "fromPort": 252, + "toPort": 86 + } + ], + "securityGroupIngress": [ + { + "cidrIp": "0.0.0.0/0", + "ipProtocol": "tcp", + "fromPort": 80, + "toPort": 80, + "description": "Allow from anyone on port 80" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "PublicListener": { + "id": "PublicListener", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::Listener", + "aws:cdk:cloudformation:props": { + "defaultActions": [ + { + "type": "forward", + "targetGroupArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D" + } + } + ], + "loadBalancerArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLB2FFE407F" + }, + "port": 80, + "protocol": "HTTP" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnListener", + "version": "0.0.0" + } + }, + "ECSGroup": { + "id": "ECSGroup", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener/ECSGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LB/PublicListener/ECSGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "aws:cdk:cloudformation:props": { + "port": 80, + "protocol": "HTTP", + "targetGroupAttributes": [ + { + "key": "stickiness.enabled", + "value": "false" + } + ], + "targetType": "instance", + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationLoadBalancer", + "version": "0.0.0" + } + }, + "LoadBalancerDNS": { + "id": "LoadBalancerDNS", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/LoadBalancerDNS", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "ServiceURL": { + "id": "ServiceURL", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/ServiceURL", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TaskDef": { + "id": "TaskDef", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/TaskRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "command": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "cpu": 256, + "entryPoint": [ + "/bin/bash", + "-l", + "-c" + ], + "essential": true, + "image": "amazon/amazon-ecs-sample", + "memory": 512, + "name": "web", + "portMappings": [ + { + "containerPort": 80, + "hostPort": 0, + "protocol": "tcp" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D" + }, + "awslogs-stream-prefix": "ALBECSServiceWithCommandEntryPoint", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + } + } + ], + "executionRoleArn": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196", + "Arn" + ] + }, + "family": "awsecsintegalbec2cmdentrypointALBECSServiceWithCommandEntryPointTaskDef8556E5D8", + "networkMode": "bridge", + "requiresCompatibilities": [ + "EC2" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefTaskRoleD0EE621C", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "web": { + "id": "web", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/web", + "children": { + "LogGroup": { + "id": "LogGroup", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/web/LogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/web/LogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.LogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", + "version": "0.0.0" + } + }, + "ExecutionRole": { + "id": "ExecutionRole", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/TaskDef/ExecutionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ALBECSServiceWithCommandEntryPointTaskDefwebLogGroup9AC53F6D", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleDefaultPolicyD8110F3F", + "roles": [ + { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDefExecutionRoleEF46B196" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.Ec2TaskDefinition", + "version": "0.0.0" + } + }, + "Service": { + "id": "Service", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/Service", + "children": { + "Service": { + "id": "Service", + "path": "aws-ecs-integ-alb-ec2-cmd-entrypoint/ALBECSServiceWithCommandEntryPoint/Service/Service", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Service", + "aws:cdk:cloudformation:props": { + "capacityProviderStrategy": [ + { + "capacityProvider": { + "Ref": "CapacityProvier480DE32F" + }, + "base": 1, + "weight": 1 + } + ], + "cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "deploymentConfiguration": { + "maximumPercent": 200, + "minimumHealthyPercent": 50 + }, + "enableEcsManagedTags": false, + "healthCheckGracePeriodSeconds": 60, + "loadBalancers": [ + { + "targetGroupArn": { + "Ref": "ALBECSServiceWithCommandEntryPointLBPublicListenerECSGroup7271102D" + }, + "containerName": "web", + "containerPort": 80 + } + ], + "schedulingStrategy": "REPLICA", + "taskDefinition": { + "Ref": "ALBECSServiceWithCommandEntryPointTaskDef63271EC5" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnService", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.Ec2Service", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs-patterns.ApplicationLoadBalancedEc2Service", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "AlbEc2ServiceWithCommandAndEntryPoint": { + "id": "AlbEc2ServiceWithCommandAndEntryPoint", + "path": "AlbEc2ServiceWithCommandAndEntryPoint", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.133" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "AlbEc2ServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.alb-ecs-service-command-entry-point.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.alb-ecs-service-command-entry-point.ts new file mode 100644 index 0000000000000..a1037350a62f9 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.alb-ecs-service-command-entry-point.ts @@ -0,0 +1,55 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as ecsPatterns from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-alb-ec2-cmd-entrypoint'); + +// Create VPC and ECS Cluster +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); +const cluster = new ecs.Cluster(stack, 'Ec2Cluster', { vpc }); +const provider = new ecs.AsgCapacityProvider(stack, 'CapacityProvier', { + autoScalingGroup: new autoscaling.AutoScalingGroup( + stack, + 'AutoScalingGroup', + { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), + }, + ), + capacityProviderName: 'test-capacity-provider', +}); +cluster.addAsgCapacityProvider(provider); + +// Create ALB service with Command and EntryPoint +new ecsPatterns.ApplicationLoadBalancedEc2Service( + stack, + 'ALBECSServiceWithCommandEntryPoint', + { + cluster, + memoryLimitMiB: 512, + cpu: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + command: ['/usr/sbin/apache2', '-D', 'FOREGROUND'], + entryPoint: ['/bin/bash', '-l', '-c'], + }, + capacityProviderStrategies: [ + { + capacityProvider: provider.capacityProviderName, + base: 1, + weight: 1, + }, + ], + }, +); + +new integ.IntegTest(app, 'AlbEc2ServiceWithCommandAndEntryPoint', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts index 6dbdcf417509d..2170ecf57e6fe 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts @@ -36,6 +36,8 @@ test('test ECS loadbalanced construct', () => { TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', }, dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + entryPoint: ['echo', 'ecs-is-awesome'], + command: ['/bin/bash'], }, desiredCount: 2, }); @@ -66,6 +68,8 @@ test('test ECS loadbalanced construct', () => { label1: 'labelValue1', label2: 'labelValue2', }, + EntryPoint: ['echo', 'ecs-is-awesome'], + Command: ['/bin/bash'], }), ], }); @@ -405,6 +409,8 @@ test('test Fargate loadbalanced construct', () => { TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', }, dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + entryPoint: ['echo', 'running-on-fargate'], + command: ['/bin/bash'], }, desiredCount: 2, }); @@ -436,6 +442,8 @@ test('test Fargate loadbalanced construct', () => { label1: 'labelValue1', label2: 'labelValue2', }, + EntryPoint: ['echo', 'running-on-fargate'], + Command: ['/bin/bash'], }), ], }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets.json new file mode 100644 index 0000000000000..1c086f5ace6ec --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets.json new file mode 100644 index 0000000000000..4837a381d8e40 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "5320a4b1cf45da124dd243ae544c2028dd0a11b8ab55996fcdc72ad27d3d4912": { + "source": { + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "5320a4b1cf45da124dd243ae544c2028dd0a11b8ab55996fcdc72ad27d3d4912.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.template.json new file mode 100644 index 0000000000000..d3edf8de7c14d --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/aws-ecs-integ-lb-fargate-cmd-entrypoint-test.template.json @@ -0,0 +1,781 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677" + ] + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + }, + "DependsOn": [ + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTableAssociationDD5762D8" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "TestFargateCluster0BF869F3": { + "Type": "AWS::ECS::Cluster" + }, + "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "Type": "application" + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTableAssociationDD5762D8" + ] + }, + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLB690B472C", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGrouptoawsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11801B449FAA": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "ALBFargateServiceWithCommandAndEntryPointLBPublicListener6589DC80": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], + "TargetType": "ip", + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointTaskDefTaskRole65CE9392": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointTaskDefCD2C6236": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Command": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "EntryPoint": [ + "/bin/bash" + ], + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925" + }, + "awslogs-stream-prefix": "ALBFargateServiceWithCommandAndEntryPoint", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E", + "Arn" + ] + }, + "Family": "awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointTaskDef2781E3BC", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefTaskRole65CE9392", + "Arn" + ] + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleDefaultPolicy9B61B60B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleDefaultPolicy9B61B60B", + "Roles": [ + { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E" + } + ] + } + }, + "ALBFargateServiceWithCommandAndEntryPointService1FDFD0BC": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "TestFargateCluster0BF869F3" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "web", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "TaskDefinition": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefCD2C6236" + } + }, + "DependsOn": [ + "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305", + "ALBFargateServiceWithCommandAndEntryPointLBPublicListener6589DC80" + ] + }, + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupfromawsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E70918046DDBFE6": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + }, + "ToPort": 80 + } + } + }, + "Outputs": { + "ALBFargateServiceWithCommandAndEntryPointLoadBalancerDNSE2B256CD": { + "Value": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA", + "DNSName" + ] + } + }, + "ALBFargateServiceWithCommandAndEntryPointServiceURLACF72094": { + "Value": { + "Fn::Join": [ + "", + [ + "http://", + { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA", + "DNSName" + ] + } + ] + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/integ.json new file mode 100644 index 0000000000000..8b2806af4743a --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest": { + "stacks": [ + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test" + ], + "assertionStack": "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert", + "assertionStackName": "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..f32d76fa6ad78 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/manifest.json @@ -0,0 +1,339 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/5320a4b1cf45da124dd243ae544c2028dd0a11b8ab55996fcdc72ad27d3d4912.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test.assets" + ], + "metadata": { + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpc8378EB38" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTable6C95E38E" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTableAssociation97140677" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute3DA9E72A" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1EIPD7E02669" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1NATGateway4D7517AA" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTable94F7E489" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTableAssociationDD5762D8" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2DefaultRoute97F91067" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2EIP3C605A87" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2NATGateway9182C01D" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1Subnet536B997A" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableB2C5B500" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1RouteTableAssociation70C59FA6" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet1DefaultRouteBE02A9ED" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2Subnet3788AAA1" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableA678073B" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2RouteTableAssociationA89CAD56" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPrivateSubnet2DefaultRoute060D2087" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcIGWD7BA715C" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcVPCGWBF912B6E" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/TestFargateCluster/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestFargateCluster0BF869F3" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/SecurityGroup/to awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11:80": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLBSecurityGrouptoawsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11801B449FAA" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLBPublicListener6589DC80" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener/ECSGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LoadBalancerDNS": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointLoadBalancerDNSE2B256CD" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/ServiceURL": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointServiceURLACF72094" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointTaskDefTaskRole65CE9392" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointTaskDefCD2C6236" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/web/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleDefaultPolicy9B61B60B" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/Service": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointService1FDFD0BC" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup/from awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E7091:80": [ + { + "type": "aws:cdk:logicalId", + "data": "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupfromawsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E70918046DDBFE6" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-integ-lb-fargate-cmd-entrypoint-test/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test" + }, + "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "AlbFargateServiceWithCommandAndEntryPointDefaultTestDeployAssert84DAACDF.assets" + ], + "metadata": { + "/AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/tree.json new file mode 100644 index 0000000000000..cff5487edde66 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-command-entry-point.integ.snapshot/tree.json @@ -0,0 +1,1294 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.133" + } + }, + "aws-ecs-integ-lb-fargate-cmd-entrypoint-test": { + "id": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test", + "children": { + "Vpc": { + "id": "Vpc", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "allocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "subnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/Vpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + }, + "internetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + }, + "TestFargateCluster": { + "id": "TestFargateCluster", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/TestFargateCluster", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/TestFargateCluster/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Cluster", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnCluster", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.Cluster", + "version": "0.0.0" + } + }, + "ALBFargateServiceWithCommandAndEntryPoint": { + "id": "ALBFargateServiceWithCommandAndEntryPoint", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint", + "children": { + "LB": { + "id": "LB", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "aws:cdk:cloudformation:props": { + "loadBalancerAttributes": [ + { + "key": "deletion_protection.enabled", + "value": "false" + } + ], + "scheme": "internet-facing", + "securityGroups": [ + { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + } + ], + "subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "type": "application" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnLoadBalancer", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "Automatically created Security Group for ELB awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLB690B472C", + "securityGroupIngress": [ + { + "cidrIp": "0.0.0.0/0", + "ipProtocol": "tcp", + "fromPort": 80, + "toPort": 80, + "description": "Allow from anyone on port 80" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + }, + "to awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11:80": { + "id": "to awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11:80", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/SecurityGroup/to awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointServiceSecurityGroup6D1E5F11:80", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroupEgress", + "aws:cdk:cloudformation:props": { + "groupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + }, + "ipProtocol": "tcp", + "description": "Load balancer to target", + "destinationSecurityGroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + }, + "fromPort": 80, + "toPort": 80 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroupEgress", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "PublicListener": { + "id": "PublicListener", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::Listener", + "aws:cdk:cloudformation:props": { + "defaultActions": [ + { + "type": "forward", + "targetGroupArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305" + } + } + ], + "loadBalancerArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLB353EA7CA" + }, + "port": 80, + "protocol": "HTTP" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnListener", + "version": "0.0.0" + } + }, + "ECSGroup": { + "id": "ECSGroup", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener/ECSGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LB/PublicListener/ECSGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "aws:cdk:cloudformation:props": { + "port": 80, + "protocol": "HTTP", + "targetGroupAttributes": [ + { + "key": "stickiness.enabled", + "value": "false" + } + ], + "targetType": "ip", + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.CfnTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationTargetGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-elasticloadbalancingv2.ApplicationLoadBalancer", + "version": "0.0.0" + } + }, + "LoadBalancerDNS": { + "id": "LoadBalancerDNS", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/LoadBalancerDNS", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "ServiceURL": { + "id": "ServiceURL", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/ServiceURL", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "TaskDef": { + "id": "TaskDef", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/TaskRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "command": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "entryPoint": [ + "/bin/bash" + ], + "essential": true, + "image": "amazon/amazon-ecs-sample", + "name": "web", + "portMappings": [ + { + "containerPort": 80, + "protocol": "tcp" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925" + }, + "awslogs-stream-prefix": "ALBFargateServiceWithCommandAndEntryPoint", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + } + } + ], + "cpu": "256", + "executionRoleArn": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E", + "Arn" + ] + }, + "family": "awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointTaskDef2781E3BC", + "memory": "512", + "networkMode": "awsvpc", + "requiresCompatibilities": [ + "FARGATE" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefTaskRole65CE9392", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "web": { + "id": "web", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/web", + "children": { + "LogGroup": { + "id": "LogGroup", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/web/LogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/web/LogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.LogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", + "version": "0.0.0" + } + }, + "ExecutionRole": { + "id": "ExecutionRole", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/TaskDef/ExecutionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointTaskDefwebLogGroup82F04925", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleDefaultPolicy9B61B60B", + "roles": [ + { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefExecutionRoleB204372E" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.FargateTaskDefinition", + "version": "0.0.0" + } + }, + "Service": { + "id": "Service", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service", + "children": { + "Service": { + "id": "Service", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/Service", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::Service", + "aws:cdk:cloudformation:props": { + "cluster": { + "Ref": "TestFargateCluster0BF869F3" + }, + "deploymentConfiguration": { + "maximumPercent": 200, + "minimumHealthyPercent": 50 + }, + "enableEcsManagedTags": false, + "healthCheckGracePeriodSeconds": 60, + "launchType": "FARGATE", + "loadBalancers": [ + { + "targetGroupArn": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointLBPublicListenerECSGroupBAD40305" + }, + "containerName": "web", + "containerPort": 80 + } + ], + "networkConfiguration": { + "awsvpcConfiguration": { + "assignPublicIp": "DISABLED", + "subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ], + "securityGroups": [ + { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + } + ] + } + }, + "taskDefinition": { + "Ref": "ALBFargateServiceWithCommandAndEntryPointTaskDefCD2C6236" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.CfnService", + "version": "0.0.0" + } + }, + "SecurityGroup": { + "id": "SecurityGroup", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + }, + "from awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E7091:80": { + "id": "from awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E7091:80", + "path": "aws-ecs-integ-lb-fargate-cmd-entrypoint-test/ALBFargateServiceWithCommandAndEntryPoint/Service/SecurityGroup/from awsecsinteglbfargatecmdentrypointtestALBFargateServiceWithCommandAndEntryPointLBSecurityGroup886E7091:80", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroupIngress", + "aws:cdk:cloudformation:props": { + "ipProtocol": "tcp", + "description": "Load balancer to target", + "fromPort": 80, + "groupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointServiceSecurityGroupD154E880", + "GroupId" + ] + }, + "sourceSecurityGroupId": { + "Fn::GetAtt": [ + "ALBFargateServiceWithCommandAndEntryPointLBSecurityGroupD7099797", + "GroupId" + ] + }, + "toPort": 80 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroupIngress", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.FargateService", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs-patterns.ApplicationLoadBalancedFargateService", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "AlbFargateServiceWithCommandAndEntryPoint": { + "id": "AlbFargateServiceWithCommandAndEntryPoint", + "path": "AlbFargateServiceWithCommandAndEntryPoint", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.133" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "AlbFargateServiceWithCommandAndEntryPoint/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-command-entry-point.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-command-entry-point.ts new file mode 100644 index 0000000000000..7c42234ead53f --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-command-entry-point.ts @@ -0,0 +1,37 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as ecsPatterns from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack( + app, + 'aws-ecs-integ-lb-fargate-cmd-entrypoint-test', +); + +// Create VPC and cluster +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); +const cluster = new ecs.Cluster(stack, 'TestFargateCluster', { vpc }); + +// Create ALB service with Command and EntryPoint +new ecsPatterns.ApplicationLoadBalancedFargateService( + stack, + 'ALBFargateServiceWithCommandAndEntryPoint', + { + cluster, + memoryLimitMiB: 512, + cpu: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + command: ['/usr/sbin/apache2', '-D', 'FOREGROUND'], + entryPoint: ['/bin/bash'], + }, + }, +); + +new integ.IntegTest(app, 'AlbFargateServiceWithCommandAndEntryPoint', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 9eb367248779a..57c2fc989e101 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -409,6 +409,50 @@ test('setting ALB deployment controller', () => { }); }); +test('setting a command for taskImageOptions in an ApplicationLoadBalancedFargateService works', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + command: ['./app/bin/start.sh', '--foo'], + }, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + Command: ['./app/bin/start.sh', '--foo'], + }), + ], + }); +}); + +test('setting an entryPoint for taskImageOptions in an ApplicationLoadBalancedFargateService works', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + entryPoint: ['echo', 'foo'], + }, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + EntryPoint: ['echo', 'foo'], + }), + ], + }); +}); + test('setting NLB deployment controller', () => { // GIVEN const stack = new cdk.Stack(); @@ -1165,4 +1209,4 @@ test('NetworkLoadBalancedFargateService multiple capacity provider strategies ar }, ]), }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index df4a9fd4d372e..896e8a1d9cee5 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -466,6 +466,14 @@ export abstract class BaseService extends Resource Annotations.of(this).addWarning('taskDefinition and launchType are blanked out when using external deployment controller.'); } + if (props.deploymentController?.type === DeploymentControllerType.CODE_DEPLOY) { + // Strip the revision ID from the service's task definition property to + // prevent new task def revisions in the stack from triggering updates + // to the stack's ECS service resource + this.resource.taskDefinition = taskDefinition.family; + this.node.addDependency(taskDefinition); + } + this.serviceArn = this.getResourceArnAttribute(this.resource.ref, { service: 'ecs', resource: 'service', diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 7523fc936227e..a8f715c294e2b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -845,7 +845,7 @@ describe('ec2 service', () => { maxHealthyPercent: 150, minHealthyPercent: 55, deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, + type: ecs.DeploymentControllerType.ECS, }, securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, @@ -873,7 +873,7 @@ describe('ec2 service', () => { MinimumHealthyPercent: 55, }, DeploymentController: { - Type: ecs.DeploymentControllerType.CODE_DEPLOY, + Type: ecs.DeploymentControllerType.ECS, }, DesiredCount: 2, LaunchType: LaunchType.EC2, @@ -1093,6 +1093,42 @@ describe('ec2 service', () => { }); + test('sets task definition to family when CODE_DEPLOY deployment controller is specified', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + addDefaultCapacityProvider(cluster, stack, vpc); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + + // THEN + Template.fromStack(stack).hasResource('AWS::ECS::Service', { + Properties: { + TaskDefinition: 'Ec2TaskDef', + DeploymentController: { + Type: 'CODE_DEPLOY', + }, + }, + DependsOn: [ + 'Ec2TaskDef0226F28C', + 'Ec2TaskDefTaskRole400FA349', + ], + }); + }); + testDeprecated('throws when both securityGroup and securityGroups are supplied', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 1bc1bea59ef92..2172169f3219c 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -744,15 +744,51 @@ describe('fargate service', () => { new ecs.FargateService(stack, 'FargateService', { cluster, taskDefinition, - minHealthyPercent: 0, + assignPublicIp: true, }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - MinimumHealthyPercent: 0, + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'ENABLED', + }, + }, + }); + }); + + test('sets task definition to family when CODE_DEPLOY deployment controller is specified', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, }, }); + + // THEN + Template.fromStack(stack).hasResource('AWS::ECS::Service', { + Properties: { + TaskDefinition: 'FargateTaskDef', + DeploymentController: { + Type: 'CODE_DEPLOY', + }, + }, + DependsOn: [ + 'FargateTaskDefC6FB60B4', + 'FargateTaskDefTaskRole0B257552', + ], + }); }); testDeprecated('throws when securityGroup and securityGroups are supplied', () => { diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index be7aab79647a1..eecee0c55e08e 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -680,20 +680,20 @@ The version of kubectl used must be compatible with the Kubernetes version of th cluster. kubectl is supported within one minor version (older or newer) of Kubernetes (see [Kubernetes version skew policy](https://kubernetes.io/releases/version-skew-policy/#kubectl)). Only version 1.20 of kubectl is available in `aws-cdk-lib`. If you need a different -version, you will need to use one of the `@aws-cdk/lambda-layer-kubectlvXY` packages. +version, you will need to use one of the `@aws-cdk/lambda-layer-kubectl-vXY` packages. ```ts -import { KubectlV22Layer } from '@aws-cdk/lambda-layer-kubectl-v22'; +import { KubectlV23Layer } from '@aws-cdk/lambda-layer-kubectl-v23'; const cluster = new eks.Cluster(this, 'hello-eks', { - version: eks.KubernetesVersion.V1_22, - kubectlLayer: new KubectlV22Layer(this, 'kubectl'), + version: eks.KubernetesVersion.V1_23, + kubectlLayer: new KubectlV23Layer(this, 'kubectl'), }); ``` You can also specify a custom `lambda.LayerVersion` if you wish to use a different version of these tools, or a version not available in any of the -`@aws-cdk/lambda-layer-kubectlvXY` packages. The handler expects the layer to +`@aws-cdk/lambda-layer-kubectl-vXY` packages. The handler expects the layer to include the following two executables: ```text diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 4ac6e5f176cbd..9dfebd5ac0715 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -30,7 +30,7 @@ export interface HelmChartOptions { readonly version?: string; /** - * The repository which contains the chart. For example: https://kubernetes-charts.storage.googleapis.com/ + * The repository which contains the chart. For example: https://charts.helm.sh/stable/ * @default - No repository will be used, which means that the chart needs to be an absolute URL. */ readonly repository?: string; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 8242e28ec64ab..d2102e5e703fc 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -1,9 +1,9 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Duration, IResource, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Duration, Lazy, Resource, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; -import { BaseListener, BaseListenerLookupOptions } from '../shared/base-listener'; +import { BaseListener, BaseListenerLookupOptions, IListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { ApplicationProtocol, ApplicationProtocolVersion, TargetGroupLoadBalancingAlgorithmType, IpAddressType, SslPolicy } from '../shared/enums'; import { IListenerCertificate, ListenerCertificate } from '../shared/listener-certificate'; @@ -485,13 +485,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis /** * Properties to reference an existing listener */ -export interface IApplicationListener extends IResource, ec2.IConnectable { - /** - * ARN of the listener - * @attribute - */ - readonly listenerArn: string; - +export interface IApplicationListener extends IListener, ec2.IConnectable { /** * Add one or more certificates to this listener. * @deprecated use `addCertificates()` diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index df043d06282b7..64f5050573e45 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -1,7 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Duration, IResource, Resource, Lazy } from '@aws-cdk/core'; +import { Duration, Resource, Lazy } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { BaseListener, BaseListenerLookupOptions } from '../shared/base-listener'; +import { BaseListener, BaseListenerLookupOptions, IListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { AlpnPolicy, Protocol, SslPolicy } from '../shared/enums'; import { IListenerCertificate } from '../shared/listener-certificate'; @@ -311,12 +311,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { /** * Properties to reference an existing listener */ -export interface INetworkListener extends IResource { - /** - * ARN of the listener - * @attribute - */ - readonly listenerArn: string; +export interface INetworkListener extends IListener { } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index da4174c157820..b9ec37208e53b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -1,5 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Annotations, ContextProvider, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Annotations, ContextProvider, IResource, Lazy, Resource, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { CfnListener } from '../elasticloadbalancingv2.generated'; @@ -56,10 +56,21 @@ export interface ListenerQueryContextProviderOptions { readonly listenerProtocol?: cxschema.LoadBalancerListenerProtocol; } +/** + * Base interface for listeners + */ +export interface IListener extends IResource { + /** + * ARN of the listener + * @attribute + */ + readonly listenerArn: string; +} + /** * Base class for listeners */ -export abstract class BaseListener extends Resource { +export abstract class BaseListener extends Resource implements IListener { /** * Queries the load balancer listener context provider for load balancer * listener info. diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/log-group-resource-policy.ts b/packages/@aws-cdk/aws-elasticsearch/lib/log-group-resource-policy.ts index 0a88658e1cf42..0fd9c98464335 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/log-group-resource-policy.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/log-group-resource-policy.ts @@ -42,7 +42,7 @@ export class LogGroupResourcePolicy extends cr.AwsCustomResource { parameters: { policyName: props.policyName, }, - ignoreErrorCodesMatching: '400', + ignoreErrorCodesMatching: 'ResourceNotFoundException', }, policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: ['*'] }), }); diff --git a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.custom-kms-key.integ.snapshot/cdk-integ-elasticsearch-custom-kms-key.template.json b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.custom-kms-key.integ.snapshot/cdk-integ-elasticsearch-custom-kms-key.template.json index dfb7d83603170..d43c1fc4648a8 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.custom-kms-key.integ.snapshot/cdk-integ-elasticsearch-custom-kms-key.template.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.custom-kms-key.integ.snapshot/cdk-integ-elasticsearch-custom-kms-key.template.json @@ -104,7 +104,7 @@ ] ] }, - "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc82ca7bfe2f2589b859ebab89e88da2efd284adfad\"},\"ignoreErrorCodesMatching\":\"400\"}", + "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc82ca7bfe2f2589b859ebab89e88da2efd284adfad\"},\"ignoreErrorCodesMatching\":\"ResourceNotFoundException\"}", "InstallLatestAwsSdk": true }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.integ.snapshot/cdk-integ-elasticsearch.template.json b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.integ.snapshot/cdk-integ-elasticsearch.template.json index 1038c14f917e8..5c4ba1a3baf2c 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.integ.snapshot/cdk-integ-elasticsearch.template.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch.integ.snapshot/cdk-integ-elasticsearch.template.json @@ -69,7 +69,7 @@ ] ] }, - "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc8858d5dba055f677469d76cb6ad538fd732ba69a6\"},\"ignoreErrorCodesMatching\":\"400\"}", + "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc8858d5dba055f677469d76cb6ad538fd732ba69a6\"},\"ignoreErrorCodesMatching\":\"ResourceNotFoundException\"}", "InstallLatestAwsSdk": true }, "DependsOn": [ @@ -377,7 +377,7 @@ ] ] }, - "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc8405238e455eeabd840cf6933e1814efc51d2de71\"},\"ignoreErrorCodesMatching\":\"400\"}", + "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc8405238e455eeabd840cf6933e1814efc51d2de71\"},\"ignoreErrorCodesMatching\":\"ResourceNotFoundException\"}", "InstallLatestAwsSdk": true }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-elasticsearch/test/log-group-resource-policy.test.ts b/packages/@aws-cdk/aws-elasticsearch/test/log-group-resource-policy.test.ts index 815c0086d4a99..bbfa0c37a23c4 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/log-group-resource-policy.test.ts +++ b/packages/@aws-cdk/aws-elasticsearch/test/log-group-resource-policy.test.ts @@ -59,7 +59,7 @@ test('minimal example renders correctly', () => { parameters: { policyName: 'TestPolicy', }, - ignoreErrorCodesMatching: '400', + ignoreErrorCodesMatching: 'ResourceNotFoundException', }), }); }); diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 3d912ec561254..fb044f4eb72bf 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -368,9 +368,12 @@ export class Rule extends Resource implements IRule { // Leaving it in for backwards compatibility. stackName: `${targetStack.stackName}-EventBusPolicy-support-${targetRegion}-${sourceAccount}`, }); + const statementPrefix = `Allow-account-${sourceAccount}-`; new CfnEventBusPolicy(eventBusPolicyStack, 'GivePermToOtherAccount', { action: 'events:PutEvents', - statementId: `Allow-account-${sourceAccount}-${this.node.addr}`, + statementId: statementPrefix + Names.uniqueResourceName(this, { + maxLength: 64 - statementPrefix.length, + }), principal: sourceAccount, }); } diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json new file mode 100644 index 0000000000000..28858ec3614c2 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72": { + "source": { + "path": "asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494": { + "source": { + "path": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json new file mode 100644 index 0000000000000..acbf2ffcabff2 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json @@ -0,0 +1,156 @@ +{ + "Resources": { + "AwsApiCallEventBridgedescribeEventBus": { + "Type": "Custom::DeployAssert@SdkCallEventBridgedescribeEventBus", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "EventBridge", + "api": "describeEventBus", + "flattenResponse": "true", + "salt": "1666817700798" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036": { + "Type": "Custom::DeployAssert@AssertEquals", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "actual": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeEventBus", + "apiCallResponse.Policy" + ] + }, + "expected": "{\"$ObjectLike\":{\"Statement\":{\"$ArrayWith\":[{\"$ObjectLike\":{\"Sid\":{\"$StringLike\":\"Allow-account-987654321\"},\"Principal\":{\"AWS\":\"arn:aws:iam::987654321:root\"},\"Resource\":{\"$StringLike\":\"arn:aws:events:us-east-1:12345678\"}}}]}}}", + "salt": "1666817700798" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "eventbridge:DescribeEventBus" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "events:DescribeEventBus" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAssertEqualsEventBridgedescribeEventBusfd3cf7d971587606ecf8442a4cb30f1b": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036", + "data" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json new file mode 100644 index 0000000000000..37430d4ba4941 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4": { + "source": { + "path": "EventBusPolicy-987654321-test-region-12345678.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json new file mode 100644 index 0000000000000..40569324086e3 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/EventBusPolicy-987654321-test-region-12345678.template.json @@ -0,0 +1,46 @@ +{ + "Resources": { + "GivePermToOtherAccount": { + "Type": "AWS::Events::EventBusPolicy", + "Properties": { + "StatementId": "Allow-account-987654321-FromCrossAccountRuleStackMyRule68A189ED", + "Action": "events:PutEvents", + "Principal": "987654321" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json new file mode 100644 index 0000000000000..90bb64e7eb6b5 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70": { + "source": { + "path": "FromCrossAccountRuleStack.template.json", + "packaging": "file" + }, + "destinations": { + "987654321-test-region": { + "bucketName": "cdk-hnb659fds-assets-987654321-test-region", + "objectKey": "c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-file-publishing-role-987654321-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json new file mode 100644 index 0000000000000..5d2cea910dadf --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/FromCrossAccountRuleStack.template.json @@ -0,0 +1,74 @@ +{ + "Resources": { + "MyRuleA44AB831": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:test-region:12345678:event-bus/default" + ] + ] + }, + "Id": "SQS" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json new file mode 100644 index 0000000000000..37baa48413aff --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.assets.json @@ -0,0 +1,20 @@ +{ + "version": "21.0.0", + "files": { + "0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c": { + "source": { + "path": "ToCrossAccountRuleStack.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json new file mode 100644 index 0000000000000..4c3f53989a1f1 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/ToCrossAccountRuleStack.template.json @@ -0,0 +1,81 @@ +{ + "Resources": { + "Queue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "IntegTestCrossEnvRule", + "ReceiveMessageWaitTimeSeconds": 20 + } + }, + "FromCrossAccountRuleStackMyRule68A189EDSQS1A422535": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sqs:test-region:12345678:IntegTestCrossEnvRule" + ] + ] + }, + "Id": "SQS" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js new file mode 100644 index 0000000000000..09ec17c1ae178 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/asset.2a53dc40a7dae81c8850e125ab49e5f55d80b7b8ceac86976f2a4119393cab72.bundle/index.js @@ -0,0 +1,611 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`); + const response = await this.processEvent(this.event.ResourceProperties); + console.log(`Event output : ${JSON.stringify(response)}`); + await this.respond({ + status: "SUCCESS", + reason: "OK", + data: response + }); + } catch (e) { + console.log(e); + await this.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + data: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.data); + } + } else { + result = { + data: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS.VERSION}`); + const service = new AWS[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + return request2.flattenResponse === "true" ? flatData : respond; + } +}; + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + const provider = createResourceHandler(event, context); + await provider.handle(); +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } + switch (event.ResourceType) { + case ASSERT_RESOURCE_TYPE: + return new AssertionHandler(event, context); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json new file mode 100644 index 0000000000000..5ac40c3def2c4 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "CrossAccountDeploy/DefaultTest": { + "stacks": [ + "ToCrossAccountRuleStack" + ], + "assertionStack": "CrossAccountDeploy/DefaultTest/DeployAssert", + "assertionStackName": "CrossAccountDeployDefaultTestDeployAssertB5328BEF" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..2d333b795f71e --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/manifest.json @@ -0,0 +1,257 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "FromCrossAccountRuleStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "FromCrossAccountRuleStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "FromCrossAccountRuleStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://987654321/test-region", + "properties": { + "templateFile": "FromCrossAccountRuleStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-deploy-role-987654321-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-cfn-exec-role-987654321-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-987654321-test-region/c6101587e135a4563e666bf1dd45e671ba4c4bc60130f3b8167502cbc174aa70.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "FromCrossAccountRuleStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::987654321:role/cdk-hnb659fds-lookup-role-987654321-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "EventBusPolicy-987654321-test-region-12345678", + "FromCrossAccountRuleStack.assets" + ], + "metadata": { + "/FromCrossAccountRuleStack/MyRule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyRuleA44AB831" + } + ], + "/FromCrossAccountRuleStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/FromCrossAccountRuleStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "FromCrossAccountRuleStack" + }, + "ToCrossAccountRuleStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "ToCrossAccountRuleStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "ToCrossAccountRuleStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/test-region", + "properties": { + "templateFile": "ToCrossAccountRuleStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-test-region/0d750187c0e1bc77f1edfc3af57e55036907d6dfaef463a7acfdc0c42325a18c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "ToCrossAccountRuleStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "FromCrossAccountRuleStack", + "ToCrossAccountRuleStack.assets" + ], + "metadata": { + "/ToCrossAccountRuleStack/Queue": [ + { + "type": "aws:cdk:logicalId", + "data": "Queue" + } + ], + "/ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FromCrossAccountRuleStackMyRule68A189EDSQS1A422535" + } + ], + "/ToCrossAccountRuleStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/ToCrossAccountRuleStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "ToCrossAccountRuleStack" + }, + "EventBusPolicy-987654321-test-region-12345678.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "EventBusPolicy-987654321-test-region-12345678.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "EventBusPolicy-987654321-test-region-12345678": { + "type": "aws:cloudformation:stack", + "environment": "aws://12345678/test-region", + "properties": { + "templateFile": "EventBusPolicy-987654321-test-region-12345678.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-test-region/462c696e4c93ec0e97ebd5917666e8ded21f0a81055e38f6683a27853ca79fd4.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "EventBusPolicy-987654321-test-region-12345678.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-test-region", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + }, + "stackName": "ToCrossAccountRuleStack-EventBusPolicy-support-test-region-987654321" + }, + "dependencies": [ + "EventBusPolicy-987654321-test-region-12345678.assets" + ], + "metadata": { + "/EventBusPolicy-987654321-test-region-12345678/GivePermToOtherAccount": [ + { + "type": "aws:cdk:logicalId", + "data": "GivePermToOtherAccount" + } + ], + "/EventBusPolicy-987654321-test-region-12345678/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/EventBusPolicy-987654321-test-region-12345678/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "EventBusPolicy-987654321-test-region-12345678" + }, + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "CrossAccountDeployDefaultTestDeployAssertB5328BEF": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "CrossAccountDeployDefaultTestDeployAssertB5328BEF.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/06c797cad62334a220096d2292d9e1028ba01ed3582f081c5ddee52bc7e0c494.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "ToCrossAccountRuleStack", + "CrossAccountDeployDefaultTestDeployAssertB5328BEF.assets" + ], + "metadata": { + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeEventBus" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeEventBusAssertEqualsEventBridgedescribeEventBusB063C036" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAssertEqualsEventBridgedescribeEventBusfd3cf7d971587606ecf8442a4cb30f1b" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/CrossAccountDeploy/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "CrossAccountDeploy/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json new file mode 100644 index 0000000000000..d535a83fdf760 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/cross-account-rule.integ.snapshot/tree.json @@ -0,0 +1,355 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "FromCrossAccountRuleStack": { + "id": "FromCrossAccountRuleStack", + "path": "FromCrossAccountRuleStack", + "children": { + "MyRule": { + "id": "MyRule", + "path": "FromCrossAccountRuleStack/MyRule", + "children": { + "Resource": { + "id": "Resource", + "path": "FromCrossAccountRuleStack/MyRule/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Rule", + "aws:cdk:cloudformation:props": { + "eventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "state": "ENABLED", + "targets": [ + { + "id": "SQS", + "arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:test-region:12345678:event-bus/default" + ] + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Rule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "ToCrossAccountRuleStack": { + "id": "ToCrossAccountRuleStack", + "path": "ToCrossAccountRuleStack", + "children": { + "Queue": { + "id": "Queue", + "path": "ToCrossAccountRuleStack/Queue", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "FromCrossAccountRuleStackMyRule68A189ED-SQS": { + "id": "FromCrossAccountRuleStackMyRule68A189ED-SQS", + "path": "ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS", + "children": { + "Resource": { + "id": "Resource", + "path": "ToCrossAccountRuleStack/FromCrossAccountRuleStackMyRule68A189ED-SQS/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Rule", + "aws:cdk:cloudformation:props": { + "eventPattern": { + "detail": { + "foo": [ + "bar" + ] + }, + "detail-type": [ + "cdk-integ-custom-rule" + ], + "source": [ + "cdk-integ" + ] + }, + "state": "ENABLED", + "targets": [ + { + "id": "SQS", + "arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sqs:test-region:12345678:IntegTestCrossEnvRule" + ] + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Rule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "EventBusPolicy-987654321-test-region-12345678": { + "id": "EventBusPolicy-987654321-test-region-12345678", + "path": "EventBusPolicy-987654321-test-region-12345678", + "children": { + "GivePermToOtherAccount": { + "id": "GivePermToOtherAccount", + "path": "EventBusPolicy-987654321-test-region-12345678/GivePermToOtherAccount", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::EventBusPolicy", + "aws:cdk:cloudformation:props": { + "statementId": "Allow-account-987654321-FromCrossAccountRuleStackMyRule68A189ED", + "action": "events:PutEvents", + "principal": "987654321" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnEventBusPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "CrossAccountDeploy": { + "id": "CrossAccountDeploy", + "path": "CrossAccountDeploy", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "CrossAccountDeploy/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert", + "children": { + "AwsApiCallEventBridgedescribeEventBus": { + "id": "AwsApiCallEventBridgedescribeEventBus", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertEqualsEventBridgedescribeEventBus": { + "id": "AssertEqualsEventBridgedescribeEventBus", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus", + "children": { + "AssertionProvider": { + "id": "AssertionProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default", + "children": { + "Default": { + "id": "Default", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeEventBus/AssertEqualsEventBridgedescribeEventBus/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.EqualsAssertion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "CrossAccountDeploy/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.102" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts b/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts new file mode 100644 index 0000000000000..c77813f90ed56 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/integ.cross-account-rule.ts @@ -0,0 +1,99 @@ +/// !cdk-integ * +import { App, Arn, CfnResource, Stack } from '@aws-cdk/core'; +import { ExpectedResult, IntegTest, Match } from '@aws-cdk/integ-tests'; +import { Rule, IRuleTarget } from '../lib'; + +/** + * Basic idea for this test is to create an EventBridge that "connects" + * an SQS queue in one account to another account. Nothing is sent on the + * queue, it's just used to set up the condition where aws-events creates + * a support stack. + */ + +const app = new App(); + +const account = process.env.CDK_INTEG_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT; + +// As the integ-runner doesnt provide a default cross account, we make our own. +const crossAccount = process.env.CDK_INTEG_CROSS_ACCOUNT || '987654321'; +const region = process.env.CDK_INTEG_REGION || process.env.CDK_DEFAULT_REGION; + +const fromCrossAccountStack = new Stack(app, 'FromCrossAccountRuleStack', { + env: { + account: crossAccount, + region, + }, +}); + +/** + * To make this testable, we need to have the stack that stores the event bridge be in + * the same account that the IntegTest stack is deployed into. Otherwise, we have no + * access to the IAM policy that the EventBusPolicy-account-region support stack creates. + */ +const toCrossAccountStack = new Stack(app, 'ToCrossAccountRuleStack', { + env: { + account, + region, + }, +}); +const queueName = 'IntegTestCrossEnvRule'; + +const queue = new CfnResource(toCrossAccountStack, 'Queue', { + type: 'AWS::SQS::Queue', + properties: { + QueueName: queueName, + ReceiveMessageWaitTimeSeconds: 20, + }, +}); + +const target: IRuleTarget = { + bind: () => ({ + id: 'SQS', + arn: Arn.format({ + resource: queueName, + service: 'sqs', + }, toCrossAccountStack), + targetResource: queue, + }), +}; + +new Rule(fromCrossAccountStack, 'MyRule', { + eventPattern: { + detail: { + foo: ['bar'], + }, + detailType: ['cdk-integ-custom-rule'], + source: ['cdk-integ'], + }, + targets: [target], +}); + +toCrossAccountStack.addDependency(fromCrossAccountStack); + +const integ = new IntegTest(app, 'CrossAccountDeploy', { + testCases: [ + toCrossAccountStack, + ], +}); + +// We are using the default event bus, don't need to define any parameters for this call. +const eventVerification = integ.assertions.awsApiCall('EventBridge', 'describeEventBus'); + +integ.node.addDependency(toCrossAccountStack); + +eventVerification.provider.addPolicyStatementFromSdkCall('events', 'DescribeEventBus'); + +// IAM policy will be created by the support stack, assert that everything created as expected. +eventVerification.assertAtPath('Policy', ExpectedResult.objectLike({ + Statement: Match.arrayWith( + [Match.objectLike({ + Sid: Match.stringLikeRegexp(`Allow-account-${crossAccount}`), + Principal: { + AWS: `arn:aws:iam::${crossAccount}:root`, + }, + Resource: Match.stringLikeRegexp(`arn:aws:events:us-east-1:${account}`), + })], + ), +})); + +app.synth(); diff --git a/packages/@aws-cdk/aws-events/test/rule.test.ts b/packages/@aws-cdk/aws-events/test/rule.test.ts index a822f2d7c256b..b000e914d8312 100644 --- a/packages/@aws-cdk/aws-events/test/rule.test.ts +++ b/packages/@aws-cdk/aws-events/test/rule.test.ts @@ -933,7 +933,7 @@ describe('rule', () => { const app = new cdk.App(); const sourceAccount = '123456789012'; - const nodeAddr = 'c810f4680339b01edf1f157c4fd07da73469742773'; + const uniqueName = 'SourceStackRuleD6962A13'; const sourceStack = new cdk.Stack(app, 'SourceStack', { env: { account: sourceAccount, @@ -1015,7 +1015,7 @@ describe('rule', () => { const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; Template.fromStack(eventBusPolicyStack).hasResourceProperties('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', - 'StatementId': `Allow-account-${sourceAccount}-${nodeAddr}`, + 'StatementId': `Allow-account-${sourceAccount}-${uniqueName}`, 'Principal': sourceAccount, }); }); diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts index 265e72535f812..67c12e1a1fbc3 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts @@ -52,6 +52,11 @@ export abstract class LambdaInsightsVersion { */ public static readonly VERSION_1_0_135_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.135.0'); + /** + * Version 1.0.143.0 + */ + public static readonly VERSION_1_0_143_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.143.0'); + /** * Use the insights extension associated with the provided ARN. Make sure the ARN is associated * with same region as your function diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts index a2e3f5a9cc484..814b1f523cf0f 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts @@ -239,4 +239,15 @@ describe('lambda-insights', () => { // On synthesis it should not throw an error expect(() => app.synth()).not.toThrow(); }); + test('can use layer v1.0.143.0', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + functionWithInsightsVersion(stack, 'MyLambda', lambda.LambdaInsightsVersion.VERSION_1_0_143_0, lambda.Architecture.X86_64); + + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + Layers: ['arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:21'], + }); + }); }); diff --git a/packages/@aws-cdk/aws-opensearchservice/lib/log-group-resource-policy.ts b/packages/@aws-cdk/aws-opensearchservice/lib/log-group-resource-policy.ts index 0a88658e1cf42..0fd9c98464335 100644 --- a/packages/@aws-cdk/aws-opensearchservice/lib/log-group-resource-policy.ts +++ b/packages/@aws-cdk/aws-opensearchservice/lib/log-group-resource-policy.ts @@ -42,7 +42,7 @@ export class LogGroupResourcePolicy extends cr.AwsCustomResource { parameters: { policyName: props.policyName, }, - ignoreErrorCodesMatching: '400', + ignoreErrorCodesMatching: 'ResourceNotFoundException', }, policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: ['*'] }), }); diff --git a/packages/@aws-cdk/aws-opensearchservice/test/log-group-resource-policy.test.ts b/packages/@aws-cdk/aws-opensearchservice/test/log-group-resource-policy.test.ts index 815c0086d4a99..bbfa0c37a23c4 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/log-group-resource-policy.test.ts +++ b/packages/@aws-cdk/aws-opensearchservice/test/log-group-resource-policy.test.ts @@ -59,7 +59,7 @@ test('minimal example renders correctly', () => { parameters: { policyName: 'TestPolicy', }, - ignoreErrorCodesMatching: '400', + ignoreErrorCodesMatching: 'ResourceNotFoundException', }), }); }); diff --git a/packages/@aws-cdk/aws-opensearchservice/test/opensearch.integ.snapshot/cdk-integ-opensearch.template.json b/packages/@aws-cdk/aws-opensearchservice/test/opensearch.integ.snapshot/cdk-integ-opensearch.template.json index 0c2015f39b20b..eb098a4ac8330 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/opensearch.integ.snapshot/cdk-integ-opensearch.template.json +++ b/packages/@aws-cdk/aws-opensearchservice/test/opensearch.integ.snapshot/cdk-integ-opensearch.template.json @@ -69,7 +69,7 @@ ] ] }, - "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc881416c4fcb1ec2b4bf7f47a5cde4097f01ec50fc\"},\"ignoreErrorCodesMatching\":\"400\"}", + "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc881416c4fcb1ec2b4bf7f47a5cde4097f01ec50fc\"},\"ignoreErrorCodesMatching\":\"ResourceNotFoundException\"}", "InstallLatestAwsSdk": true }, "DependsOn": [ @@ -374,7 +374,7 @@ ] ] }, - "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc80140a7754e9c0dd4e81167ef19e15da5b55dca02\"},\"ignoreErrorCodesMatching\":\"400\"}", + "Delete": "{\"service\":\"CloudWatchLogs\",\"action\":\"deleteResourcePolicy\",\"parameters\":{\"policyName\":\"ESLogPolicyc80140a7754e9c0dd4e81167ef19e15da5b55dca02\"},\"ignoreErrorCodesMatching\":\"ResourceNotFoundException\"}", "InstallLatestAwsSdk": true }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts index 9918535134c67..0973734194573 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts @@ -109,6 +109,10 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase { resources: [ this.props.resultConfiguration?.outputLocation?.bucketName ? cdk.Stack.of(this).formatArn({ + // S3 Bucket names are globally unique in a partition, + // and so their ARNs have empty region and account components + region: '', + account: '', service: 's3', resource: this.props.resultConfiguration?.outputLocation?.bucketName, resourceName: this.props.resultConfiguration?.outputLocation?.objectKey, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.ts index f59032aa76430..1a8e3e21d9bb2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.ts @@ -20,6 +20,10 @@ const startQueryExecutionJob = new AthenaStartQueryExecution(stack, 'Start Athen encryptionConfiguration: { encryptionOption: EncryptionOption.S3_MANAGED, }, + outputLocation: { + bucketName: 'query-results-bucket', + objectKey: 'folder', + }, }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.assets.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.assets.json index 04bfe2e5913af..4460ae813ec4f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.assets.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "60ec4a933a76b17b02958f5e3be45afa01bedec88203c997738cf20fe4cf7fc2": { + "18086c8605b3e9331cb12bb96c57aa22b79a3d214ce457de38940cde714961ad": { "source": { "path": "aws-stepfunctions-tasks-athena-start-query-execution-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "60ec4a933a76b17b02958f5e3be45afa01bedec88203c997738cf20fe4cf7fc2.json", + "objectKey": "18086c8605b3e9331cb12bb96c57aa22b79a3d214ce457de38940cde714961ad.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.template.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.template.json index f4ea4a9173888..6f7da739fe6af 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.template.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/aws-stepfunctions-tasks-athena-start-query-execution-integ.template.json @@ -83,17 +83,34 @@ { "Action": [ "lakeformation:GetDataAccess", - "s3:AbortMultipartUpload", "s3:CreateBucket", "s3:GetBucketLocation", "s3:GetObject", - "s3:ListBucket", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" ], "Effect": "Allow", - "Resource": "*" + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::query-results-bucket/folder" + ] + ] + } }, { "Action": [ @@ -229,7 +246,7 @@ { "Ref": "AWS::Partition" }, - ":states:::athena:startQueryExecution\",\"Parameters\":{\"QueryString.$\":\"$.queryString\",\"QueryExecutionContext\":{\"Database\":\"mydatabase\"},\"ResultConfiguration\":{\"EncryptionConfiguration\":{\"EncryptionOption\":\"SSE_S3\"}}}}},\"TimeoutSeconds\":30}" + ":states:::athena:startQueryExecution\",\"Parameters\":{\"QueryString.$\":\"$.queryString\",\"QueryExecutionContext\":{\"Database\":\"mydatabase\"},\"ResultConfiguration\":{\"EncryptionConfiguration\":{\"EncryptionOption\":\"SSE_S3\"},\"OutputLocation\":\"s3://query-results-bucket/folder/\"}}}},\"TimeoutSeconds\":30}" ] ] } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/integ.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/integ.json index bdbde3211eb26..e9ae4d978b1e9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.start-query-execution": { "stacks": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/manifest.json index 869f809699425..b37b0910850ac 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/60ec4a933a76b17b02958f5e3be45afa01bedec88203c997738cf20fe4cf7fc2.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/18086c8605b3e9331cb12bb96c57aa22b79a3d214ce457de38940cde714961ad.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/tree.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/tree.json index 9d89653ccc961..2fc8b7e7dfad7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "aws-stepfunctions-tasks-athena-start-query-execution-integ": { @@ -130,17 +130,34 @@ { "Action": [ "lakeformation:GetDataAccess", - "s3:AbortMultipartUpload", "s3:CreateBucket", "s3:GetBucketLocation", "s3:GetObject", - "s3:ListBucket", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" ], "Effect": "Allow", - "Resource": "*" + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::query-results-bucket/folder" + ] + ] + } }, { "Action": [ @@ -296,7 +313,7 @@ { "Ref": "AWS::Partition" }, - ":states:::athena:startQueryExecution\",\"Parameters\":{\"QueryString.$\":\"$.queryString\",\"QueryExecutionContext\":{\"Database\":\"mydatabase\"},\"ResultConfiguration\":{\"EncryptionConfiguration\":{\"EncryptionOption\":\"SSE_S3\"}}}}},\"TimeoutSeconds\":30}" + ":states:::athena:startQueryExecution\",\"Parameters\":{\"QueryString.$\":\"$.queryString\",\"QueryExecutionContext\":{\"Database\":\"mydatabase\"},\"ResultConfiguration\":{\"EncryptionConfiguration\":{\"EncryptionOption\":\"SSE_S3\"},\"OutputLocation\":\"s3://query-results-bucket/folder/\"}}}},\"TimeoutSeconds\":30}" ] ] } @@ -317,28 +334,28 @@ "id": "stateMachineArn", "path": "aws-stepfunctions-tasks-athena-start-query-execution-integ/stateMachineArn", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" } }, "Service-principalMap": { "id": "Service-principalMap", "path": "aws-stepfunctions-tasks-athena-start-query-execution-integ/Service-principalMap", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts index 6b3c24b62a045..5efe5525e9a99 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts @@ -216,15 +216,7 @@ describe('Start Query Execution', () => { { Ref: 'AWS::Partition', }, - ':s3:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':query-results-bucket/folder', + ':s3:::query-results-bucket/folder', ], ], }, diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 52386680d26ba..e4ae99f92f423 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -192,9 +192,23 @@ You can also call [intrinsic functions](https://docs.aws.amazon.com/step-functio | Method | Purpose | |--------|---------| | `JsonPath.array(JsonPath.stringAt('$.Field'), ...)` | make an array from other elements. | -| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. | +| `JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4)` | partition an array. | +| `JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5)` | determine if a specific value is present in an array. | +| `JsonPath.arrayRange(1, 9, 2)` | create a new array containing a specific range of elements. | +| `JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5)` | get a specified index's value in an array. | +| `JsonPath.arrayLength(JsonPath.listAt('$.inputArray'))` | get the length of an array. | +| `JsonPath.arrayUnique(JsonPath.listAt('$.inputArray'))` | remove duplicate values from an array. | +| `JsonPath.base64Encode(JsonPath.stringAt('$.input'))` | encode data based on MIME Base64 encoding scheme. | +| `JsonPath.base64Decode(JsonPath.stringAt('$.base64'))` | decode data based on MIME Base64 decoding scheme. | +| `JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm'))` | calculate the hash value of a given input. | +| `JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2'))` | merge two JSON objects into a single object. | | `JsonPath.stringToJson(JsonPath.stringAt('$.ObjStr'))` | parse a JSON string to an object | | `JsonPath.jsonToString(JsonPath.objectAt('$.Obj'))` | stringify an object to a JSON string | +| `JsonPath.mathRandom(1, 999)` | return a random number. | +| `JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step'))` | return the sum of two numbers. | +| `JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter'))` | split a string into an array of values. | +| `JsonPath.uuid()` | return a version 4 universally unique identifier (v4 UUID). | +| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. | ## Amazon States Language diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts index 485b92b62337c..0d152d15c9656 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts @@ -103,6 +103,161 @@ export class JsonPath { return new JsonPathToken(`States.Array(${values.map(renderInExpression).join(', ')})`).toString(); } + /** + * Make an intrinsic States.ArrayPartition expression + * + * Use this function to partition a large array. You can also use this intrinsic to slice the data and then send the payload in smaller chunks. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayPartition(array: any, chunkSize: number): string { + return new JsonPathToken(`States.ArrayPartition(${[array, chunkSize].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayContains expression + * + * Use this function to determine if a specific value is present in an array. For example, you can use this function to detect if there was an error in a Map state iteration. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayContains(array: any, value: any): string { + return new JsonPathToken(`States.ArrayContains(${[array, value].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayRange expression + * + * Use this function to create a new array containing a specific range of elements. The new array can contain up to 1000 elements. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayRange(start: number, end: number, step: number): string { + return new JsonPathToken(`States.ArrayRange(${[start, end, step].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayGetItem expression + * + * Use this function to get a specified index's value in an array. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayGetItem(array: any, index: number): string { + return new JsonPathToken(`States.ArrayGetItem(${[array, index].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayLength expression + * + * Use this function to get the length of an array. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayLength(array: any): string { + return new JsonPathToken(`States.ArrayLength(${renderInExpression(array)})`).toString(); + } + + /** + * Make an intrinsic States.ArrayUnique expression + * + * Use this function to get the length of an array. + * Use this function to remove duplicate values from an array and returns an array containing only unique elements. This function takes an array, which can be unsorted, as its sole argument. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayUnique(array: any): string { + return new JsonPathToken(`States.ArrayUnique(${renderInExpression(array)})`).toString(); + } + + /** + * Make an intrinsic States.Base64Encode expression + * + * Use this function to encode data based on MIME Base64 encoding scheme. You can use this function to pass data to other AWS services without using an AWS Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static base64Encode(input: string): string { + return new JsonPathToken(`States.Base64Encode(${renderInExpression(input)})`).toString(); + } + + /** + * Make an intrinsic States.Base64Decode expression + * + * Use this function to decode data based on MIME Base64 decoding scheme. You can use this function to pass data to other AWS services without using a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static base64Decode(base64: string): string { + return new JsonPathToken(`States.Base64Decode(${renderInExpression(base64)})`).toString(); + } + + /** + * Make an intrinsic States.Hash expression + * + * Use this function to calculate the hash value of a given input. You can use this function to pass data to other AWS services without using a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static hash(data: any, algorithm: string): string { + return new JsonPathToken(`States.Hash(${[data, algorithm].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.JsonMerge expression + * + * Use this function to merge two JSON objects into a single object. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static jsonMerge(value1: any, value2: any): string { + return new JsonPathToken(`States.JsonMerge(${[value1, value2].map(renderInExpression).join(', ')}, false)`).toString(); + } + + /** + * Make an intrinsic States.MathRandom expression + * + * Use this function to return a random number between the specified start and end number. For example, you can use this function to distribute a specific task between two or more resources. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static mathRandom(start: number, end: number): string { + return new JsonPathToken(`States.MathRandom(${[start, end].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.MathAdd expression + * + * Use this function to return the sum of two numbers. For example, you can use this function to increment values inside a loop without invoking a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static mathAdd(num1: number, num2: number): string { + return new JsonPathToken(`States.MathAdd(${[num1, num2].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.StringSplit expression + * + * Use this function to split a string into an array of values. This function takes two arguments.The first argument is a string and the second argument is the delimiting character that the function will use to divide the string. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static stringSplit(inputString: string, splitter: string): string { + return new JsonPathToken(`States.StringSplit(${[inputString, splitter].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.UUID expression + * + * Use this function to return a version 4 universally unique identifier (v4 UUID) generated using random numbers. For example, you can use this function to call other AWS services or resources that need a UUID parameter or insert items in a DynamoDB table. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static uuid(): string { + return new JsonPathToken('States.UUID()').toString(); + } + /** * Make an intrinsic States.Format expression * @@ -298,14 +453,45 @@ export class FieldUtils { } function validateJsonPath(path: string) { + const intrinsicFunctionNames = [ + // Intrinsics for arrays + 'Array', + 'ArrayPartition', + 'ArrayContains', + 'ArrayRange', + 'ArrayGetItem', + 'ArrayLength', + 'ArrayUnique', + // Intrinsics for data encoding and decoding + 'Base64Encode', + 'Base64Decode', + // Intrinsic for hash calculation + 'Hash', + // Intrinsics for JSON data manipulation + 'JsonMerge', + 'StringToJson', + 'JsonToString', + // Intrinsics for Math operations + 'MathRandom', + 'MathAdd', + // Intrinsic for String operation + 'StringSplit', + // Intrinsic for unique identifier generation + 'UUID', + // Intrinsic for generic operation + 'Format', + ]; + const intrinsicFunctionFullNames = intrinsicFunctionNames.map((fn) => `States.${fn}`); if (path !== '$' && !path.startsWith('$.') && path !== '$$' && !path.startsWith('$$.') && !path.startsWith('$[') - && ['Format', 'StringToJson', 'JsonToString', 'Array'].every(fn => !path.startsWith(`States.${fn}`)) + && intrinsicFunctionFullNames.every(fn => !path.startsWith(fn)) ) { - throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.', start with '$[', or start with an intrinsic function: States.Format, States.StringToJson, States.JsonToString, or States.Array. Received: ${path}`); + const lastItem = intrinsicFunctionFullNames.pop(); + const intrinsicFunctionsStr = intrinsicFunctionFullNames.join(', ') + ', or ' + lastItem; + throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.', start with '$[', or start with an intrinsic function: ${intrinsicFunctionsStr}. Received: ${path}`); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts b/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts index 7263da227261f..d6802227c4305 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts @@ -312,17 +312,22 @@ function pathFromToken(token: IResolvable | undefined) { } /** - * Render the string in a valid JSON Path expression. + * Render the string or number value in a valid JSON Path expression. * - * If the string is a Tokenized JSON path reference -- return the JSON path reference inside it. - * Otherwise, single-quote it. + * If the value is a Tokenized JSON path reference -- return the JSON path reference inside it. + * If the value is a number -- convert it to string. + * If the value is a string -- single-quote it. + * Otherwise, throw errors. * * Call this function whenever you're building compound JSONPath expressions, in * order to avoid having tokens-in-tokens-in-tokens which become very hard to parse. */ -export function renderInExpression(x: string) { - const path = jsonPathString(x); - return path ?? singleQuotestring(x); +export function renderInExpression(x: any) { + const path = jsonPathFromAny(x); + if (path) return path; + if (typeof x === 'number') return x.toString(10); + if (typeof x === 'string') return singleQuotestring(x); + throw new Error('Unxexpected value.'); } function singleQuotestring(x: string) { @@ -341,4 +346,4 @@ function singleQuotestring(x: string) { } ret.push("'"); return ret.join(''); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 3acbb95036924..fa3ee095f4187 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -1,7 +1,7 @@ import { FieldUtils, JsonPath, TaskInput } from '../lib'; describe('Fields', () => { - const jsonPathValidationErrorMsg = /exactly '\$', '\$\$', start with '\$.', start with '\$\$.', start with '\$\[', or start with an intrinsic function: States.Format, States.StringToJson, States.JsonToString, or States.Array./; + const jsonPathValidationErrorMsg = /exactly '\$', '\$\$', start with '\$.', start with '\$\$.', start with '\$\[', or start with an intrinsic function: States.Array, States.ArrayPartition, States.ArrayContains, States.ArrayRange, States.ArrayGetItem, States.ArrayLength, States.ArrayUnique, States.Base64Encode, States.Base64Decode, States.Hash, States.JsonMerge, States.StringToJson, States.JsonToString, States.MathRandom, States.MathAdd, States.StringSplit, States.UUID, or States.Format./; test('deep replace correctly handles fields in arrays', () => { expect( @@ -81,6 +81,20 @@ describe('Fields', () => { expect(JsonPath.stringAt('States.StringToJson')).toBeDefined(); expect(JsonPath.stringAt('States.JsonToString')).toBeDefined(); expect(JsonPath.stringAt('States.Array')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayPartition')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayContains')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayRange')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayGetItem')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayLength')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayUnique')).toBeDefined(); + expect(JsonPath.stringAt('States.Base64Encode')).toBeDefined(); + expect(JsonPath.stringAt('States.Base64Decode')).toBeDefined(); + expect(JsonPath.stringAt('States.Hash')).toBeDefined(); + expect(JsonPath.stringAt('States.JsonMerge')).toBeDefined(); + expect(JsonPath.stringAt('States.MathRandom')).toBeDefined(); + expect(JsonPath.stringAt('States.MathAdd')).toBeDefined(); + expect(JsonPath.stringAt('States.StringSplit')).toBeDefined(); + expect(JsonPath.stringAt('States.UUID')).toBeDefined(); expect(() => JsonPath.stringAt('$hello')).toThrowError(jsonPathValidationErrorMsg); expect(() => JsonPath.stringAt('hello')).toThrowError(jsonPathValidationErrorMsg); @@ -229,6 +243,184 @@ describe('intrinsics constructors', () => { }); }); + test('arrayPartition', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4), + })).toEqual({ + 'Field.$': 'States.ArrayPartition($.inputArray, 4)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.chunkSize')), + })).toEqual({ + 'Field.$': 'States.ArrayPartition($.inputArray, $.chunkSize)', + }); + }); + + test('arrayContains', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5), + })).toEqual({ + 'Field.$': 'States.ArrayContains($.inputArray, 5)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 'a'), + })).toEqual({ + 'Field.$': "States.ArrayContains($.inputArray, 'a')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.lookingFor')), + })).toEqual({ + 'Field.$': 'States.ArrayContains($.inputArray, $.lookingFor)', + }); + }); + + test('arrayRange', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayRange(1, 9, 2), + })).toEqual({ + 'Field.$': 'States.ArrayRange(1, 9, 2)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayRange(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end'), JsonPath.numberAt('$.step')), + })).toEqual({ + 'Field.$': 'States.ArrayRange($.start, $.end, $.step)', + }); + }); + + test('arrayGetItem', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5), + })).toEqual({ + 'Field.$': 'States.ArrayGetItem($.inputArray, 5)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayGetItem(JsonPath.numberAt('$.inputArray'), JsonPath.numberAt('$.index')), + })).toEqual({ + 'Field.$': 'States.ArrayGetItem($.inputArray, $.index)', + }); + }); + + test('arrayLength', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayLength(JsonPath.listAt('$.inputArray')), + })).toEqual({ + 'Field.$': 'States.ArrayLength($.inputArray)', + }); + }); + + test('arrayUnique', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayUnique(JsonPath.listAt('$.inputArray')), + })).toEqual({ + 'Field.$': 'States.ArrayUnique($.inputArray)', + }); + }); + + test('base64Encode', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Encode('Data to encode'), + })).toEqual({ + 'Field.$': "States.Base64Encode('Data to encode')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Encode(JsonPath.stringAt('$.input')), + })).toEqual({ + 'Field.$': 'States.Base64Encode($.input)', + }); + }); + + test('base64Decode', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Decode('RGF0YSB0byBlbmNvZGU='), + })).toEqual({ + 'Field.$': "States.Base64Decode('RGF0YSB0byBlbmNvZGU=')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Decode(JsonPath.stringAt('$.base64')), + })).toEqual({ + 'Field.$': 'States.Base64Decode($.base64)', + }); + }); + + test('hash', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.hash('Input data', 'SHA-1'), + })).toEqual({ + 'Field.$': "States.Hash('Input data', 'SHA-1')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm')), + })).toEqual({ + 'Field.$': 'States.Hash($.Data, $.Algorithm)', + }); + }); + + test('jsonMerge', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2')), + })).toEqual({ + 'Field.$': 'States.JsonMerge($.Obj1, $.Obj2, false)', + }); + }); + + test('mathRandom', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.mathRandom(1, 999), + })).toEqual({ + 'Field.$': 'States.MathRandom(1, 999)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.mathRandom(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end')), + })).toEqual({ + 'Field.$': 'States.MathRandom($.start, $.end)', + }); + }); + + test('mathAdd', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.mathAdd(1, 999), + })).toEqual({ + 'Field.$': 'States.MathAdd(1, 999)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step')), + })).toEqual({ + 'Field.$': 'States.MathAdd($.value1, $.step)', + }); + }); + + test('stringSplit', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.stringSplit('1,2,3,4,5', ','), + })).toEqual({ + 'Field.$': "States.StringSplit('1,2,3,4,5', ',')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter')), + })).toEqual({ + 'Field.$': 'States.StringSplit($.inputString, $.splitter)', + }); + }); + + test('uuid', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.uuid(), + })).toEqual({ + 'Field.$': 'States.UUID()', + }); + }); + test('format', () => { expect(FieldUtils.renderObject({ Field: JsonPath.format('Hi my name is {}.', JsonPath.stringAt('$.Name')), @@ -267,4 +459,4 @@ test('find task token even if nested in intrinsic functions', () => { // Even if it's a hand-written literal and doesn't use our constructors expect(FieldUtils.containsTaskToken({ x: JsonPath.stringAt('States.Array($$.Task.Token)') })).toEqual(true); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts b/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts new file mode 100644 index 0000000000000..f07836da4c30e --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts @@ -0,0 +1,56 @@ +import * as cdk from '@aws-cdk/core'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests'; +import { JsonPath, Pass, StateMachine } from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-stepfunctions-intrinsics-integ'); + +const pass = new Pass(stack, 'pass', { + parameters: { + array1: JsonPath.array('asdf', JsonPath.stringAt('$.Id')), + arrayPartition1: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4), + arrayPartition2: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.chunkSize')), + arrayContains1: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5), + arrayContains2: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 'a'), + arrayContains3: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.lookingFor')), + arrayRange1: JsonPath.arrayRange(1, 9, 2), + arrayRange2: JsonPath.arrayRange(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end'), JsonPath.numberAt('$.step')), + arrayGetItem1: JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5), + arrayGetItem2: JsonPath.arrayGetItem(JsonPath.numberAt('$.inputArray'), JsonPath.numberAt('$.index')), + arrayLength1: JsonPath.arrayLength(JsonPath.listAt('$.inputArray')), + arrayUnique1: JsonPath.arrayUnique(JsonPath.listAt('$.inputArray')), + base64Encode1: JsonPath.base64Encode('Data to encode'), + base64Encode2: JsonPath.base64Encode(JsonPath.stringAt('$.input')), + base64Decode1: JsonPath.base64Decode('RGF0YSB0byBlbmNvZGU='), + base64Decode2: JsonPath.base64Decode(JsonPath.stringAt('$.base64')), + hash1: JsonPath.hash('Input data', 'SHA-1'), + hash2: JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm')), + jsonMerge1: JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2')), + mathRandom1: JsonPath.mathRandom(1, 999), + mathRandom2: JsonPath.mathRandom(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end')), + mathAdd1: JsonPath.mathAdd(1, 999), + mathAdd2: JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step')), + stringSplit1: JsonPath.stringSplit('1,2,3,4,5', ','), + stringSplit2: JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter')), + uuid: JsonPath.uuid(), + format1: JsonPath.format('Hi my name is {}.', JsonPath.stringAt('$.Name')), + format2: JsonPath.format(JsonPath.stringAt('$.Format'), JsonPath.stringAt('$.Name')), + stringToJson1: JsonPath.stringToJson(JsonPath.stringAt('$.Str')), + jsonToString1: JsonPath.jsonToString(JsonPath.objectAt('$.Obj')), + }, +}); + +const stateMachine = new StateMachine(stack, 'StateMachine', { + definition: pass, +}); + +const integ = new IntegTest(app, 'StateMachineIntrinsicsTest', { + testCases: [stack], +}); +integ.assertions.awsApiCall('StepFunctions', 'describeStateMachine', { + stateMachineArn: stateMachine.stateMachineArn, +}).expect(ExpectedResult.objectLike({ + status: 'ACTIVE', +})); + +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json new file mode 100644 index 0000000000000..b732375a777a4 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac": { + "source": { + "path": "asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0": { + "source": { + "path": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json new file mode 100644 index 0000000000000..52f6684a436e2 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "AwsApiCallStepFunctionsdescribeStateMachine": { + "Type": "Custom::DeployAssert@SdkCallStepFunctionsdescribeStateMachin", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "StepFunctions", + "api": "describeStateMachine", + "expected": "{\"$ObjectLike\":{\"status\":\"ACTIVE\"}}", + "parameters": { + "stateMachineArn": { + "Fn::ImportValue": "aws-stepfunctions-intrinsics-integ:ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + }, + "flattenResponse": "false", + "salt": "1666195539323" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "states:DescribeStateMachine" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallStepFunctionsdescribeStateMachine": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallStepFunctionsdescribeStateMachine", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js new file mode 100644 index 0000000000000..6bee1ced2a9b7 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js @@ -0,0 +1,767 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json new file mode 100644 index 0000000000000..d2e1e83193b8a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c": { + "source": { + "path": "aws-stepfunctions-intrinsics-integ.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json new file mode 100644 index 0000000000000..4b26dc59ad824 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json @@ -0,0 +1,182 @@ +{ + "Resources": { + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "states" + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"pass\",\"States\":{\"pass\":{\"Type\":\"Pass\",\"Parameters\":{\"array1.$\":\"States.Array('asdf', $.Id)\",\"arrayPartition1.$\":\"States.ArrayPartition($.inputArray, 4)\",\"arrayPartition2.$\":\"States.ArrayPartition($.inputArray, $.chunkSize)\",\"arrayContains1.$\":\"States.ArrayContains($.inputArray, 5)\",\"arrayContains2.$\":\"States.ArrayContains($.inputArray, 'a')\",\"arrayContains3.$\":\"States.ArrayContains($.inputArray, $.lookingFor)\",\"arrayRange1.$\":\"States.ArrayRange(1, 9, 2)\",\"arrayRange2.$\":\"States.ArrayRange($.start, $.end, $.step)\",\"arrayGetItem1.$\":\"States.ArrayGetItem($.inputArray, 5)\",\"arrayGetItem2.$\":\"States.ArrayGetItem($.inputArray, $.index)\",\"arrayLength1.$\":\"States.ArrayLength($.inputArray)\",\"arrayUnique1.$\":\"States.ArrayUnique($.inputArray)\",\"base64Encode1.$\":\"States.Base64Encode('Data to encode')\",\"base64Encode2.$\":\"States.Base64Encode($.input)\",\"base64Decode1.$\":\"States.Base64Decode('RGF0YSB0byBlbmNvZGU=')\",\"base64Decode2.$\":\"States.Base64Decode($.base64)\",\"hash1.$\":\"States.Hash('Input data', 'SHA-1')\",\"hash2.$\":\"States.Hash($.Data, $.Algorithm)\",\"jsonMerge1.$\":\"States.JsonMerge($.Obj1, $.Obj2, false)\",\"mathRandom1.$\":\"States.MathRandom(1, 999)\",\"mathRandom2.$\":\"States.MathRandom($.start, $.end)\",\"mathAdd1.$\":\"States.MathAdd(1, 999)\",\"mathAdd2.$\":\"States.MathAdd($.value1, $.step)\",\"stringSplit1.$\":\"States.StringSplit('1,2,3,4,5', ',')\",\"stringSplit2.$\":\"States.StringSplit($.inputString, $.splitter)\",\"uuid.$\":\"States.UUID()\",\"format1.$\":\"States.Format('Hi my name is {}.', $.Name)\",\"format2.$\":\"States.Format($.Format, $.Name)\",\"stringToJson1.$\":\"States.StringToJson($.Str)\",\"jsonToString1.$\":\"States.JsonToString($.Obj)\"},\"End\":true}}}" + }, + "DependsOn": [ + "StateMachineRoleB840431D" + ] + } + }, + "Mappings": { + "ServiceprincipalMap": { + "af-south-1": { + "states": "states.af-south-1.amazonaws.com" + }, + "ap-east-1": { + "states": "states.ap-east-1.amazonaws.com" + }, + "ap-northeast-1": { + "states": "states.ap-northeast-1.amazonaws.com" + }, + "ap-northeast-2": { + "states": "states.ap-northeast-2.amazonaws.com" + }, + "ap-northeast-3": { + "states": "states.ap-northeast-3.amazonaws.com" + }, + "ap-south-1": { + "states": "states.ap-south-1.amazonaws.com" + }, + "ap-southeast-1": { + "states": "states.ap-southeast-1.amazonaws.com" + }, + "ap-southeast-2": { + "states": "states.ap-southeast-2.amazonaws.com" + }, + "ap-southeast-3": { + "states": "states.ap-southeast-3.amazonaws.com" + }, + "ca-central-1": { + "states": "states.ca-central-1.amazonaws.com" + }, + "cn-north-1": { + "states": "states.cn-north-1.amazonaws.com" + }, + "cn-northwest-1": { + "states": "states.cn-northwest-1.amazonaws.com" + }, + "eu-central-1": { + "states": "states.eu-central-1.amazonaws.com" + }, + "eu-north-1": { + "states": "states.eu-north-1.amazonaws.com" + }, + "eu-south-1": { + "states": "states.eu-south-1.amazonaws.com" + }, + "eu-south-2": { + "states": "states.eu-south-2.amazonaws.com" + }, + "eu-west-1": { + "states": "states.eu-west-1.amazonaws.com" + }, + "eu-west-2": { + "states": "states.eu-west-2.amazonaws.com" + }, + "eu-west-3": { + "states": "states.eu-west-3.amazonaws.com" + }, + "me-south-1": { + "states": "states.me-south-1.amazonaws.com" + }, + "sa-east-1": { + "states": "states.sa-east-1.amazonaws.com" + }, + "us-east-1": { + "states": "states.us-east-1.amazonaws.com" + }, + "us-east-2": { + "states": "states.us-east-2.amazonaws.com" + }, + "us-gov-east-1": { + "states": "states.us-gov-east-1.amazonaws.com" + }, + "us-gov-west-1": { + "states": "states.us-gov-west-1.amazonaws.com" + }, + "us-iso-east-1": { + "states": "states.amazonaws.com" + }, + "us-iso-west-1": { + "states": "states.amazonaws.com" + }, + "us-isob-east-1": { + "states": "states.amazonaws.com" + }, + "us-west-1": { + "states": "states.us-west-1.amazonaws.com" + }, + "us-west-2": { + "states": "states.us-west-2.amazonaws.com" + } + } + }, + "Outputs": { + "ExportsOutputRefStateMachine2E01A3A5BA46F753": { + "Value": { + "Ref": "StateMachine2E01A3A5" + }, + "Export": { + "Name": "aws-stepfunctions-intrinsics-integ:ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json new file mode 100644 index 0000000000000..96a6796634bf0 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "StateMachineIntrinsicsTest/DefaultTest": { + "stacks": [ + "aws-stepfunctions-intrinsics-integ" + ], + "assertionStack": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert", + "assertionStackName": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..610687b57db93 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json @@ -0,0 +1,154 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-stepfunctions-intrinsics-integ.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-stepfunctions-intrinsics-integ.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-stepfunctions-intrinsics-integ": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-stepfunctions-intrinsics-integ.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-stepfunctions-intrinsics-integ.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-stepfunctions-intrinsics-integ.assets" + ], + "metadata": { + "/aws-stepfunctions-intrinsics-integ/StateMachine/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachineRoleB840431D" + } + ], + "/aws-stepfunctions-intrinsics-integ/StateMachine/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2E01A3A5" + } + ], + "/aws-stepfunctions-intrinsics-integ/Service-principalMap": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceprincipalMap" + } + ], + "/aws-stepfunctions-intrinsics-integ/Exports/Output{\"Ref\":\"StateMachine2E01A3A5\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + ], + "/aws-stepfunctions-intrinsics-integ/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-stepfunctions-intrinsics-integ/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-stepfunctions-intrinsics-integ" + }, + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-stepfunctions-intrinsics-integ", + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets" + ], + "metadata": { + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallStepFunctionsdescribeStateMachine" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallStepFunctionsdescribeStateMachine" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json new file mode 100644 index 0000000000000..34c717c5aa1c2 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json @@ -0,0 +1,264 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "aws-stepfunctions-intrinsics-integ": { + "id": "aws-stepfunctions-intrinsics-integ", + "path": "aws-stepfunctions-intrinsics-integ", + "children": { + "pass": { + "id": "pass", + "path": "aws-stepfunctions-intrinsics-integ/pass", + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.Pass", + "version": "0.0.0" + } + }, + "StateMachine": { + "id": "StateMachine", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine", + "children": { + "Role": { + "id": "Role", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "states" + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::StepFunctions::StateMachine", + "aws:cdk:cloudformation:props": { + "roleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "definitionString": "{\"StartAt\":\"pass\",\"States\":{\"pass\":{\"Type\":\"Pass\",\"Parameters\":{\"array1.$\":\"States.Array('asdf', $.Id)\",\"arrayPartition1.$\":\"States.ArrayPartition($.inputArray, 4)\",\"arrayPartition2.$\":\"States.ArrayPartition($.inputArray, $.chunkSize)\",\"arrayContains1.$\":\"States.ArrayContains($.inputArray, 5)\",\"arrayContains2.$\":\"States.ArrayContains($.inputArray, 'a')\",\"arrayContains3.$\":\"States.ArrayContains($.inputArray, $.lookingFor)\",\"arrayRange1.$\":\"States.ArrayRange(1, 9, 2)\",\"arrayRange2.$\":\"States.ArrayRange($.start, $.end, $.step)\",\"arrayGetItem1.$\":\"States.ArrayGetItem($.inputArray, 5)\",\"arrayGetItem2.$\":\"States.ArrayGetItem($.inputArray, $.index)\",\"arrayLength1.$\":\"States.ArrayLength($.inputArray)\",\"arrayUnique1.$\":\"States.ArrayUnique($.inputArray)\",\"base64Encode1.$\":\"States.Base64Encode('Data to encode')\",\"base64Encode2.$\":\"States.Base64Encode($.input)\",\"base64Decode1.$\":\"States.Base64Decode('RGF0YSB0byBlbmNvZGU=')\",\"base64Decode2.$\":\"States.Base64Decode($.base64)\",\"hash1.$\":\"States.Hash('Input data', 'SHA-1')\",\"hash2.$\":\"States.Hash($.Data, $.Algorithm)\",\"jsonMerge1.$\":\"States.JsonMerge($.Obj1, $.Obj2, false)\",\"mathRandom1.$\":\"States.MathRandom(1, 999)\",\"mathRandom2.$\":\"States.MathRandom($.start, $.end)\",\"mathAdd1.$\":\"States.MathAdd(1, 999)\",\"mathAdd2.$\":\"States.MathAdd($.value1, $.step)\",\"stringSplit1.$\":\"States.StringSplit('1,2,3,4,5', ',')\",\"stringSplit2.$\":\"States.StringSplit($.inputString, $.splitter)\",\"uuid.$\":\"States.UUID()\",\"format1.$\":\"States.Format('Hi my name is {}.', $.Name)\",\"format2.$\":\"States.Format($.Format, $.Name)\",\"stringToJson1.$\":\"States.StringToJson($.Str)\",\"jsonToString1.$\":\"States.JsonToString($.Obj)\"},\"End\":true}}}" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.CfnStateMachine", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.StateMachine", + "version": "0.0.0" + } + }, + "Service-principalMap": { + "id": "Service-principalMap", + "path": "aws-stepfunctions-intrinsics-integ/Service-principalMap", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "aws-stepfunctions-intrinsics-integ/Exports", + "children": { + "Output{\"Ref\":\"StateMachine2E01A3A5\"}": { + "id": "Output{\"Ref\":\"StateMachine2E01A3A5\"}", + "path": "aws-stepfunctions-intrinsics-integ/Exports/Output{\"Ref\":\"StateMachine2E01A3A5\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "StateMachineIntrinsicsTest": { + "id": "StateMachineIntrinsicsTest", + "path": "StateMachineIntrinsicsTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "StateMachineIntrinsicsTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert", + "children": { + "AwsApiCallStepFunctionsdescribeStateMachine": { + "id": "AwsApiCallStepFunctionsdescribeStateMachine", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default", + "children": { + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts new file mode 100644 index 0000000000000..20a7b3a2bcec6 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts @@ -0,0 +1,26 @@ +import { JsonPath } from '../../lib'; +import { renderInExpression } from '../../lib/private/json-path'; + +describe('RenderInExpression', () => { + test('simple number', () => { + expect(renderInExpression(1)).toBe('1'); + }); + test('simple string', () => { + expect(renderInExpression('a')).toBe("'a'"); + }); + test('jsonpath stringAt', () => { + expect(renderInExpression(JsonPath.stringAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath numberAt', () => { + expect(renderInExpression(JsonPath.numberAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath listAt', () => { + expect(renderInExpression(JsonPath.listAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath objectAt', () => { + expect(renderInExpression(JsonPath.objectAt('$.Field'))).toBe('$.Field'); + }); + test('raw array', () => { + expect(() => renderInExpression([1, 2])).toThrow(); + }); +}); diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index 0f68a385d998c..09c771a1e5215 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -42,7 +42,7 @@ const canary = new synthetics.Canary(this, 'MyCanary', { code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), handler: 'index.handler', }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, environmentVariables: { stage: 'prod', }, @@ -129,7 +129,7 @@ new synthetics.Canary(this, 'Inline Canary', { code: synthetics.Code.fromInline('/* Synthetics handler code */'), handler: 'index.handler', // must be 'index.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, }); // To supply the code from your local filesystem: @@ -138,7 +138,7 @@ new synthetics.Canary(this, 'Asset Canary', { code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), handler: 'index.handler', // must end with '.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, }); // To supply the code from a S3 bucket: @@ -149,7 +149,7 @@ new synthetics.Canary(this, 'Bucket Canary', { code: synthetics.Code.fromBucket(bucket, 'canary.zip'), handler: 'index.handler', // must end with '.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, }); ``` @@ -188,7 +188,7 @@ new synthetics.Canary(this, 'Vpc Canary', { code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), handler: 'index.handler', }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, vpc, }); ``` diff --git a/packages/@aws-cdk/aws-synthetics/lib/runtime.ts b/packages/@aws-cdk/aws-synthetics/lib/runtime.ts index f6eefc5e8ddde..5e37368d2f80c 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/runtime.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/runtime.ts @@ -150,10 +150,27 @@ export class Runtime { * - Puppeteer-core version 10.1.0 * - Chromium version 92.0.4512 * + * New Features: + * - **Logging enhancement**: The canary will upload logs to Amazon S3 even if it times out or crashes. + * - **Lambda layer size reduced**: The size of the Lambda layer used for canaries is reduced by 34%. + * * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.7 */ public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_7 = new Runtime('syn-nodejs-puppeteer-3.7', RuntimeFamily.NODEJS); + /** + * `syn-nodejs-puppeteer-3.8` includes the following: + * - Lambda runtime Node.js 14.x + * - Puppeteer-core version 10.1.0 + * - Chromium version 92.0.4512 + * + * New Features: + * - **Profile cleanup**: Chromium profiles are now cleaned up after each canary run. + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.8 + */ + public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_8 = new Runtime('syn-nodejs-puppeteer-3.8', RuntimeFamily.NODEJS); + /** * `syn-python-selenium-1.0` includes the following: * - Lambda runtime Python 3.8 diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.assets.json b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.assets.json index ca535da2aecc1..63cb882a1205c 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.assets.json +++ b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.assets.json @@ -27,7 +27,7 @@ } } }, - "38dcc41263b1a009e2d7560d81dee9041f4f7941b66cd73b63df402a6fe3c22d": { + "32cb6cc1550e67ae5e77eb85a64192d2aa44c328b778079b139e3965fb042575": { "source": { "path": "canary-one.template.json", "packaging": "file" @@ -35,7 +35,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "38dcc41263b1a009e2d7560d81dee9041f4f7941b66cd73b63df402a6fe3c22d.json", + "objectKey": "32cb6cc1550e67ae5e77eb85a64192d2aa44c328b778079b139e3965fb042575.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.template.json b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.template.json index c4a1a1232eeb4..04fff7008b44f 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.template.json +++ b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/canary-one.template.json @@ -925,6 +925,203 @@ "StartCanaryAfterCreation": true } }, + "MyCanaryRuntime38ArtifactsBucket66BD74F0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyCanaryRuntime38ArtifactsBucketPolicy9D7ABC32": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "MyCanaryRuntime38ArtifactsBucket66BD74F0" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "MyCanaryRuntime38ServiceRole9FE5290C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/cwsyn-*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyCanaryRuntime388C091D7C": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyCanaryRuntime38ArtifactsBucket66BD74F0" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820.zip" + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryRuntime38ServiceRole9FE5290C", + "Arn" + ] + }, + "Name": "assetcanary-five", + "RuntimeVersion": "syn-nodejs-puppeteer-3.8", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + }, "MyPythonCanaryArtifactsBucket7AE88133": { "Type": "AWS::S3::Bucket", "Properties": { diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/manifest.json index 346dbfd1f3265..1046b3ff32e7a 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/38dcc41263b1a009e2d7560d81dee9041f4f7941b66cd73b63df402a6fe3c22d.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/32cb6cc1550e67ae5e77eb85a64192d2aa44c328b778079b139e3965fb042575.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -153,6 +153,30 @@ "data": "MyCanaryFour15095F40" } ], + "/canary-one/MyCanaryRuntime38/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCanaryRuntime38ArtifactsBucket66BD74F0" + } + ], + "/canary-one/MyCanaryRuntime38/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCanaryRuntime38ArtifactsBucketPolicy9D7ABC32" + } + ], + "/canary-one/MyCanaryRuntime38/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCanaryRuntime38ServiceRole9FE5290C" + } + ], + "/canary-one/MyCanaryRuntime38/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCanaryRuntime388C091D7C" + } + ], "/canary-one/MyPythonCanary/ArtifactsBucket/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/tree.json b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/tree.json index 688b06c0583c7..cb9fa9d58d965 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-synthetics/test/canary.integ.snapshot/tree.json @@ -1378,6 +1378,299 @@ "version": "0.0.0" } }, + "MyCanaryRuntime38": { + "id": "MyCanaryRuntime38", + "path": "canary-one/MyCanaryRuntime38", + "children": { + "ArtifactsBucket": { + "id": "ArtifactsBucket", + "path": "canary-one/MyCanaryRuntime38/ArtifactsBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "canary-one/MyCanaryRuntime38/ArtifactsBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "aws:kms" + } + } + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "canary-one/MyCanaryRuntime38/ArtifactsBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "canary-one/MyCanaryRuntime38/ArtifactsBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "MyCanaryRuntime38ArtifactsBucket66BD74F0" + }, + "policyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "ServiceRole": { + "id": "ServiceRole", + "path": "canary-one/MyCanaryRuntime38/ServiceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "canary-one/MyCanaryRuntime38/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "policies": [ + { + "policyName": "canaryPolicy", + "policyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryRuntime38ArtifactsBucket66BD74F0", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/cwsyn-*" + ] + ] + } + } + ], + "Version": "2012-10-17" + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "canary-one/MyCanaryRuntime38/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "canary-one/MyCanaryRuntime38/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "canary-one/MyCanaryRuntime38/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "canary-one/MyCanaryRuntime38/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Synthetics::Canary", + "aws:cdk:cloudformation:props": { + "artifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyCanaryRuntime38ArtifactsBucket66BD74F0" + } + ] + ] + }, + "code": { + "handler": "canary.handler", + "s3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "s3Key": "b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820.zip" + }, + "executionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryRuntime38ServiceRole9FE5290C", + "Arn" + ] + }, + "name": "assetcanary-five", + "runtimeVersion": "syn-nodejs-puppeteer-3.8", + "schedule": { + "durationInSeconds": "0", + "expression": "rate(5 minutes)" + }, + "startCanaryAfterCreation": true + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-synthetics.CfnCanary", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-synthetics.Canary", + "version": "0.0.0" + } + }, "MyPythonCanary": { "id": "MyPythonCanary", "path": "canary-one/MyPythonCanary", diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts index c4dde679acde3..bd5963acf77e6 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts @@ -70,6 +70,15 @@ new synthetics.Canary(stack, 'MyCanaryFour', { runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_7, }); +new synthetics.Canary(stack, 'MyCanaryRuntime38', { + canaryName: 'assetcanary-five', + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary.zip')), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_8, +}); + new synthetics.Canary(stack, 'MyPythonCanary', { canaryName: 'py-canary-integ', test: synthetics.Test.custom({ diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index dff275e6e263a..b22c9eba3ec01 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -136,7 +136,8 @@ Nested stacks also support the use of Docker image and file assets. ## Accessing resources in a different stack You can access resources in a different stack, as long as they are in the -same account and AWS Region. The following example defines the stack `stack1`, +same account and AWS Region (see [next section](#accessing-resources-in-a-different-stack-and-region) for an exception). +The following example defines the stack `stack1`, which defines an Amazon S3 bucket. Then it defines a second stack, `stack2`, which takes the bucket from stack1 as a constructor property. @@ -161,6 +162,56 @@ in the producing stack and an in the consuming stack to transfer that information from one stack to the other. +## Accessing resources in a different stack and region + +> **This feature is currently experimental** + +You can enable the Stack property `crossRegionReferences` +in order to access resources in a different stack _and_ region. With this feature flag +enabled it is possible to do something like creating a CloudFront distribution in `us-east-2` and +an ACM certificate in `us-east-1`. + +```ts +const stack1 = new Stack(app, 'Stack1', { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, +}); +const cert = new acm.Certificate(stack1, 'Cert', { + domainName: '*.example.com', + validation: acm.CertificateValidation.fromDns(route53.PublicHostedZone.fromHostedZoneId(stack1, 'Zone', 'Z0329774B51CGXTDQV3X')), +}); + +const stack2 = new Stack(app, 'Stack2', { + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, +}); +new cloudfront.Distribution(stack2, 'Distribution', { + defaultBehavior: { + origin: new origins.HttpOrigin('example.com'), + }, + domainNames: ['dev.example.com'], + certificate: cert, +}); +``` + +When the AWS CDK determines that the resource is in a different stack _and_ is in a different +region, it will "export" the value by creating a custom resource in the producing stack which +creates SSM Parameters in the consuming region for each exported value. The parameters will be +created with the name '/cdk/exports/${consumingStackName}/${export-name}'. +In order to "import" the exports into the consuming stack a [SSM Dynamic reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-ssm) +is used to reference the SSM parameter which was created. + +In order to mimic strong references, a Custom Resource is also created in the consuming +stack which marks the SSM parameters as being "imported". When a parameter has been successfully +imported, the producing stack cannot update the value. + +See the [adr](https://github.com/aws/aws-cdk/blob/main/packages/@aws-cdk/core/adr/cross-region-stack-references) +for more details on this feature. + ### Removing automatic cross-stack references The automatic references created by CDK when you use resources across stacks diff --git a/packages/@aws-cdk/core/adr/cross-region-stack-references.md b/packages/@aws-cdk/core/adr/cross-region-stack-references.md new file mode 100644 index 0000000000000..306497cb15d3b --- /dev/null +++ b/packages/@aws-cdk/core/adr/cross-region-stack-references.md @@ -0,0 +1,270 @@ +# Cross Region Stack References + +## Status + +accepted + +## Context + +The CDK allows for you to natively (in code) reference resources between stacks. For example: + +```ts +const bucket = new s3.Bucket(stack1, 'Bucket'); +const handler = new lambda.Function(stack2, 'Handler'); +bucket.grantRead(handler); +``` + +Here we have create an S3 bucket in one stack and natively referenced the bucket from a resource +in a different stack. This works because CDK knows that this is a cross stack reference and will create the +appropriate stack exports and imports. In this case it would create an `Export` in `stack1`. + +```json +{ + "Outputs": { + "ExportsOutputFnGetAttBucket83908E77Arn063C8555": { + "Value": { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "Export": { + "Name": "stack1:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + } + } + } +} +``` + +And an "Import" in stack2 + +```json +{ + "Resources": { + "HandlerServiceRoleDefaultPolicyCBD0CC91": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::ImportValue": "stack1:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "stack1:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "HandlerServiceRoleDefaultPolicyCBD0CC91", + "Roles": [ + { + "Ref": "HandlerServiceRoleFCDC14AE" + } + ] + } + } + } +} +``` + +If these stack exist in different regions this is no longer possible. This is due to +an underlying limitation with CloudFormation, namely that Stack Exports/Imports do not +work cross-region. There are some cases where cross region references are _required_ +by the AWS services themselves. A good example of this is AWS CloudFront. CloudFront is a global +service and you can create a CloudFront distribution via CloudFormation in any AWS Region. Other +resources that are used by CloudFront are required to be created in the `us-east-1` region. + +For example, lets say you have an application that you created in `us-east-2`. + +```ts +const appStack = new Stack(app, 'AppStack', { env: { region: 'us-east-2' } }); +const service = new ApplicationLoadBalancedFargateService(appStack, 'Service'); +``` + +If I want to add CloudFront to this application. I can add the distribution in the +same Stack, but if I also want to add a ACM Certificate (why wouldn't I?) it becomes +more difficult. In order to use a ACM Certificate with CloudFront, the certificate +must be created in `us-east-1` regardless of what region you create the CloudFront +distribution from. + +```ts +const appStack = new Stack(app, 'AppStack', { env: { region: 'us-east-2' } }); +const service = new ApplicationLoadBalancedFargateService(appStack, 'Service'); + +// this won't work!!! +const certificate = new acm.Certificate(appStack, 'Cert'); +const distribution = new Distribution(appStack, 'Distribution', { + defaultBehavior: { origin: new LoadBalancerV2Origin(service.loadBalancer) }, + certificate, +}); +``` + +To workaround this issue we have created things like the `DnsValidatedCertificate` construct +which uses custom resources to create the certificate in `us-east-1`. This requires us +to essentially maintain our own `Certificate` resource that maintains feature parity with the +official `AWS::CertificateManager::Certificate` resource. + +Another example is the `aws-cloudfront.experimental` `EdgeFunction` construct. This takes +a different approach to managing cross region resources. Instead of creating the resources with +a custom resource, we instead create a support stack in `us-east-1` which creates the lambda +function. We then use a custom resource to "lookup" the function arn in the Stack that creates the +CloudFront distribution. This is becoming our recommended pattern for creating cross-region +resources, so why not add an officially supported method for doing this. + +## Constraints + +The biggest constraint with implementing a solution is CloudFormation itself. A common +request from CDK users is for the CDK to support [weak +references](https://github.com/aws/aws-cdk-rfcs/issues/82). The reason we have not yet implemented +this feature is that there are good reasons as to why strong references exist and are the only +officially supported method. + +Let's walk through an example to illustrate. Suppose I had a Lambda function that referenced an S3 +bucket in some way (read data, write data, etc). CloudFormation will create a "strong" reference +between these two resources. + +```ts +const bucket = new s3.Bucket(stack1, 'Bucket', { + bucketName: 'mybucket', +}); +const handler = new lambda.Function(stack2, 'Handler', { + environment: { + BUCKET_NAME: bucket.bucketName, + }, +}); +bucket.grantRead(handler); +``` + +If I tried to update the bucket, for example changing the name from `mybucket` to `myNewNameBucket` +CloudFormation will fail the deployment for stack1 and prevent the bucket from being recreated. This +is because it _knows_ that `stack2` is using the bucket. If it allows the bucket to change and the +export to change you could end up in an unrecoverable state for `stack2`. + +```mermaid +sequenceDiagram + Note over Stack1,Stack2: Initial Deployment + activate Stack2 + Note over Stack1: Create export mybucket + Stack2->>+Stack1: Read export mybucket + Note over Stack2: Create Function + deactivate Stack2 + Note over Stack1,Stack2: Second Deployment + activate Stack2 + Note over Stack1: Delete export mybucket + Note over Stack1: Create export myNewNameBucket + Stack2->>+Stack1: Read export myNewNameBucket + deactivate Stack2 + Note over Stack2: Update Function Failed! + Stack2-->>+Stack2: Rollback + activate Stack2 + Stack2->>+Stack1: Read export mybucket + Note right of Stack1: Export doesn't exist! + deactivate Stack2 + Note over Stack2: Stack rollback failed! + Note over Stack1,Stack2: We're stuck!! +``` + +For the CDK to implement it's own concept of references it needs to take this into account. + +### Custom Resources + +This solution utilizes custom resources to manage outputs/imports, and custom resources come with +their own constraints. + +- Custom resources are only executed when the properties change. There is no way to have the + resource execute on every deploy. + +- Custom resources only know about the current update (via `ResourceProperties`) and the previous update + (via `OldResourceProperties`). Custom resources cannot keep track of all prior updates, unless + we were to implement some external state mechanism. + + +## Decision + +The CDK will natively support cross region stack references. + +```ts +const appStack = new Stack(app, 'AppStack', { env: { region: 'us-east-2' } }); +const service = new ApplicationLoadBalancedFargateService(appStack, 'Service'); + +// this will work!!! +const certificate = new acm.Certificate(appStack, 'Cert'); +const distribution = new Distribution(appStack, 'Distribution', { + defaultBehavior: { origin: new LoadBalancerV2Origin(service.loadBalancer) }, + certificate, +}); +``` + +Since it is not natively supported by CloudFormation we will use CloudFormation custom resources to +perform the output/import. This behavior will not be enabled by default and will be controlled by an +optional Stack property. + +```ts +new Stack(app, 'MyStack', { + crossRegionReferences: true, +}); +``` + +### Outputs + +In order to "output" the value from the producing stack, a custom resource will be created in the +producing stack which will create an SSM parameter with a generated name in the consuming region. +For example the name might be `/cdk/exports/stack2/stack1useast1CertRefCert5C9F`. + +To implement strong references the custom resources will be allowed to create new outputs, but will +only be allowed to update/delete existing outputs if the output has _not_ been imported. If it has +been imported the stack update will fail (similar to the behavior of native exports). See +[Imports](#imports) for how the import is performed. + +### Imports + +The consuming stack will then "import" the value via a SSM dynamic reference. This is possible +because we know the name of the SSM parameter that the producing stack creates. This will look +something like `{{resolve:ssm:/cdk/exports/stack2/stack1useast1CertRefCert5C9F}}`. + +A custom resource will also be created in the consuming stack that will be responsible for marking +the SSM parameter as having been "imported". It will do so by adding a tag to the parameter, +something like `aws-cdk:strong-ref=stack2`. If the value is no longer imported by the stack then the +tag will be removed. The producing stack will use the presence of the tag to determine whether or +not the output can be updated/deleted. + +Since the imports for a stack are exported as SSM parameters with the stack name as part of the name +prefix, when the importing stack is deleted it will clean up and remove any SSM parameters under +that prefix. + +## Alternatives + +This solution uses a push model where the producing stack "pushes" the output to the target region. +One alternative that was considered was to use a pull model where the consuming stack would "pull" +the output from the producing region. For example the producing stack could produce a normal +CloudFormation export and then the consuming stack would have a custom resource the reads the +exports. + +This alternative had several limitations: +1. No way to implement strong references. CloudFormation would not know the export is being used and + would allow it to be updated/deleted. +2. The consuming custom resource would need to run every time the stack is deployed. This would + require introducing a salt that would cause a template diff on every deploy (not ideal). + +## Consequences + +If we add support for cross region references we will need to support cross region references going +forward. We will not be tied to this implementation though. diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-reader-handler/index.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-reader-handler/index.ts new file mode 100644 index 0000000000000..4a9535d804e88 --- /dev/null +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-reader-handler/index.ts @@ -0,0 +1,96 @@ +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +import { SSM } from 'aws-sdk'; +import { ExportReaderCRProps, CrossRegionExports } from '../types'; + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + const props: ExportReaderCRProps = event.ResourceProperties.ReaderProps; + const imports: CrossRegionExports = props.imports as CrossRegionExports; + const importNames = Object.keys(imports); + const keyName: string = `aws-cdk:strong-ref:${props.prefix}`; + + const ssm = new SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info('Tagging SSM Parameter imports'); + await addTags(ssm, importNames, keyName); + break; + case 'Update': + const oldProps: ExportReaderCRProps = event.OldResourceProperties.ReaderProps; + const oldExports: CrossRegionExports = oldProps.imports as CrossRegionExports; + const newExports = except(importNames, Object.keys(oldExports)); + const paramsToRelease = except(Object.keys(oldExports), importNames); + console.info('Releasing unused SSM Parameter imports'); + if (Object.keys(paramsToRelease).length > 0) { + await removeTags(ssm, paramsToRelease, keyName); + } + console.info('Tagging new SSM Parameter imports'); + await addTags(ssm, newExports, keyName); + break; + case 'Delete': + console.info('Releasing all SSM Parameter exports by removing tags'); + await removeTags(ssm, importNames, keyName); + return; + } + } catch (e) { + console.error('Error importing cross region stack exports: ', e); + throw e; + } + return { + Data: imports, + }; +}; + +/** + * Add tag to parameters for existing exports + */ +async function addTags(ssm: SSM, parameters: string[], keyName: string): Promise { + await Promise.all(parameters.map(async name => { + try { + return await ssm.addTagsToResource({ + ResourceId: name, + ResourceType: 'Parameter', + Tags: [{ + Key: keyName, + Value: 'true', + }], + }).promise(); + } catch (e) { + throw new Error(`Error importing ${name}: ${e}`); + } + })); +} + +/** + * Remove tags from parameters + */ +async function removeTags(ssm: SSM, parameters: string[], keyName: string): Promise { + await Promise.all(parameters.map(async name => { + try { + return await ssm.removeTagsFromResource({ + TagKeys: [keyName], + ResourceType: 'Parameter', + ResourceId: name, + }).promise(); + } catch (e) { + switch (e.code) { + // if the parameter doesn't exist then there is nothing to release + case 'InvalidResourceId': + return; + default: + throw new Error(`Error releasing import ${name}: ${e}`); + } + } + })); +} + +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + */ +function except(source: string[], filter: string[]): string[] { + return source.filter(key => !filter.includes(key)); +} diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-writer-handler/index.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-writer-handler/index.ts new file mode 100644 index 0000000000000..84d0e4fe679b1 --- /dev/null +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-writer-handler/index.ts @@ -0,0 +1,151 @@ +/*eslint-disable no-console*/ +/* eslint-disable import/no-extraneous-dependencies */ +import { SSM } from 'aws-sdk'; +import { CrossRegionExports, ExportWriterCRProps } from '../types'; + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + const props: ExportWriterCRProps = event.ResourceProperties.WriterProps; + const exports = props.exports as CrossRegionExports; + + const ssm = new SSM({ region: props.region }); + try { + switch (event.RequestType) { + case 'Create': + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await throwIfAnyInUse(ssm, exports); + await putParameters(ssm, exports); + return; + case 'Update': + const oldProps: ExportWriterCRProps = event.OldResourceProperties.WriterProps; + const oldExports = oldProps.exports as CrossRegionExports; + const newExports = except(exports, oldExports); + + // throw an error to fail the deployment if any export value is changing + const changedExports = changed(oldExports, exports); + if (changedExports.length > 0) { + throw new Error('Some exports have changed!\n'+ changedExports.join('\n')); + } + // if we are removing any exports that are in use, then throw an + // error to fail the deployment + const removedExports = except(oldExports, exports); + await throwIfAnyInUse(ssm, removedExports); + // if the ones we are removing are not in use then delete them + await ssm.deleteParameters({ + Names: Object.keys(removedExports), + }).promise(); + + // also throw an error if we are creating a new export that already exists for some reason + await throwIfAnyInUse(ssm, newExports); + console.info(`Creating new SSM Parameter exports in region ${props.region}`); + await putParameters(ssm, newExports); + return; + case 'Delete': + // if any of the exports are currently in use then throw an error to fail + // the stack deletion. + await throwIfAnyInUse(ssm, exports); + // if none are in use then delete all of them + await ssm.deleteParameters({ + Names: Object.keys(exports), + }).promise(); + return; + default: + return; + } + } catch (e) { + console.error('Error processing event: ', e); + throw e; + } +}; + +/** + * Create parameters for existing exports + */ +async function putParameters(ssm: SSM, parameters: CrossRegionExports): Promise { + await Promise.all(Array.from(Object.entries(parameters), ([name, value]) => { + return ssm.putParameter({ + Name: name, + Value: value, + Type: 'String', + }).promise(); + })); +} + +/** + * Query for existing parameters that are in use + */ +async function throwIfAnyInUse(ssm: SSM, parameters: CrossRegionExports): Promise { + const tagResults: Map> = new Map(); + await Promise.all(Object.keys(parameters).map(async (name: string) => { + const result = await isInUse(ssm, name); + if (result.size > 0) { + tagResults.set(name, result); + } + })); + + if (tagResults.size > 0) { + const message: string = Object.entries(tagResults) + .map((result: [string, string[]]) => `${result[0]} is in use by stack(s) ${result[1].join(' ')}`) + .join('\n'); + throw new Error(`Exports cannot be updated: \n${message}`); + } +} + +/** + * Check if a parameter is in use + */ +async function isInUse(ssm: SSM, parameterName: string): Promise> { + const tagResults: Set = new Set(); + try { + const result = await ssm.listTagsForResource({ + ResourceId: parameterName, + ResourceType: 'Parameter', + }).promise(); + result.TagList?.forEach(tag => { + const tagParts = tag.Key.split(':'); + if (tagParts[0] === 'aws-cdk' && tagParts[1] === 'strong-ref') { + tagResults.add(tagParts[2]); + } + }); + } catch (e) { + // an InvalidResourceId means that the parameter doesn't exist + // which we should ignore since that means it's not in use + if (e.code === 'InvalidResourceId') { + return new Set(); + } + throw e; + } + return tagResults; +} + +/** + * Return only the items from source that do not exist in the filter + * + * @param source the source object to perform the filter on + * @param filter filter out items that exist in this object + * @returns any exports that don't exist in the filter + */ +function except(source: CrossRegionExports, filter: CrossRegionExports): CrossRegionExports { + return Object.keys(source) + .filter(key => (!filter.hasOwnProperty(key))) + .reduce((acc: CrossRegionExports, curr: string) => { + acc[curr] = source[curr]; + return acc; + }, {}); +} + +/** + * Return items that exist in both the the old parameters and the new parameters, + * but have different values + * + * @param oldParams the exports that existed previous to this execution + * @param newParams the exports for the current execution + * @returns any parameters that have different values + */ +function changed(oldParams: CrossRegionExports, newParams: CrossRegionExports): string[] { + return Object.keys(oldParams) + .filter(key => (newParams.hasOwnProperty(key) && oldParams[key] !== newParams[key])) + .reduce((acc: string[], curr: string) => { + acc.push(curr); + return acc; + }, []); +} diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-reader-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-reader-provider.ts new file mode 100644 index 0000000000000..76653660c41f3 --- /dev/null +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-reader-provider.ts @@ -0,0 +1,94 @@ +import * as path from 'path'; +import { Construct } from 'constructs'; +import { CfnResource } from '../../cfn-resource'; +import { CustomResource } from '../../custom-resource'; +import { Lazy } from '../../lazy'; +import { Intrinsic } from '../../private/intrinsic'; +import { Stack } from '../../stack'; +import { CustomResourceProvider, CustomResourceProviderRuntime } from '../custom-resource-provider'; +import { SSM_EXPORT_PATH_PREFIX, ExportReaderCRProps, CrossRegionExports } from './types'; + + +/** + * Properties for an ExportReader + */ +export interface ExportReaderProps {} + +/** + * Creates a custom resource that will return a list of stack imports from a given + * The export can then be referenced by the export name. + * + * @internal - this is intentionally not exported from core + */ +export class ExportReader extends Construct { + public static getOrCreate(scope: Construct, uniqueId: string, _props: ExportReaderProps = {}): ExportReader { + const stack = Stack.of(scope); + const existing = stack.node.tryFindChild(uniqueId); + return existing + ? existing as ExportReader + : new ExportReader(stack, uniqueId); + } + + private readonly importParameters: CrossRegionExports = {}; + private readonly customResource: CustomResource; + constructor(scope: Construct, id: string, _props: ExportReaderProps = {}) { + super(scope, id); + const stack = Stack.of(this); + + const resourceType = 'Custom::CrossRegionExportReader'; + const serviceToken = CustomResourceProvider.getOrCreate(this, resourceType, { + codeDirectory: path.join(__dirname, 'cross-region-ssm-reader-handler'), + runtime: CustomResourceProviderRuntime.NODEJS_14_X, + policyStatements: [{ + Effect: 'Allow', + Resource: stack.formatArn({ + service: 'ssm', + resource: 'parameter', + resourceName: `${SSM_EXPORT_PATH_PREFIX}${stack.stackName}/*`, + }), + Action: [ + 'ssm:AddTagsToResource', + 'ssm:RemoveTagsFromResource', + 'ssm:GetParameters', + ], + }], + }); + + const properties: ExportReaderCRProps = { + region: stack.region, + prefix: stack.stackName, + imports: Lazy.any({ produce: () => this.importParameters }), + }; + this.customResource = new CustomResource(this, 'Resource', { + resourceType: resourceType, + serviceToken, + properties: { + ReaderProps: properties, + }, + }); + } + + /** + * This is the only way to add a dependency on a custom resource currently + */ + public addDependency(resource: CfnResource): void { + const customResource = this.customResource.node.tryFindChild('Default'); + if (customResource && CfnResource.isCfnResource(customResource)) { + customResource.addDependsOn(resource); + } + } + + /** + * Register a reference with the writer and returns a CloudFormation Stack export by name + * + * The value will be "exported" via the ExportWriter. It will perform + * the export by creating an SSM parameter in the region that the consuming + * stack is created. + * + * @param exports map of unique name associated with the export to SSM Dynamic reference + */ + public importValue(name: string, value: Intrinsic): Intrinsic { + this.importParameters[name] = value.toString(); + return this.customResource.getAtt(name); + } +} diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-writer-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-writer-provider.ts new file mode 100644 index 0000000000000..a83a7a178c05c --- /dev/null +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/export-writer-provider.ts @@ -0,0 +1,126 @@ +import * as path from 'path'; +import { Construct } from 'constructs'; +import { CfnDynamicReference, CfnDynamicReferenceService } from '../../cfn-dynamic-reference'; +import { CustomResource } from '../../custom-resource'; +import { Lazy } from '../../lazy'; +import { Intrinsic } from '../../private/intrinsic'; +import { makeUniqueId } from '../../private/uniqueid'; +import { Reference } from '../../reference'; +import { Stack } from '../../stack'; +import { CustomResourceProvider, CustomResourceProviderRuntime } from '../custom-resource-provider'; +import { ExportReader } from './export-reader-provider'; +import { CrossRegionExports, SSM_EXPORT_PATH_PREFIX, ExportWriterCRProps } from './types'; + +/** + * Properties for an ExportReader + */ +export interface ExportWriterProps { + /** + * The AWS region to read Stack exports from + * + * @default - the stack region + */ + readonly region?: string; +} + +/** + * Creates a custom resource that will return a list of stack exports from a given + * AWS region. The export can then be referenced by the export name. + * + * + * @example + * declare const app: App; + * const stack1 = new Stack(app, 'East1Stack', { env: { region: 'us-east-1' } }); + * new CfnOutput(stack1, 'Output', { value: 'someValue', exportName: 'someName' }); + * + * const stack2 = new Stack(app, 'East2Stack', { env: { region: 'us-east-2' } }); + * const exportReader = new ExportReader(stack2, 'ExportReader', { region: 'us-east-1' }); + * const anotherResource = new CfnResource(stack2, 'AnotherResource', { + * Parameters: { + * SomeParam: exportReader.importValue('someName'), + * }, + * }); + * + * @internal - this is intentionally not exported from core + */ +export class ExportWriter extends Construct { + public static getOrCreate(scope: Construct, uniqueId: string, props: ExportWriterProps): ExportWriter { + const stack = Stack.of(scope); + const existing = stack.node.tryFindChild(uniqueId); + return existing + ? existing as ExportWriter + : new ExportWriter(stack, uniqueId, { + region: props.region, + }); + } + private readonly _references: CrossRegionExports = {}; + constructor(scope: Construct, id: string, props: ExportWriterProps) { + super(scope, id); + const stack = Stack.of(this); + const region = props.region ?? stack.region; + + const resourceType = 'Custom::CrossRegionExportWriter'; + const serviceToken = CustomResourceProvider.getOrCreate(this, resourceType, { + codeDirectory: path.join(__dirname, 'cross-region-ssm-writer-handler'), + runtime: CustomResourceProviderRuntime.NODEJS_14_X, + policyStatements: [{ + Effect: 'Allow', + Resource: stack.formatArn({ + service: 'ssm', + resource: 'parameter', + region, + resourceName: `${SSM_EXPORT_PATH_PREFIX}*`, + }), + Action: [ + 'ssm:DeleteParameters', + 'ssm:ListTagsForResource', + 'ssm:GetParameters', + 'ssm:PutParameter', + ], + }], + }); + + const properties: ExportWriterCRProps = { + region: region, + exports: Lazy.any({ produce: () => this._references }), + }; + new CustomResource(this, 'Resource', { + resourceType: resourceType, + serviceToken, + properties: { + WriterProps: properties, + }, + }); + } + + /** + * Register a reference with the writer and returns a CloudFormation Stack export by name + * + * The value will be "exported" via the ExportWriter. It will perform + * the export by creating an SSM parameter in the region that the consuming + * stack is created. + * + * @param exportName the unique name associated with the export + * @param reference the value that will be exported + * @returns a reference to the reader custom resource + */ + public exportValue(exportName: string, reference: Reference, importStack: Stack): Intrinsic { + const stack = Stack.of(this); + const parameterName = `/${SSM_EXPORT_PATH_PREFIX}${exportName}`; + + const ref = new CfnDynamicReference(CfnDynamicReferenceService.SSM, parameterName); + + this._references[parameterName] = stack.resolve(reference.toString()); + return this.addToExportReader(parameterName, ref, importStack); + } + + /** + * Add the export to the export reader which is created in the importing stack + */ + private addToExportReader(exportName: string, exportValueRef: Intrinsic, importStack: Stack): Intrinsic { + const readerConstructName = makeUniqueId(['ExportsReader']); + const exportReader = ExportReader.getOrCreate(importStack.nestedStackParent ?? importStack, readerConstructName); + + return exportReader.importValue(exportName, exportValueRef); + } +} diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/types.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/types.ts new file mode 100644 index 0000000000000..01d64b00f06fd --- /dev/null +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/cross-region-export-providers/types.ts @@ -0,0 +1,49 @@ +import { IResolvable } from '../../resolvable'; + +/** + * The SSM parameter prefix that will be used for + * all cross region exports + */ +export const SSM_EXPORT_PATH_PREFIX = 'cdk/exports/'; + +/** + * Map of exportName to export value + */ +export type CrossRegionExports = { [exportName: string]: string }; + +/** + * Properties for the CrossRegionExportReader Custom Resource + */ +export interface ExportReaderCRProps { + /** + * The region that this resource exists in + */ + readonly region: string; + + /** + * An additional prefix to use. This will be appended + * to SSM_EXPORT_PATH_PREFIX. + */ + readonly prefix: string; + + /** + * A list of imports used by this stack. + * Will be a list of parameter names + */ + readonly imports: CrossRegionExports | IResolvable; +} + +/** + * Properties for the CrossRegionExportWriter custom resource + */ +export interface ExportWriterCRProps { + /** + * The region to export the value to + */ + readonly region: string; + + /** + * A list of values to export to the target region + */ + readonly exports: CrossRegionExports | IResolvable; +} diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts index 35d563a83447b..c394ec3959cef 100644 --- a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts @@ -209,7 +209,7 @@ export class CustomResourceProvider extends Construct { } const stagingDirectory = FileSystem.mkdtemp('cdk-custom-resource'); - fse.copySync(props.codeDirectory, stagingDirectory); + fse.copySync(props.codeDirectory, stagingDirectory, { filter: (src, _dest) => !src.endsWith('.ts') }); fs.copyFileSync(ENTRYPOINT_NODEJS_SOURCE, path.join(stagingDirectory, `${ENTRYPOINT_FILENAME}.js`)); const staging = new AssetStaging(this, 'Staging', { @@ -349,4 +349,4 @@ function customResourceProviderRuntimeToString(x: CustomResourceProviderRuntime) case CustomResourceProviderRuntime.NODEJS_16_X: return 'nodejs16.x'; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index 3adb30e677800..039a611b28eb7 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -120,6 +120,7 @@ export class NestedStack extends Stack { env: { account: parentStack.account, region: parentStack.region }, synthesizer: new NestedStackSynthesizer(parentStack.synthesizer), description: props.description, + crossRegionReferences: parentStack._crossRegionReferences, }); this._parentStack = parentStack; diff --git a/packages/@aws-cdk/core/lib/private/refs.ts b/packages/@aws-cdk/core/lib/private/refs.ts index 550af6c39127d..7569907e62d87 100644 --- a/packages/@aws-cdk/core/lib/private/refs.ts +++ b/packages/@aws-cdk/core/lib/private/refs.ts @@ -2,10 +2,12 @@ // CROSS REFERENCES // ---------------------------------------------------- +import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct } from 'constructs'; import { CfnElement } from '../cfn-element'; import { CfnOutput } from '../cfn-output'; import { CfnParameter } from '../cfn-parameter'; +import { ExportWriter } from '../custom-resource-provider/cross-region-export-providers/export-writer-provider'; import { Names } from '../names'; import { Reference } from '../reference'; import { IResolvable } from '../resolvable'; @@ -14,6 +16,7 @@ import { Token, Tokenization } from '../token'; import { CfnReference } from './cfn-reference'; import { Intrinsic } from './intrinsic'; import { findTokens } from './resolve'; +import { makeUniqueId } from './uniqueid'; /** * This is called from the App level to resolve all references defined. Each @@ -33,11 +36,16 @@ export function resolveReferences(scope: IConstruct): void { } } + /** * Resolves the value for `reference` in the context of `consumer`. */ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { const producer = Stack.of(reference.target); + const producerAccount = !Token.isUnresolved(producer.account) ? producer.account : cxapi.UNKNOWN_ACCOUNT; + const producerRegion = !Token.isUnresolved(producer.region) ? producer.region : cxapi.UNKNOWN_REGION; + const consumerAccount = !Token.isUnresolved(consumer.account) ? consumer.account : cxapi.UNKNOWN_ACCOUNT; + const consumerRegion = !Token.isUnresolved(consumer.region) ? consumer.region : cxapi.UNKNOWN_REGION; // produce and consumer stacks are the same, we can just return the value itself. if (producer === consumer) { @@ -49,11 +57,20 @@ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { throw new Error('Cannot reference across apps. Consuming and producing stacks must be defined within the same CDK app.'); } - // unsupported: stacks are not in the same environment - if (producer.environment !== consumer.environment) { + // unsupported: stacks are not in the same account + if (producerAccount !== consumerAccount) { throw new Error( `Stack "${consumer.node.path}" cannot consume a cross reference from stack "${producer.node.path}". ` + - 'Cross stack references are only supported for stacks deployed to the same environment or between nested stacks and their parent stack'); + 'Cross stack references are only supported for stacks deployed to the same account or between nested stacks and their parent stack'); + } + + + // Stacks are in the same account, but different regions + if (producerRegion !== consumerRegion && !consumer._crossRegionReferences) { + throw new Error( + `Stack "${consumer.node.path}" cannot consume a cross reference from stack "${producer.node.path}". ` + + 'Cross stack references are only supported for stacks deployed to the same environment or between nested stacks and their parent stack. ' + + 'Set crossRegionReferences=true to enable cross region references'); } // ---------------------------------------------------------------------- @@ -91,6 +108,18 @@ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { // export/import // ---------------------------------------------------------------------- + // Stacks are in the same account, but different regions + if (producerRegion !== consumerRegion && consumer._crossRegionReferences) { + if (producerRegion === cxapi.UNKNOWN_REGION || consumerRegion === cxapi.UNKNOWN_REGION) { + throw new Error( + `Stack "${consumer.node.path}" cannot consume a cross reference from stack "${producer.node.path}". ` + + 'Cross stack/region references are only supported for stacks with an explicit region defined. '); + } + consumer.addDependency(producer, + `${consumer.node.path} -> ${reference.target.node.path}.${reference.displayName}`); + return createCrossRegionImportValue(reference, consumer); + } + // export the value through a cloudformation "export name" and use an // Fn::ImportValue in the consumption site. @@ -170,6 +199,70 @@ function createImportValue(reference: Reference): Intrinsic { return Tokenization.reverseCompleteString(importExpr) as Intrinsic; } +/** + * Imports a value from another stack in a different region by creating an "Output" with an "ExportName" + * in the producing stack, and a "ExportsReader" custom resource in the consumer stack + * + * Returns a reference to the ExportsReader attribute which contains the exported value + */ +function createCrossRegionImportValue(reference: Reference, importStack: Stack): Intrinsic { + const referenceStack = Stack.of(reference.target); + const exportingStack = referenceStack.nestedStackParent ?? referenceStack; + + // generate an export name + const exportable = getExportable(exportingStack, reference); + const id = JSON.stringify(exportingStack.resolve(exportable)); + const exportName = generateExportName(importStack, reference, id); + if (Token.isUnresolved(exportName)) { + throw new Error(`unresolved token in generated export name: ${JSON.stringify(exportingStack.resolve(exportName))}`); + } + + // get or create the export writer + const writerConstructName = makeUniqueId(['ExportsWriter', importStack.region]); + const exportReader = ExportWriter.getOrCreate(exportingStack, writerConstructName, { + region: importStack.region, + }); + + const exported = exportReader.exportValue(exportName, reference, importStack); + if (importStack.nestedStackParent) { + return createNestedStackParameter(importStack, (exported as CfnReference), exported); + } + return exported; +} + +/** + * Generate a unique physical name for the export + */ +function generateExportName(importStack: Stack, reference: Reference, id: string): string { + const referenceStack = Stack.of(reference.target); + + const components = [ + referenceStack.stackName ?? '', + referenceStack.region, + id, + ]; + const prefix = `${importStack.nestedStackParent?.stackName ?? importStack.stackName}/`; + const localPart = makeUniqueId(components); + // max name length for a system manager parameter is 1011 characters + // including the arn, i.e. + // arn:aws:ssm:us-east-2:111122223333:parameter/cdk/exports/${stackName}/${name} + const maxLength = 900; + return prefix + localPart.slice(Math.max(0, localPart.length - maxLength + prefix.length)); +} + +export function getExportable(stack: Stack, reference: Reference): Reference { + // could potentially be changed by a call to overrideLogicalId. This would cause our Export/Import + // to have an incorrect id. For a better user experience, lock the logicalId and throw an error + // if the user tries to override the id _after_ calling exportValue + if (CfnElement.isCfnElement(reference.target)) { + reference.target._lockLogicalId(); + } + + // "teleport" the value here, in case it comes from a nested stack. This will also + // ensure the value is from our own scope. + return referenceNestedStackValueInParent(reference, stack); +} + // ------------------------------------------------------------------------------------------------ // nested stacks // ------------------------------------------------------------------------------------------------ diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index 9d407f4030ef2..2135f5ccf1083 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -256,7 +256,9 @@ export abstract class Resource extends Construct implements IResource { produce: (context: IResolveContext) => { const consumingStack = Stack.of(context.scope); - if (this.stack.environment !== consumingStack.environment) { + if (this.stack.account !== consumingStack.account || + (this.stack.region !== consumingStack.region && + !consumingStack._crossRegionReferences)) { this._enableCrossEnvironment(); return this.physicalName; } else { @@ -287,7 +289,9 @@ export abstract class Resource extends Construct implements IResource { return mimicReference(arnAttr, { produce: (context: IResolveContext) => { const consumingStack = Stack.of(context.scope); - if (this.stack.environment !== consumingStack.environment) { + if (this.stack.account !== consumingStack.account || + (this.stack.region !== consumingStack.region && + !consumingStack._crossRegionReferences)) { this._enableCrossEnvironment(); return this.stack.formatArn(arnComponents); } else { diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 7f8e3e9a898c6..744da3288649c 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -139,6 +139,18 @@ export interface StackProps { * 'aws:cdk:version-reporting' context key */ readonly analyticsReporting?: boolean; + + /** + * Enable this flag to allow native cross region stack references. + * + * Enabling this will create a CloudFormation custom resource + * in both the producing stack and consuming stack in order to perform the export/import + * + * This feature is currently experimental + * + * @default false + */ + readonly crossRegionReferences?: boolean; } /** @@ -300,6 +312,13 @@ export class Stack extends Construct implements ITaggable { */ public readonly _versionReportingEnabled: boolean; + /** + * Whether cross region references are enabled for this stack + * + * @internal + */ + public readonly _crossRegionReferences: boolean; + /** * Logical ID generation strategy */ @@ -344,6 +363,7 @@ export class Stack extends Construct implements ITaggable { this._missingContext = new Array(); this._stackDependencies = { }; this.templateOptions = { }; + this._crossRegionReferences = !!props.crossRegionReferences; Object.defineProperty(this, STACK_SYMBOL, { value: true }); @@ -920,17 +940,9 @@ export class Stack extends Construct implements ITaggable { throw new Error('exportValue: either supply \'name\' or make sure to export a resource attribute (like \'bucket.bucketName\')'); } - // if exportValue is being called manually (which is pre onPrepare) then the logicalId - // could potentially be changed by a call to overrideLogicalId. This would cause our Export/Import - // to have an incorrect id. For a better user experience, lock the logicalId and throw an error - // if the user tries to override the id _after_ calling exportValue - if (CfnElement.isCfnElement(resolvable.target)) { - resolvable.target._lockLogicalId(); - } - // "teleport" the value here, in case it comes from a nested stack. This will also // ensure the value is from our own scope. - const exportable = referenceNestedStackValueInParent(resolvable, this); + const exportable = getExportable(this, resolvable); // Ensure a singleton "Exports" scoping Construct // This mostly exists to trigger LogicalID munging, which would be @@ -1402,7 +1414,7 @@ import { DefaultStackSynthesizer, IStackSynthesizer, ISynthesisSession, LegacySt import { Stage } from './stage'; import { ITaggable, TagManager } from './tag-manager'; import { Token, Tokenization } from './token'; -import { referenceNestedStackValueInParent } from './private/refs'; +import { getExportable } from './private/refs'; import { Fact, RegionInfo } from '@aws-cdk/region-info'; import { deployTimeLookup } from './private/region-lookup'; import { makeUniqueResourceName } from './private/unique-resource-name'; diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 7af9004bb4087..e048a1561961b 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -189,6 +189,7 @@ "@types/jest": "^27.5.2", "@types/lodash": "^4.14.186", "@types/minimatch": "^3.0.5", + "aws-sdk": "^2.928.0", "@types/node": "^14.18.32", "@types/sinon": "^9.0.11", "fast-check": "^2.25.0", diff --git a/packages/@aws-cdk/core/rosetta/default.ts-fixture b/packages/@aws-cdk/core/rosetta/default.ts-fixture index 23d992a8629a0..cc57d5981d90c 100644 --- a/packages/@aws-cdk/core/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/core/rosetta/default.ts-fixture @@ -1,4 +1,8 @@ import * as cfn from '@aws-cdk/aws-cloudformation'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as origins from '@aws-cdk/aws-cloudfront-origins'; import * as customresources from '@aws-cdk/custom-resources'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; diff --git a/packages/@aws-cdk/core/test/cross-environment-token.test.ts b/packages/@aws-cdk/core/test/cross-environment-token.test.ts index a84da4714ba41..7270d94822b50 100644 --- a/packages/@aws-cdk/core/test/cross-environment-token.test.ts +++ b/packages/@aws-cdk/core/test/cross-environment-token.test.ts @@ -186,6 +186,99 @@ describe('cross environment', () => { /Cannot use resource 'Stack1\/MyResource' in a cross-environment fashion/); }); + test('can reference a deploy-time physical name across regions, when crossRegionReferences=true', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { + env: { + account: '123456789012', + region: 'bermuda-triangle-1337', + }, + crossRegionReferences: true, + }); + const stack2 = new Stack(app, 'Stack2', { + env: { + account: '123456789012', + region: 'bermuda-triangle-42', + }, + crossRegionReferences: true, + }); + + // WHEN + const myResource = new MyResource(stack1, 'MyResource'); + new CfnOutput(stack2, 'Output', { + value: myResource.name, + }); + + // THEN + const assembly = app.synth(); + const template1 = assembly.getStackByName(stack1.stackName).template; + const template2 = assembly.getStackByName(stack2.stackName).template; + + expect(template1?.Resources).toMatchObject({ + 'ExportsWriterbermudatriangle42E59594276156AC73': { + 'DeletionPolicy': 'Delete', + 'Properties': { + 'WriterProps': { + 'exports': { + '/cdk/exports/Stack2/Stack1bermudatriangle1337RefMyResource6073B41F66B72887': { + 'Ref': 'MyResource6073B41F', + }, + }, + 'region': 'bermuda-triangle-42', + }, + 'ServiceToken': { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + 'Type': 'Custom::CrossRegionExportWriter', + 'UpdateReplacePolicy': 'Delete', + }, + }); + expect(template2?.Outputs).toEqual({ + 'Output': { + 'Value': { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1bermudatriangle1337RefMyResource6073B41F66B72887', + ], + }, + }, + }); + }); + + test('cannot reference a deploy-time physical name across regions, when crossRegionReferences=false', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { + env: { + account: '123456789012', + region: 'bermuda-triangle-1337', + }, + crossRegionReferences: true, + }); + const stack2 = new Stack(app, 'Stack2', { + env: { + account: '123456789012', + region: 'bermuda-triangle-42', + }, + crossRegionReferences: false, + }); + + // WHEN + const myResource = new MyResource(stack1, 'MyResource'); + new CfnOutput(stack2, 'Output', { + value: myResource.name, + }); + + // THEN + expect(() => toCloudFormation(stack2)).toThrow( + /Cannot use resource 'Stack1\/MyResource' in a cross-environment fashion/); + }); + test('cross environment when stack is a substack', () => { const app = new App(); diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-reader-handler.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-reader-handler.test.ts new file mode 100644 index 0000000000000..cdd021a54a9f6 --- /dev/null +++ b/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-reader-handler.test.ts @@ -0,0 +1,217 @@ +import { handler } from '../../lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-reader-handler'; +import { SSM_EXPORT_PATH_PREFIX } from '../../lib/custom-resource-provider/cross-region-export-providers/types'; + +let mockDeleteParameters: jest.Mock ; +let mockAddTagsToResource: jest.Mock; +let mockGetParametersByPath: jest.Mock; +let mockRemoveTagsFromResource: jest.Mock; +jest.mock('aws-sdk', () => { + return { + SSM: jest.fn(() => { + return { + addTagsToResource: jest.fn((params) => { + return { + promise: () => mockAddTagsToResource(params), + }; + }), + removeTagsFromResource: jest.fn((params) => { + return { + promise: () => mockRemoveTagsFromResource(params), + }; + }), + getParametersByPath: jest.fn((params) => { + return { + promise: () => mockGetParametersByPath(params), + }; + }), + }; + }), + }; +}); +beforeEach(() => { + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'error').mockImplementation(() => {}); + mockDeleteParameters = jest.fn(); + mockGetParametersByPath = jest.fn(); + mockRemoveTagsFromResource = jest.fn().mockImplementation(() => { return {}; }); + mockAddTagsToResource = jest.fn().mockImplementation(() => { + return {}; + }); +}); +afterEach(() => { + jest.restoreAllMocks(); +}); + +describe('cross-region-ssm-reader entrypoint', () => { + test('Create event', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Create', + ResourceProperties: { + ReaderProps: { + region: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/MyExport': 'abc', + }, + }, + ServiceToken: '', + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockAddTagsToResource).toHaveBeenCalledWith({ + ResourceId: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + ResourceType: 'Parameter', + Tags: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }); + expect(mockDeleteParameters).toHaveBeenCalledTimes(0); + }); + + test('Update event', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ReaderProps: { + region: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/ExistingExport': 'abc', + }, + }, + ServiceToken: '', + }, + ResourceProperties: { + ReaderProps: { + r: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/ExistingExport': 'abc', + '/cdk/exports/MyStack/MyExport': 'xyz', + }, + }, + ServiceToken: '', + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockAddTagsToResource).toHaveBeenCalledWith({ + ResourceId: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + ResourceType: 'Parameter', + Tags: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }); + expect(mockDeleteParameters).toHaveBeenCalledTimes(0); + expect(mockRemoveTagsFromResource).toHaveBeenCalledTimes(0); + expect(mockGetParametersByPath).toHaveBeenCalledTimes(0); + }); + + test('Update event with export removal', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ReaderProps: { + region: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/RemovedExport': 'abc', + }, + }, + ServiceToken: '', + }, + ResourceProperties: { + ServiceToken: '', + ReaderProps: { + region: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/MyExport': 'abc', + }, + }, + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockAddTagsToResource).toHaveBeenCalledWith({ + ResourceId: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + ResourceType: 'Parameter', + Tags: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }); + expect(mockRemoveTagsFromResource).toHaveBeenCalledWith({ + ResourceId: '/cdk/exports/MyStack/RemovedExport', + ResourceType: 'Parameter', + TagKeys: ['aws-cdk:strong-ref:MyStack'], + }); + expect(mockDeleteParameters).toHaveBeenCalledTimes(0); + }); + + test('Delete event', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: '', + ReaderProps: { + region: 'us-east-1', + prefix: 'MyStack', + imports: { + '/cdk/exports/MyStack/RemovedExport': 'abc', + }, + }, + }, + }); + + // WHEN + mockGetParametersByPath.mockImplementationOnce(() => { + return Promise.resolve({ + Parameters: [{ + Name: '/cdk/exports/MyStack/OtherExport', + }], + }); + }); + await handler(event); + + // THEN + expect(mockRemoveTagsFromResource).toHaveBeenCalledTimes(1); + expect(mockRemoveTagsFromResource).toHaveBeenCalledWith({ + ResourceType: 'Parameter', + ResourceId: '/cdk/exports/MyStack/RemovedExport', + TagKeys: ['aws-cdk:strong-ref:MyStack'], + }); + }); +}); + +function makeEvent(req: Partial): AWSLambda.CloudFormationCustomResourceEvent { + return { + LogicalResourceId: '', + RequestId: '', + ResourceType: '', + ResponseURL: '', + ServiceToken: '', + StackId: '', + ResourceProperties: { + ServiceToken: '', + ...req.ResourceProperties, + }, + ...req, + } as any; +} diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-writer-handler.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-writer-handler.test.ts new file mode 100644 index 0000000000000..17ee6b3c26a31 --- /dev/null +++ b/packages/@aws-cdk/core/test/custom-resource-provider/cross-region-ssm-writer-handler.test.ts @@ -0,0 +1,423 @@ +import { handler } from '../../lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-writer-handler'; +import { SSM_EXPORT_PATH_PREFIX } from '../../lib/custom-resource-provider/cross-region-export-providers/types'; + +let mockPutParameter: jest.Mock ; +let mocklistTagsForResource: jest.Mock; +let mockDeleteParameters: jest.Mock; +jest.mock('aws-sdk', () => { + return { + SSM: jest.fn(() => { + return { + putParameter: jest.fn((params) => { + return { + promise: () => mockPutParameter(params), + }; + }), + listTagsForResource: jest.fn((params) => { + return { + promise: () => mocklistTagsForResource(params), + }; + }), + deleteParameters: jest.fn((params) => { + return { + promise: () => mockDeleteParameters(params), + }; + }), + }; + }), + }; +}); +beforeEach(() => { + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'error').mockImplementation(() => {}); + mockPutParameter = jest.fn(); + mockDeleteParameters = jest.fn().mockImplementation(() => { + return {}; + }); + mocklistTagsForResource = jest.fn().mockImplementation(() => { + return {}; + }); + mockPutParameter.mockImplementation(() => { + return {}; + }); +}); +afterEach(() => { + jest.restoreAllMocks(); +}); + +describe('cross-region-ssm-writer throws', () => { + +}); + +describe('cross-region-ssm-writer entrypoint', () => { + describe('create events', () => { + test('Create event', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Create', + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockPutParameter).toHaveBeenCalledWith({ + Name: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + Value: 'Value', + Type: 'String', + }); + expect(mockPutParameter).toHaveBeenCalledTimes(1); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(1); + }); + + test('create throws if params already exist', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Create', + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockImplementation(() => { + return { + TagList: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }; + }); + + // THEN + await expect(handler(event)).rejects.toThrow(/Exports cannot be updated/); + }); + + test('Create event does not throw for new parameters', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Create', + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockRejectedValue({ + code: 'InvalidResourceId', + }); + await handler(event); + + // THEN + expect(mockPutParameter).toHaveBeenCalledWith({ + Name: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + Value: 'Value', + Type: 'String', + }); + expect(mockPutParameter).toHaveBeenCalledTimes(1); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(1); + }); + }); + + describe('Update events', () => { + test('new export added', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/ExistingExport': 'MyExistingValue', + }, + }, + }, + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/ExistingExport': 'MyExistingValue', + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockPutParameter).toHaveBeenCalledWith({ + Name: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + Value: 'Value', + Type: 'String', + }); + expect(mockPutParameter).toHaveBeenCalledTimes(1); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(1); + }); + + test('removed exports are deleted', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/ExistingExport': 'MyExistingValue', + '/cdk/exports/MyStack/RemovedExport': 'MyExistingValue', + }, + }, + }, + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/ExistingExport': 'MyExistingValue', + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockPutParameter).toHaveBeenCalledWith({ + Name: `/${SSM_EXPORT_PATH_PREFIX}MyStack/MyExport`, + Value: 'Value', + Type: 'String', + }); + expect(mockPutParameter).toHaveBeenCalledTimes(1); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(2); + expect(mockDeleteParameters).toHaveBeenCalledTimes(1); + expect(mockDeleteParameters).toHaveBeenCalledWith({ + Names: ['/cdk/exports/MyStack/RemovedExport'], + }); + }); + + test('update throws if params already exist', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + '/cdk/exports/MyStack/AlreadyExists': 'Value', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockImplementation(() => { + return { + TagList: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }; + }); + + // THEN + await expect(handler(event)).rejects.toThrow(/Exports cannot be updated/); + }); + + test('update throws if value changes for existing parameter', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + '/cdk/exports/MyStack/AlreadyExists': 'Original', + }, + }, + }, + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + '/cdk/exports/MyStack/AlreadyExists': 'NewValue', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockImplementation((params) => { + expect(params).toEqual({ + ResourceId: '/cdk/exports/MyStack/AlreadyExists', + ResourceType: 'Parameter', + }); + return { + TagList: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }; + }); + + // THEN + await expect(handler(event)).rejects.toThrow(/Some exports have changed/); + }); + + test('update throws if in use param is deleted', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/MyExport': 'Value', + }, + }, + }, + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/AlreadyExists': 'Value', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockImplementation(() => { + return { + TagList: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }; + }); + + // THEN + await expect(handler(event)).rejects.toThrow(/Exports cannot be updated/); + }); + }); + + describe('delete events', () => { + test('parameters are deleted', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/RemovedExport': 'RemovedValue', + }, + }, + }, + }); + + // WHEN + await handler(event); + + // THEN + expect(mockPutParameter).toHaveBeenCalledTimes(0); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(1); + expect(mockDeleteParameters).toHaveBeenCalledTimes(1); + expect(mockDeleteParameters).toHaveBeenCalledWith({ + Names: ['/cdk/exports/MyStack/RemovedExport'], + }); + }); + + test('thorws if parameters are in use', async () => { + // GIVEN + const event = makeEvent({ + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: '', + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyStack/RemovedExport': 'RemovedValue', + }, + }, + }, + }); + + // WHEN + mocklistTagsForResource.mockImplementation(() => { + return { + TagList: [{ + Key: 'aws-cdk:strong-ref:MyStack', + Value: 'true', + }], + }; + }); + + // THEN + await expect(handler(event)).rejects.toThrow(/Exports cannot be updated/); + expect(mockPutParameter).toHaveBeenCalledTimes(0); + expect(mocklistTagsForResource).toHaveBeenCalledTimes(1); + expect(mockDeleteParameters).toHaveBeenCalledTimes(0); + }); + }); +}); + +function makeEvent(req: Partial): AWSLambda.CloudFormationCustomResourceEvent { + return { + LogicalResourceId: '', + RequestId: '', + ResourceType: '', + ResponseURL: '', + ServiceToken: '', + StackId: '', + ResourceProperties: { + ServiceToken: '', + ...req.ResourceProperties, + }, + ...req, + } as any; +} diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/export-writer-provider.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/export-writer-provider.test.ts new file mode 100644 index 0000000000000..90f71197929a0 --- /dev/null +++ b/packages/@aws-cdk/core/test/custom-resource-provider/export-writer-provider.test.ts @@ -0,0 +1,515 @@ +import { App, Stack, AssetStaging, CfnResource, NestedStack } from '../../lib'; +import { ExportWriter } from '../../lib/custom-resource-provider/cross-region-export-providers/export-writer-provider'; +import { toCloudFormation } from '../util'; + + +describe('export writer provider', () => { + test('basic configuration', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const resource = new CfnResource(stack, 'MyResource', { + type: 'Custom::MyResource', + }); + + // WHEN + const exportWriter = new ExportWriter(stack, 'ExportWriter', { + region: 'us-east-1', + }); + const exportValue = exportWriter.exportValue('MyResourceName', resource.getAtt('arn'), stack2); + + // THEN + const cfn = toCloudFormation(stack); + const stack2Cfn = toCloudFormation(stack2); + const staging = stack.node.tryFindChild('Custom::CrossRegionExportWriterCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; + const assetHash = staging.assetHash; + + expect(stack.resolve(exportValue)).toEqual({ + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/MyResourceName', + ], + }); + expect(cfn).toEqual({ + Resources: { + MyResource: { + Type: 'Custom::MyResource', + }, + CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + }, + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssm:DeleteParameters', + 'ssm:ListTagsForResource', + 'ssm:GetParameters', + 'ssm:PutParameter', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:us-east-1:', + { + Ref: 'AWS::AccountId', + }, + ':parameter/cdk/exports/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Inline', + }, + ], + ManagedPolicyArns: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + }, + ], + }, + }, + ExportWriterA770449C: { + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyResourceName': { + 'Fn::GetAtt': [ + 'MyResource', + 'arn', + ], + }, + }, + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportWriter', + UpdateReplacePolicy: 'Delete', + }, + CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', + }, + S3Key: `${assetHash}.zip`, + }, + Timeout: 900, + MemorySize: 128, + Handler: '__entrypoint__.handler', + Role: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1', + 'Arn', + ], + }, + Runtime: 'nodejs14.x', + }, + DependsOn: [ + 'CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1', + ], + }, + }, + }); + expect(stack2Cfn).toEqual({ + Resources: { + CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68: { + DependsOn: [ + 'CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD', + ], + Properties: { + Code: { + S3Bucket: { + 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', + }, + S3Key: expect.any(String), + }, + Handler: '__entrypoint__.handler', + MemorySize: 128, + Role: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD', + 'Arn', + ], + }, + Runtime: 'nodejs14.x', + Timeout: 900, + }, + Type: 'AWS::Lambda::Function', + }, + CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD: { + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + }, + ], + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssm:AddTagsToResource', + 'ssm:RemoveTagsFromResource', + 'ssm:GetParameters', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/cdk/exports/Stack2/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Inline', + }, + ], + }, + Type: 'AWS::IAM::Role', + }, + ExportsReader8B249524: { + DeletionPolicy: 'Delete', + Properties: { + ReaderProps: { + imports: { + '/cdk/exports/MyResourceName': '{{resolve:ssm:/cdk/exports/MyResourceName}}', + }, + region: { + Ref: 'AWS::Region', + }, + prefix: 'Stack2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportReader', + UpdateReplacePolicy: 'Delete', + }, + }, + }); + }); + + test('when consumer is a nested stack, ExportReader is created in the parent stack', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const nested2 = new NestedStack(stack2, 'Nested1'); + const resource = new CfnResource(stack, 'MyResource', { + type: 'Custom::MyResource', + }); + + // WHEN + const exportWriter = new ExportWriter(stack, 'ExportWriter', { + region: 'us-east-1', + }); + const exportValue = exportWriter.exportValue('MyResourceName', resource.getAtt('arn'), nested2); + + // THEN + const cfn = toCloudFormation(stack); + const stack2Cfn = toCloudFormation(stack2); + const staging = stack.node.tryFindChild('Custom::CrossRegionExportWriterCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; + const assetHash = staging.assetHash; + + expect(stack.resolve(exportValue)).toEqual({ + 'Fn::GetAtt': ['ExportsReader8B249524', '/cdk/exports/MyResourceName'], + }); + expect(cfn).toEqual({ + Resources: { + MyResource: { + Type: 'Custom::MyResource', + }, + CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + }, + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssm:DeleteParameters', + 'ssm:ListTagsForResource', + 'ssm:GetParameters', + 'ssm:PutParameter', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:us-east-1:', + { + Ref: 'AWS::AccountId', + }, + ':parameter/cdk/exports/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Inline', + }, + ], + ManagedPolicyArns: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + }, + ], + }, + }, + ExportWriterA770449C: { + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + region: 'us-east-1', + exports: { + '/cdk/exports/MyResourceName': { + 'Fn::GetAtt': [ + 'MyResource', + 'arn', + ], + }, + }, + + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportWriter', + UpdateReplacePolicy: 'Delete', + }, + CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', + }, + S3Key: `${assetHash}.zip`, + }, + Timeout: 900, + MemorySize: 128, + Handler: '__entrypoint__.handler', + Role: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1', + 'Arn', + ], + }, + Runtime: 'nodejs14.x', + }, + DependsOn: [ + 'CustomCrossRegionExportWriterCustomResourceProviderRoleC951B1E1', + ], + }, + }, + }); + expect(stack2Cfn).toEqual({ + Resources: { + CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68: { + DependsOn: [ + 'CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD', + ], + Properties: { + Code: { + S3Bucket: { + 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', + }, + S3Key: expect.any(String), + }, + Handler: '__entrypoint__.handler', + MemorySize: 128, + Role: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD', + 'Arn', + ], + }, + Runtime: 'nodejs14.x', + Timeout: 900, + }, + Type: 'AWS::Lambda::Function', + }, + CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD: { + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + }, + ], + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssm:AddTagsToResource', + 'ssm:RemoveTagsFromResource', + 'ssm:GetParameters', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/cdk/exports/Stack2/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Inline', + }, + ], + }, + Type: 'AWS::IAM::Role', + }, + ExportsReader8B249524: { + DeletionPolicy: 'Delete', + Properties: { + ReaderProps: { + imports: { + '/cdk/exports/MyResourceName': '{{resolve:ssm:/cdk/exports/MyResourceName}}', + }, + region: { + Ref: 'AWS::Region', + }, + prefix: 'Stack2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportReader', + UpdateReplacePolicy: 'Delete', + }, + Nested1NestedStackNested1NestedStackResourceCD0AD36B: { + DeletionPolicy: 'Delete', + Properties: { + TemplateURL: '', + }, + Type: 'AWS::CloudFormation::Stack', + UpdateReplacePolicy: 'Delete', + }, + }, + }); + }); +}); diff --git a/packages/@aws-cdk/core/test/nested-stack.test.ts b/packages/@aws-cdk/core/test/nested-stack.test.ts index 697253fe5f7fd..4d2ffbec186ac 100644 --- a/packages/@aws-cdk/core/test/nested-stack.test.ts +++ b/packages/@aws-cdk/core/test/nested-stack.test.ts @@ -1,5 +1,8 @@ +import * as path from 'path'; +import { Construct } from 'constructs'; +import { readFileSync } from 'fs-extra'; import { - Stack, NestedStack, CfnStack, + Stack, NestedStack, CfnStack, Resource, CfnResource, App, CfnOutput, } from '../lib'; import { toCloudFormation } from './util'; @@ -31,4 +34,163 @@ describe('nested-stack', () => { expect(nestedStack.templateOptions.description).toEqual(description); }); -}); \ No newline at end of file + + test('can create cross region references when crossRegionReferences=true', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { + env: { + account: '123456789012', + region: 'bermuda-triangle-1337', + }, + crossRegionReferences: true, + }); + const stack2 = new Stack(app, 'Stack2', { + env: { + account: '123456789012', + region: 'bermuda-triangle-42', + }, + crossRegionReferences: true, + }); + const nestedStack = new NestedStack(stack1, 'Nested1'); + const nestedStack2 = new NestedStack(stack2, 'Nested2'); + + // WHEN + const myResource = new MyResource(nestedStack, 'Resource1'); + + new CfnResource(nestedStack2, 'Resource2', { + type: 'My::Resource', + properties: { + Prop1: myResource.name, + }, + }); + + // THEN + const assembly = app.synth(); + const nestedTemplate2 = JSON.parse(readFileSync(path.join(assembly.directory, `${nestedStack2.artifactId}.nested.template.json`), 'utf8')); + expect(nestedTemplate2).toMatchObject({ + Resources: { + Resource2: { + Properties: { + Prop1: { + Ref: 'referencetoStack2ExportsReader861D07DCcdkexportsStack2Stack1bermudatriangle1337FnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsStack1Nested1Resource178AEB067RefCEEE331E', + }, + }, + Type: 'My::Resource', + }, + }, + }); + const template2 = assembly.getStackByName(stack2.stackName).template; + expect(template2?.Resources).toMatchObject({ + ExportsReader8B249524: { + DeletionPolicy: 'Delete', + Properties: { + ReaderProps: { + imports: { + '/cdk/exports/Stack2/Stack1bermudatriangle1337FnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsStack1Nested1Resource178AEB067RefCEEE331E': '{{resolve:ssm:/cdk/exports/Stack2/Stack1bermudatriangle1337FnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsStack1Nested1Resource178AEB067RefCEEE331E}}', + }, + region: 'bermuda-triangle-42', + prefix: 'Stack2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportReader', + UpdateReplacePolicy: 'Delete', + }, + }); + const template1 = assembly.getStackByName(stack1.stackName).template; + const nestedTemplate1 = JSON.parse(readFileSync(path.join(assembly.directory, `${nestedStack.artifactId}.nested.template.json`), 'utf8')); + expect(nestedTemplate1?.Outputs).toEqual({ + Stack1Nested1Resource178AEB067Ref: { + Value: { + Ref: 'Resource1CCD41AB7', + }, + }, + }); + + expect(template1?.Resources).toMatchObject({ + ExportsWriterbermudatriangle42E59594276156AC73: { + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack1bermudatriangle1337FnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsStack1Nested1Resource178AEB067RefCEEE331E': { + 'Fn::GetAtt': [ + 'Nested1NestedStackNested1NestedStackResourceCD0AD36B', + 'Outputs.Stack1Nested1Resource178AEB067Ref', + ], + }, + }, + region: 'bermuda-triangle-42', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportWriter', + UpdateReplacePolicy: 'Delete', + }, + }); + }); + + test('cannot create cross region references when crossRegionReferences=false', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { + env: { + account: '123456789012', + region: 'bermuda-triangle-1337', + }, + }); + const stack2 = new Stack(app, 'Stack2', { + env: { + account: '123456789012', + region: 'bermuda-triangle-42', + }, + }); + const nestedStack = new NestedStack(stack1, 'MyNestedStack'); + + // WHEN + const myResource = new MyResource(nestedStack, 'MyResource'); + new CfnOutput(stack2, 'Output', { + value: myResource.name, + }); + + // THEN + expect(() => toCloudFormation(stack2)).toThrow( + /Cannot use resource 'Stack1\/MyNestedStack\/MyResource' in a cross-environment fashion/); + }); +}); + +class MyResource extends Resource { + public readonly arn: string; + public readonly name: string; + + constructor(scope: Construct, id: string, physicalName?: string) { + super(scope, id, { physicalName }); + + const res = new CfnResource(this, 'Resource', { + type: 'My::Resource', + properties: { + resourceName: this.physicalName, + }, + }); + + this.name = this.getResourceNameAttribute(res.ref.toString()); + this.arn = this.getResourceArnAttribute(res.getAtt('Arn').toString(), { + region: '', + account: '', + resource: 'my-resource', + resourceName: this.physicalName, + service: 'myservice', + }); + } +} diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index b9692f8d77e3d..ae1cc4419944b 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -462,6 +462,415 @@ describe('stack', () => { }); }); + test('cross-region stack references, crossRegionReferences=true', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { env: { region: 'us-east-1' }, crossRegionReferences: true }); + const exportResource = new CfnResource(stack1, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack2 = new Stack(app, 'Stack2', { env: { region: 'us-east-2' }, crossRegionReferences: true }); + + // WHEN - used in another stack + new CfnResource(stack2, 'SomeResource', { + type: 'AWS::S3::Bucket', + properties: { + Name: exportResource.getAtt('name'), + }, + }); + + const assembly = app.synth(); + const template2 = assembly.getStackByName(stack2.stackName).template; + const template1 = assembly.getStackByName(stack1.stackName).template; + + // THEN + expect(template1).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + ExportsWriteruseast2828FA26B86FBEFA7: { + Type: 'Custom::CrossRegionExportWriter', + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'name', + ], + }, + }, + region: 'us-east-2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + }, + }, + }); + + expect(template2).toMatchObject({ + Resources: { + SomeResource: { + Type: 'AWS::S3::Bucket', + Properties: { + Name: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F', + ], + }, + }, + }, + }, + }); + }); + + test('cross-region stack references throws error', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { env: { region: 'us-east-1' }, crossRegionReferences: true }); + const exportResource = new CfnResource(stack1, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack2 = new Stack(app, 'Stack2', { env: { region: 'us-east-2' } }); + + // WHEN - used in another stack + new CfnResource(stack2, 'SomeResource', { + type: 'AWS::S3::Bucket', + properties: { + Name: exportResource.getAtt('name'), + }, + }); + + // THEN + expect(() => { + app.synth(); + }).toThrow(/Set crossRegionReferences=true to enable cross region references/); + }); + + test('cross region stack references with multiple stacks, crossRegionReferences=true', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { env: { region: 'us-east-1' }, crossRegionReferences: true }); + const exportResource = new CfnResource(stack1, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack3 = new Stack(app, 'Stack3', { env: { region: 'us-east-1' }, crossRegionReferences: true }); + const exportResource3 = new CfnResource(stack3, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack2 = new Stack(app, 'Stack2', { env: { region: 'us-east-2' }, crossRegionReferences: true }); + + // WHEN - used in another stack + new CfnResource(stack2, 'SomeResource', { + type: 'AWS::S3::Bucket', + properties: { + Name: exportResource.getAtt('name'), + Other: exportResource.getAtt('other'), + Other2: exportResource3.getAtt('other2'), + }, + }); + + const assembly = app.synth(); + const template2 = assembly.getStackByName(stack2.stackName).template; + const template3 = assembly.getStackByName(stack3.stackName).template; + const template1 = assembly.getStackByName(stack1.stackName).template; + + // THEN + expect(template2).toMatchObject({ + Resources: { + CustomCrossRegionExportReaderCustomResourceProviderRole10531BBD: { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssm:AddTagsToResource', + 'ssm:RemoveTagsFromResource', + 'ssm:GetParameters', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:us-east-2:', + { + Ref: 'AWS::AccountId', + }, + ':parameter/cdk/exports/Stack2/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Inline', + }, + ], + }, + Type: 'AWS::IAM::Role', + }, + ExportsReader8B249524: { + DeletionPolicy: 'Delete', + Properties: { + ReaderProps: { + imports: { + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F': '{{resolve:ssm:/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F}}', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1': '{{resolve:ssm:/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1}}', + '/cdk/exports/Stack2/Stack3useast1FnGetAttSomeResourceExportother2190A679B': '{{resolve:ssm:/cdk/exports/Stack2/Stack3useast1FnGetAttSomeResourceExportother2190A679B}}', + }, + region: 'us-east-2', + prefix: 'Stack2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportReaderCustomResourceProviderHandler46647B68', + 'Arn', + ], + }, + }, + Type: 'Custom::CrossRegionExportReader', + UpdateReplacePolicy: 'Delete', + }, + SomeResource: { + Type: 'AWS::S3::Bucket', + Properties: { + Name: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F', + ], + }, + Other: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1', + ], + }, + Other2: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack3useast1FnGetAttSomeResourceExportother2190A679B', + ], + }, + }, + }, + }, + }); + expect(template3).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + ExportsWriteruseast2828FA26B86FBEFA7: { + Type: 'Custom::CrossRegionExportWriter', + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack3useast1FnGetAttSomeResourceExportother2190A679B': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'other2', + ], + }, + }, + region: 'us-east-2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + }, + }, + }); + expect(template1).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + ExportsWriteruseast2828FA26B86FBEFA7: { + Type: 'Custom::CrossRegionExportWriter', + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'name', + ], + }, + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'other', + ], + }, + }, + region: 'us-east-2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + }, + }, + }); + }); + + test('cross region stack references with multiple stacks and multiple regions, crossRegionReferences=true', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { env: { region: 'us-east-1' }, crossRegionReferences: true }); + const exportResource = new CfnResource(stack1, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack3 = new Stack(app, 'Stack3', { env: { region: 'us-west-1' }, crossRegionReferences: true }); + const exportResource3 = new CfnResource(stack3, 'SomeResourceExport', { + type: 'AWS::S3::Bucket', + }); + const stack2 = new Stack(app, 'Stack2', { env: { region: 'us-east-2' }, crossRegionReferences: true }); + + // WHEN - used in another stack + new CfnResource(stack2, 'SomeResource', { + type: 'AWS::S3::Bucket', + properties: { + Name: exportResource.getAtt('name'), + Other: exportResource.getAtt('other'), + Other2: exportResource3.getAtt('other2'), + }, + }); + + const assembly = app.synth(); + const template2 = assembly.getStackByName(stack2.stackName).template; + const template3 = assembly.getStackByName(stack3.stackName).template; + const template1 = assembly.getStackByName(stack1.stackName).template; + + // THEN + expect(template3).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + }, + }); + expect(template2).toMatchObject({ + Resources: { + SomeResource: { + Type: 'AWS::S3::Bucket', + Properties: { + Name: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F', + ], + }, + Other: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1', + ], + }, + Other2: { + 'Fn::GetAtt': [ + 'ExportsReader8B249524', + '/cdk/exports/Stack2/Stack3uswest1FnGetAttSomeResourceExportother2491B5DA7', + ], + }, + }, + }, + }, + }); + expect(template3).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + ExportsWriteruseast2828FA26B86FBEFA7: { + Type: 'Custom::CrossRegionExportWriter', + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack3uswest1FnGetAttSomeResourceExportother2491B5DA7': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'other2', + ], + }, + }, + region: 'us-east-2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + }, + }, + }); + expect(template1).toMatchObject({ + Resources: { + SomeResourceExport: { + Type: 'AWS::S3::Bucket', + }, + ExportsWriteruseast2828FA26B86FBEFA7: { + Type: 'Custom::CrossRegionExportWriter', + DeletionPolicy: 'Delete', + Properties: { + WriterProps: { + exports: { + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportname47AD304F': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'name', + ], + }, + '/cdk/exports/Stack2/Stack1useast1FnGetAttSomeResourceExportotherC6F8CBD1': { + 'Fn::GetAtt': [ + 'SomeResourceExport', + 'other', + ], + }, + }, + region: 'us-east-2', + }, + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A', + 'Arn', + ], + }, + }, + }, + }, + }); + }); + test('cross stack references and dependencies work within child stacks (non-nested)', () => { // GIVEN const app = new App({ @@ -826,12 +1235,12 @@ describe('stack', () => { expect(stack2.dependencies.map(s => s.node.id)).toEqual(['Stack1']); }); - test('cannot create references to stacks in other regions/accounts', () => { + test('cannot create references to stacks in other accounts', () => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1', { env: { account: '123456789012', region: 'es-norst-1' } }); const account1 = new ScopedAws(stack1).accountId; - const stack2 = new Stack(app, 'Stack2', { env: { account: '123456789012', region: 'es-norst-2' } }); + const stack2 = new Stack(app, 'Stack2', { env: { account: '11111111111', region: 'es-norst-2' } }); // WHEN new CfnParameter(stack2, 'SomeParameter', { type: 'String', default: account1 }); diff --git a/packages/@aws-cdk/cx-api/README.md b/packages/@aws-cdk/cx-api/README.md index ba2b89724a96d..0a15d225ee257 100644 --- a/packages/@aws-cdk/cx-api/README.md +++ b/packages/@aws-cdk/cx-api/README.md @@ -104,3 +104,4 @@ becomes: Principal: AWS: "arn:aws:iam::123456789876:root" ``` + diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts b/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts index a2a63df342a7e..5b994265b6bc8 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/waiter-state-machine.ts @@ -169,6 +169,10 @@ export class WaiterStateMachine extends Construct { * Calculate the max number of retries */ function calculateMaxRetries(maxSeconds: number, intervalSeconds: number, backoff: number): number { + // if backoff === 1 then we aren't really using backoff + if (backoff === 1) { + return Math.floor(maxSeconds / intervalSeconds); + } let retries = 1; let nextInterval = intervalSeconds; let i = 0; diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index ca2a5a8dd986f..0611e73e8d3f8 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -191,6 +191,56 @@ export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { // https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html export const CLOUDWATCH_LAMBDA_INSIGHTS_ARNS: { [key: string]: any } = { + '1.0.143.0': { + x86_64: { + // US East (N. Virginia) + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:21', + // US East (Ohio) + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:21', + // US West (N. California) + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:20', + // US West (Oregon) + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:21', + // Africa (Cape Town) + 'af-south-1': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:13', + // Asia Pacific (Hong Kong) + 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:13', + // Asia Pacific (Mumbai) + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:21', + // Asia Pacific (Osaka) + 'ap-northeast-3': 'arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:2', + // Asia Pacific (Seoul) + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:20', + // Asia Pacific (Singapore) + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:21', + // Asia Pacific (Sydney) + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:21', + // Asia Pacific (Tokyo) + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:31', + // Canada (Central) + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:20', + // China (Beijing) + 'cn-north-1': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:14', + // China (Ningxia) + 'cn-northwest-1': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:14', + // Europe (Frankfurt) + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:21', + // Europe (Ireland) + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:21', + // Europe (London) + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:21', + // Europe (Milan) + 'eu-south-1': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:13', + // Europe (Paris) + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:20', + // Europe (Stockholm) + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:20', + // Middle East (Bahrain) + 'me-south-1': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:13', + // South America (Sao Paulo) + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:20', + }, + }, '1.0.135.0': { arm64: { // US East (N. Virginia) @@ -229,7 +279,7 @@ export const CLOUDWATCH_LAMBDA_INSIGHTS_ARNS: { [key: string]: any } = { 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:11', // Asia Pacific (Mumbai) 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:18', - // Asia Pacific (Oskaka) + // Asia Pacific (Osaka) 'ap-northeast-3': 'arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:1', // Asia Pacific (Seoul) 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:18', diff --git a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap index 2ce71cae5df83..6251a48f804da 100644 --- a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap +++ b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap @@ -12,6 +12,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:13", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -45,6 +46,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:13", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -78,6 +80,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23", "1.0.135.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:25", + "1.0.143.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:31", "1.0.54.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -111,6 +114,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -144,6 +148,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": "arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:1", + "1.0.143.0": "arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:2", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -177,6 +182,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:12", @@ -210,6 +216,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -243,6 +250,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -276,6 +284,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -309,6 +318,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -342,6 +352,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:14", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -375,6 +386,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:14", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -408,6 +420,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -441,6 +454,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:12", @@ -474,6 +488,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:13", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -507,6 +522,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -540,6 +556,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -573,6 +590,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:12", @@ -606,6 +624,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:12", @@ -639,6 +658,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9", "1.0.135.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:11", + "1.0.143.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:13", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -672,6 +692,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -705,6 +726,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -738,6 +760,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:12", @@ -771,6 +794,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -804,6 +828,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -837,6 +862,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -870,6 +896,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -903,6 +930,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": undefined, "1.0.135.0": undefined, + "1.0.143.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -936,6 +964,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:20", "1.0.54.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -969,6 +998,7 @@ Object { "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16", "1.0.135.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:18", + "1.0.143.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:21", "1.0.54.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:12", diff --git a/packages/aws-cdk-lib/README.md b/packages/aws-cdk-lib/README.md index 2cda63fee8efe..92bb8a042acec 100644 --- a/packages/aws-cdk-lib/README.md +++ b/packages/aws-cdk-lib/README.md @@ -167,7 +167,8 @@ Nested stacks also support the use of Docker image and file assets. ## Accessing resources in a different stack You can access resources in a different stack, as long as they are in the -same account and AWS Region. The following example defines the stack `stack1`, +same account and AWS Region (see [next section](#accessing-resources-in-a-different-stack-and-region) for an exception). +The following example defines the stack `stack1`, which defines an Amazon S3 bucket. Then it defines a second stack, `stack2`, which takes the bucket from stack1 as a constructor property. @@ -192,6 +193,56 @@ in the producing stack and an in the consuming stack to transfer that information from one stack to the other. +## Accessing resources in a different stack and region + +> **This feature is currently experimental** + +You can enable the Stack property `crossRegionReferences` +in order to access resources in a different stack _and_ region. With this feature flag +enabled it is possible to do something like creating a CloudFront distribution in `us-east-2` and +an ACM certificate in `us-east-1`. + +```ts +const stack1 = new Stack(app, 'Stack1', { + env: { + region: 'us-east-1', + }, + crossRegionReferences: true, +}); +const cert = new acm.Certificate(stack1, 'Cert', { + domainName: '*.example.com', + validation: acm.CertificateValidation.fromDns(route53.PublicHostedZone.fromHostedZoneId(stack1, 'Zone', 'Z0329774B51CGXTDQV3X')), +}); + +const stack2 = new Stack(app, 'Stack2', { + env: { + region: 'us-east-2', + }, + crossRegionReferences: true, +}); +new cloudfront.Distribution(stack2, 'Distribution', { + defaultBehavior: { + origin: new origins.HttpOrigin('example.com'), + }, + domainNames: ['dev.example.com'], + certificate: cert, +}); +``` + +When the AWS CDK determines that the resource is in a different stack _and_ is in a different +region, it will "export" the value by creating a custom resource in the producing stack which +creates SSM Parameters in the consuming region for each exported value. The parameters will be +created with the name '/cdk/exports/${consumingStackName}/${export-name}'. +In order to "import" the exports into the consuming stack a [SSM Dynamic reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-ssm) +is used to reference the SSM parameter which was created. + +In order to mimic strong references, a Custom Resource is also created in the consuming +stack which marks the SSM parameters as being "imported". When a parameter has been successfully +imported, the producing stack cannot update the value. + +See the [adr](https://github.com/aws/aws-cdk/blob/main/packages/@aws-cdk/core/adr/cross-region-stack-references) +for more details on this feature. + ### Removing automatic cross-stack references The automatic references created by CDK when you use resources across stacks diff --git a/version.v2.json b/version.v2.json index ea5b160d632e1..c2eec0166e469 100644 --- a/version.v2.json +++ b/version.v2.json @@ -1,4 +1,4 @@ { - "version": "2.49.1", - "alphaVersion": "2.49.1-alpha.0" + "version": "2.50.0", + "alphaVersion": "2.50.0-alpha.0" } \ No newline at end of file