From beb77513e18b0a2c39b785ccf4d30342c77c3a1f Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Tue, 22 Sep 2020 03:05:25 +0000 Subject: [PATCH 01/46] chore(release): 1.64.0 --- CHANGELOG.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca071fd1212bf..69a3baea0bb4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,76 @@ 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. +## [1.64.0](https://github.com/aws/aws-cdk/compare/v1.63.0...v1.64.0) (2020-09-22) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **codedeploy:** the default policy for `LambdaDeploymentGroup` no longer contains `sns:Publish` on `*` permissions +* **cfn-include:** the construction property 'nestedStacks' of class 'CfnInclude' has been renamed to 'loadNestedStacks' +* **rds:** removed protected member `subnetGroup` from DatabaseCluster classes +* **rds:** Cluster now has deletionProtection enabled if its removal policy is `RETAIN` +* **rds**: Instance now has deletionProtection enabled by default only if its removal policy is `RETAIN` +* **cfnspec:** Fixed ECS task definition within the L1 layer. Fixed the casing of the `efsVolumeConfiguration` property to match the spec published by cloudformation. Fixed the type of the `DockerVolumeConfiguration.labels` property to allow users to properly apply labels. + +* **ecs**: Task definitions configured with an `efsVolumeConfiguration` will incur a resource replacement due to wrong casing of the underlying resources introduced in this [PR](https://github.com/aws/aws-cdk/pull/8467/files). This replacement will in turn cause a rolling update to any running tasks that use that definition. +* **ecs**: `DockerVolumeConfiguration.labels` changed from an **array** to a **map**. This was a long standing latent bug and in fact configuring labels in the old format would have resulted in the wrong behavior. +* **eks:** Clusters previously running k8s version other than `1.15` and bottlerocket AMI(`aws-k8s-1.15` variant) will trigger AMI and node replacement. + +### Features + +* **cfn-include:** add 'loadNestedStack()' method ([#10292](https://github.com/aws/aws-cdk/issues/10292)) ([9d6817f](https://github.com/aws/aws-cdk/commit/9d6817f4bc3cc052f351bf464403165972ef0afb)) +* **cfn-include:** the package cloudformation-include is now 'Developer Preview' ([#10436](https://github.com/aws/aws-cdk/issues/10436)) ([d45a57c](https://github.com/aws/aws-cdk/commit/d45a57c22a006f682e584c5ef6c8ef3f416caf86)) +* **cfnspec:** cloudformation spec v18.3.0 ([#10385](https://github.com/aws/aws-cdk/issues/10385)) ([dbdc7ff](https://github.com/aws/aws-cdk/commit/dbdc7ff20812157be518229ee9be90a5bbcb8d65)) +* **cli:** skip bundling for operations where stack is not needed ([#9889](https://github.com/aws/aws-cdk/issues/9889)) ([28cee39](https://github.com/aws/aws-cdk/commit/28cee393be75c6785d8b5471a6ecc656fa29648c)), closes [#9540](https://github.com/aws/aws-cdk/issues/9540) +* **codedeploy:** change LambdaDeploymentGroup default managed policy to AWSCodeDeployRoleForLambdaLimited ([#10276](https://github.com/aws/aws-cdk/issues/10276)) ([13e7bde](https://github.com/aws/aws-cdk/commit/13e7bde5f8f53f49ccc57def38aba2ec00b85409)) +* **cognito:** user pool client logout urls ([#10301](https://github.com/aws/aws-cdk/issues/10301)) ([5111837](https://github.com/aws/aws-cdk/commit/511183771b844e22881e9a2b3640a4645437f34c)) +* **custom-resource:** allow referencing resource id in updates/deletes ([#10327](https://github.com/aws/aws-cdk/issues/10327)) ([a726dad](https://github.com/aws/aws-cdk/commit/a726dad3fb220e10bc12928fded3702b740e28a7)), closes [#10305](https://github.com/aws/aws-cdk/issues/10305) +* **ec2:** generic ssm backed machine image ([#10369](https://github.com/aws/aws-cdk/issues/10369)) ([1dbad6e](https://github.com/aws/aws-cdk/commit/1dbad6e1c9aa3821988735b320b397b1106cca46)) +* **ec2:** user-defined subnet selectors ([#10112](https://github.com/aws/aws-cdk/issues/10112)) ([491113d](https://github.com/aws/aws-cdk/commit/491113d7367ad087fa10d2c00bf220e7973ce320)) +* **eks:** bottlerocket versoin follows the cluster k8s versoin ([#10189](https://github.com/aws/aws-cdk/issues/10189)) ([19638a6](https://github.com/aws/aws-cdk/commit/19638a6dfeb33554a5c25a75914adbf2019688f3)), closes [#10188](https://github.com/aws/aws-cdk/issues/10188) +* **events-targets:** supports to specify fargate platform version ([#10223](https://github.com/aws/aws-cdk/issues/10223)) ([3dcd01e](https://github.com/aws/aws-cdk/commit/3dcd01eb1f6fa8504db444db59dacb03dd5d4578)), closes [#10186](https://github.com/aws/aws-cdk/issues/10186) +* **lambda-nodejs:** custom bundling image ([#10270](https://github.com/aws/aws-cdk/issues/10270)) ([a2174a4](https://github.com/aws/aws-cdk/commit/a2174a460a8e7b51e8bdd75304b2eb38ae1adc78)), closes [#10194](https://github.com/aws/aws-cdk/issues/10194) +* **pipelines:** support VPC property in ShellScriptAction ([#10240](https://github.com/aws/aws-cdk/issues/10240)) ([08a3c55](https://github.com/aws/aws-cdk/commit/08a3c55f973436393103fce26467800183d51e69)), closes [#9982](https://github.com/aws/aws-cdk/issues/9982) +* **rds:** add SQL Server version 15.00.4043.16.v1 ([#10289](https://github.com/aws/aws-cdk/issues/10289)) ([a578ef8](https://github.com/aws/aws-cdk/commit/a578ef88b1554947504e02d74b1cfd90709c2f44)), closes [#10273](https://github.com/aws/aws-cdk/issues/10273) +* **rds:** S3 import and export for DatabaseInstances ([#10370](https://github.com/aws/aws-cdk/issues/10370)) ([80a2ac9](https://github.com/aws/aws-cdk/commit/80a2ac94359f18b59f6f61bd068fd01e221be8b6)), closes [#4419](https://github.com/aws/aws-cdk/issues/4419) +* **rds:** support existing cluster subnet groups ([#10391](https://github.com/aws/aws-cdk/issues/10391)) ([a1df511](https://github.com/aws/aws-cdk/commit/a1df51187d77512d7618e205d21427557bd212b7)), closes [#9991](https://github.com/aws/aws-cdk/issues/9991) +* **redshift:** support existing cluster subnet groups ([#10340](https://github.com/aws/aws-cdk/issues/10340)) ([5ad8cdb](https://github.com/aws/aws-cdk/commit/5ad8cdb662a2b3a27a3590370d055eeb53b3645b)), closes [#9241](https://github.com/aws/aws-cdk/issues/9241) +* **secretsmanager:** import secrets by name ([#10309](https://github.com/aws/aws-cdk/issues/10309)) ([a8e8ed3](https://github.com/aws/aws-cdk/commit/a8e8ed37379c5bbaeeb13a773d5438ea5e5b2fec)), closes [#7444](https://github.com/aws/aws-cdk/issues/7444) [#7949](https://github.com/aws/aws-cdk/issues/7949) [#7994](https://github.com/aws/aws-cdk/issues/7994) +* add support for the 'Version' resource attribute ([#10376](https://github.com/aws/aws-cdk/issues/10376)) ([aac235a](https://github.com/aws/aws-cdk/commit/aac235aab349a103f92934b86dce9f0eee424c06)) +* **stepfunctions:** added new condition operators ([#9920](https://github.com/aws/aws-cdk/issues/9920)) ([b8490f2](https://github.com/aws/aws-cdk/commit/b8490f25a8eb6104163cf03c4e4ea9a61163877d)) +* **stepfunctions:** support X-Ray tracing ([#10371](https://github.com/aws/aws-cdk/issues/10371)) ([#10374](https://github.com/aws/aws-cdk/issues/10374)) ([ad011c0](https://github.com/aws/aws-cdk/commit/ad011c0afb487dcd27df968d7b48ea6d21ff04cb)) +* **stepfunctions-tasks:** handle Lambda service exceptions ([#10386](https://github.com/aws/aws-cdk/issues/10386)) ([edf75b6](https://github.com/aws/aws-cdk/commit/edf75b6707086d61b5379f832f6597427a08a84e)) + + +### Bug Fixes + +* **bootstrap:** no longer creates KMS master key by default ([#10365](https://github.com/aws/aws-cdk/issues/10365)) ([bedd4c0](https://github.com/aws/aws-cdk/commit/bedd4c00177f67809dd186488b254956039bd799)), closes [#10115](https://github.com/aws/aws-cdk/issues/10115) +* **bootstrapping:** `--cloudformation-execution-policies` not checked ([#10337](https://github.com/aws/aws-cdk/issues/10337)) ([ad9a705](https://github.com/aws/aws-cdk/commit/ad9a70543703e8d8ebaac82001ee9a62f784bea7)) +* **cfn-include:** allow referring to Conditions in Outputs and Rules ([#10373](https://github.com/aws/aws-cdk/issues/10373)) ([4751f42](https://github.com/aws/aws-cdk/commit/4751f4281287ab8fdfba5790b88148bbb1a8a0de)) +* **cfn-include:** correctly handle the 'AWS::CloudFormation::CustomResource' resource type ([#10415](https://github.com/aws/aws-cdk/issues/10415)) ([1a5a024](https://github.com/aws/aws-cdk/commit/1a5a024b601e28d158b6401b5d97ed408a73eb5d)) +* **cli:** `--profile` is ignored if AWS_ variables are set ([#10362](https://github.com/aws/aws-cdk/issues/10362)) ([957a12e](https://github.com/aws/aws-cdk/commit/957a12eeb464443687e3dfd5f224f2769814a41b)) +* **cli:** `cdk synth` fails if AWS_ credentials have expired ([#10343](https://github.com/aws/aws-cdk/issues/10343)) ([406f665](https://github.com/aws/aws-cdk/commit/406f6650a4d9ba0f2b6158aea27707710bb213f3)), closes [#7849](https://github.com/aws/aws-cdk/issues/7849) +* **cli:** stack outputs aren't sorted ([#10328](https://github.com/aws/aws-cdk/issues/10328)) ([9f430fc](https://github.com/aws/aws-cdk/commit/9f430fc86239e299b39aaaeea7982ff4a57fdcfd)) +* **cloudwatch:** LTE operator renders wrong symbol ([#10418](https://github.com/aws/aws-cdk/issues/10418)) ([2543584](https://github.com/aws/aws-cdk/commit/254358449ec3040c750a416c0b4923884a3d2612)), closes [#8913](https://github.com/aws/aws-cdk/issues/8913) +* **codebuild:** Project.addFileSystemLocation does not work without providing locations at construction ([#10460](https://github.com/aws/aws-cdk/issues/10460)) ([994d3c3](https://github.com/aws/aws-cdk/commit/994d3c3d6aca6b6aee84412333a073ebb6671f7f)), closes [#10442](https://github.com/aws/aws-cdk/issues/10442) +* **core:** CfnParameter of Number type cannot be used as a string ([#10422](https://github.com/aws/aws-cdk/issues/10422)) ([28adc88](https://github.com/aws/aws-cdk/commit/28adc8826a7498288e0cf4ee96f43471d24062cb)), closes [#10228](https://github.com/aws/aws-cdk/issues/10228) +* **diff:** `deepEqual` may miss difference other than `DependsOn` ([#10394](https://github.com/aws/aws-cdk/issues/10394)) ([9bcaf75](https://github.com/aws/aws-cdk/commit/9bcaf7564f72deea6942c3cd2e2fb98c14f3d152)), closes [#10322](https://github.com/aws/aws-cdk/issues/10322) +* **diff:** allow strings to be passed for boolean properties ([#10378](https://github.com/aws/aws-cdk/issues/10378)) ([673dd82](https://github.com/aws/aws-cdk/commit/673dd82268aa199099a7a589c956fead2a800d02)) +* **diff:** handle YAML short-forms like '!GetAtt' in diff ([#10381](https://github.com/aws/aws-cdk/issues/10381)) ([457e109](https://github.com/aws/aws-cdk/commit/457e109c649d97916ba1e21d08180a267e4c0711)), closes [#6537](https://github.com/aws/aws-cdk/issues/6537) +* **dynamodb:** cannot change serverSideEncryption from true to false ([#8450](https://github.com/aws/aws-cdk/issues/8450)) ([7a266b5](https://github.com/aws/aws-cdk/commit/7a266b53a3b07f70062639a4b68b1b89ecae726e)), closes [#8286](https://github.com/aws/aws-cdk/issues/8286) +* **ec2:** `InitFile` does not work on Windows ([#10450](https://github.com/aws/aws-cdk/issues/10450)) ([84b9d5e](https://github.com/aws/aws-cdk/commit/84b9d5ea8abd14dc2de228de3a0cb65dca0028ab)), closes [#10390](https://github.com/aws/aws-cdk/issues/10390) +* **eks:** cannot import a cluster with cdk managed `kubectlPrivateSubnets` ([#10459](https://github.com/aws/aws-cdk/issues/10459)) ([10d0a36](https://github.com/aws/aws-cdk/commit/10d0a368c0fe34513ba9c359c0fdaa24a569dc5a)) +* **eks:** circular dependencies when security groups from other stacks are used ([#10339](https://github.com/aws/aws-cdk/issues/10339)) ([857acbb](https://github.com/aws/aws-cdk/commit/857acbbb7f26feecca938dc881add57fe5cae7e4)) +* **lambda:** unable to add permissions to imported lambda functions ([#8828](https://github.com/aws/aws-cdk/issues/8828)) ([9bf8e13](https://github.com/aws/aws-cdk/commit/9bf8e13bd47608070b73221c11c55b09d03c0a4c)), closes [#7588](https://github.com/aws/aws-cdk/issues/7588) +* **lambda-nodejs:** local parcel not detected ([#10268](https://github.com/aws/aws-cdk/issues/10268)) ([457fab8](https://github.com/aws/aws-cdk/commit/457fab8768b89933beb8d659ac7ecab7fd8dfac4)) +* **pipelines:** make CdkPipeline build stage optional ([#10345](https://github.com/aws/aws-cdk/issues/10345)) ([e9ffa67](https://github.com/aws/aws-cdk/commit/e9ffa67c6bcfdfc96067bd70feda3450f3249867)), closes [#10148](https://github.com/aws/aws-cdk/issues/10148) +* **rds:** cannot use s3ImportBuckets or s3ExportBuckets with aurora postgres ([#10132](https://github.com/aws/aws-cdk/issues/10132)) ([cb6fef8](https://github.com/aws/aws-cdk/commit/cb6fef8ee4746ffea66df73e6ef64f613af5f983)), closes [#4419](https://github.com/aws/aws-cdk/issues/4419) [#8201](https://github.com/aws/aws-cdk/issues/8201) +* SSM Association 'parameters' property has incorrect type ([#10316](https://github.com/aws/aws-cdk/issues/10316)) ([7b5c9d2](https://github.com/aws/aws-cdk/commit/7b5c9d260a9f0600a35dd5f37454bea74e5f786f)), closes [#3092](https://github.com/aws/aws-cdk/issues/3092) +* **rds:** standardize removal policies and deletion protection ([#10412](https://github.com/aws/aws-cdk/issues/10412)) ([75811c1](https://github.com/aws/aws-cdk/commit/75811c1325c3d857cf9891048474201b2f28477a)) +* **redshift:** cluster defaultChild broken after adding subnet group ([#10389](https://github.com/aws/aws-cdk/issues/10389)) ([746dfe2](https://github.com/aws/aws-cdk/commit/746dfe2b8d0fced5d2a9e4b760f477b0abcb6df9)), closes [#10340](https://github.com/aws/aws-cdk/issues/10340) +* **s3-notifications:** lambda destination creates a circular dependency when bucket and lambda are in different stacks ([#10426](https://github.com/aws/aws-cdk/issues/10426)) ([7222b5d](https://github.com/aws/aws-cdk/commit/7222b5d62c70719f9a7b3af5a80840d750b109b1)) + ## [1.63.0](https://github.com/aws/aws-cdk/compare/v1.62.0...v1.63.0) (2020-09-12) diff --git a/lerna.json b/lerna.json index 41116c625ce66..7b4680783877f 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.63.0" + "version": "1.64.0" } From 2e7cb95e644cc61f36314c35ac91648d23c499ec Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Tue, 22 Sep 2020 15:22:45 -0700 Subject: [PATCH 02/46] Update CHANGELOG.md --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a3baea0bb4c..77867355f3560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,7 @@ All notable changes to this project will be documented in this file. See [standa * **rds:** removed protected member `subnetGroup` from DatabaseCluster classes * **rds:** Cluster now has deletionProtection enabled if its removal policy is `RETAIN` * **rds**: Instance now has deletionProtection enabled by default only if its removal policy is `RETAIN` -* **cfnspec:** Fixed ECS task definition within the L1 layer. Fixed the casing of the `efsVolumeConfiguration` property to match the spec published by cloudformation. Fixed the type of the `DockerVolumeConfiguration.labels` property to allow users to properly apply labels. -* **ecs**: Task definitions configured with an `efsVolumeConfiguration` will incur a resource replacement due to wrong casing of the underlying resources introduced in this [PR](https://github.com/aws/aws-cdk/pull/8467/files). This replacement will in turn cause a rolling update to any running tasks that use that definition. -* **ecs**: `DockerVolumeConfiguration.labels` changed from an **array** to a **map**. This was a long standing latent bug and in fact configuring labels in the old format would have resulted in the wrong behavior. * **eks:** Clusters previously running k8s version other than `1.15` and bottlerocket AMI(`aws-k8s-1.15` variant) will trigger AMI and node replacement. ### Features @@ -71,6 +68,7 @@ All notable changes to this project will be documented in this file. See [standa * **rds:** standardize removal policies and deletion protection ([#10412](https://github.com/aws/aws-cdk/issues/10412)) ([75811c1](https://github.com/aws/aws-cdk/commit/75811c1325c3d857cf9891048474201b2f28477a)) * **redshift:** cluster defaultChild broken after adding subnet group ([#10389](https://github.com/aws/aws-cdk/issues/10389)) ([746dfe2](https://github.com/aws/aws-cdk/commit/746dfe2b8d0fced5d2a9e4b760f477b0abcb6df9)), closes [#10340](https://github.com/aws/aws-cdk/issues/10340) * **s3-notifications:** lambda destination creates a circular dependency when bucket and lambda are in different stacks ([#10426](https://github.com/aws/aws-cdk/issues/10426)) ([7222b5d](https://github.com/aws/aws-cdk/commit/7222b5d62c70719f9a7b3af5a80840d750b109b1)) +* **ecs**: `DockerVolumeConfiguration.labels` changed from an **array** to a **map**. This was a long standing latent bug and in fact configuring labels in the old format would have resulted in the wrong behavior. ([#10385](https://github.com/aws/aws-cdk/pull/10385)) ## [1.63.0](https://github.com/aws/aws-cdk/compare/v1.62.0...v1.63.0) (2020-09-12) From dd308b6610a670f0dd91df2476253cffb4cb04a3 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 23 Sep 2020 01:49:18 +0300 Subject: [PATCH 03/46] chore: revert casing of EFSVolumeConfiguration to prevent breaking changes (#10483) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-ecs/lib/base/task-definition.ts | 2 +- .../test/ec2/test.ec2-task-definition.ts | 4 +-- ...finition_EfsVolumeConfiguration_patch.json | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/cfnspec/spec-source/620_Ecs_TaskDefinition_EfsVolumeConfiguration_patch.json diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index a27c65a6a34b2..0db212f2247a1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -343,7 +343,7 @@ export class TaskDefinition extends TaskDefinitionBase { scope: spec.dockerVolumeConfiguration.scope, }, efsVolumeConfiguration: spec.efsVolumeConfiguration && { - filesystemId: spec.efsVolumeConfiguration.fileSystemId, + fileSystemId: spec.efsVolumeConfiguration.fileSystemId, authorizationConfig: spec.efsVolumeConfiguration.authorizationConfig, rootDirectory: spec.efsVolumeConfiguration.rootDirectory, transitEncryption: spec.efsVolumeConfiguration.transitEncryption, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index 362c040ae97ba..9688e649b545c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -1006,8 +1006,8 @@ export = { Family: 'Ec2TaskDef', Volumes: [{ Name: 'scratch', - EFSVolumeConfiguration: { - FilesystemId: 'local', + EfsVolumeConfiguration: { + FileSystemId: 'local', }, }], })); diff --git a/packages/@aws-cdk/cfnspec/spec-source/620_Ecs_TaskDefinition_EfsVolumeConfiguration_patch.json b/packages/@aws-cdk/cfnspec/spec-source/620_Ecs_TaskDefinition_EfsVolumeConfiguration_patch.json new file mode 100644 index 0000000000000..f19367bd89ea0 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/620_Ecs_TaskDefinition_EfsVolumeConfiguration_patch.json @@ -0,0 +1,29 @@ +{ + "PropertyTypes": { + "patch": { + "description": "Reverting EfsVolumeConfiguration casing", + "operations": [ + { + "path": "/AWS::ECS::TaskDefinition.Volume/Properties/EFSVolumeConfiguration/Type", + "op": "replace", + "value": "EfsVolumeConfiguration" + }, + { + "from": "/AWS::ECS::TaskDefinition.EFSVolumeConfiguration/Properties/FilesystemId", + "path": "/AWS::ECS::TaskDefinition.EFSVolumeConfiguration/Properties/FileSystemId", + "op": "move" + }, + { + "from": "/AWS::ECS::TaskDefinition.Volume/Properties/EFSVolumeConfiguration", + "path": "/AWS::ECS::TaskDefinition.Volume/Properties/EfsVolumeConfiguration", + "op": "move" + }, + { + "from": "/AWS::ECS::TaskDefinition.EFSVolumeConfiguration", + "path": "/AWS::ECS::TaskDefinition.EfsVolumeConfiguration", + "op": "move" + } + ] + } + } +} \ No newline at end of file From c8e72e5567409c3340b243c2c240bb1ba1afa1dc Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 24 Sep 2020 13:13:02 +0200 Subject: [PATCH 04/46] chore(integ): run all CDK integ tests with `-v` (#10503) Now that we suppress output of non-failing tests, it becomes all the more important to have detailed information for failing tests. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cli-regression-patches/v1.64.0/NOTES.md | 3 + .../v1.64.0/cdk-helpers.js | 322 ++++++++++++++++++ .../aws-cdk/test/integ/cli/cdk-helpers.ts | 2 +- 3 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md new file mode 100644 index 0000000000000..ade25e2ca159c --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md @@ -0,0 +1,3 @@ +Added a `-v` switch to the cdk executions that also needs to be +applied to the regression tesets so we have a better chance +of catching sporadically failing tests in the act. diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js new file mode 100644 index 0000000000000..1b8bea33bf320 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js @@ -0,0 +1,322 @@ +"use strict"; +var _a, _b; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.randomString = exports.rimraf = exports.shell = exports.TestFixture = exports.cloneDirectory = exports.withDefaultFixture = exports.withCdkApp = exports.withAws = void 0; +const child_process = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const resource_pool_1 = require("./resource-pool"); +const REGIONS = process.env.AWS_REGIONS + ? process.env.AWS_REGIONS.split(',') + : [(_b = (_a = process.env.AWS_REGION) !== null && _a !== void 0 ? _a : process.env.AWS_DEFAULT_REGION) !== null && _b !== void 0 ? _b : 'us-east-1']; +process.stdout.write(`Using regions: ${REGIONS}\n`); +const REGION_POOL = new resource_pool_1.ResourcePool(REGIONS); +/** + * Higher order function to execute a block with an AWS client setup + * + * Allocate the next region from the REGION pool and dispose it afterwards. + */ +function withAws(block) { + return (context) => REGION_POOL.using(async (region) => { + const aws = await aws_helpers_1.AwsClients.forRegion(region, context.output); + await sanityCheck(aws); + return block({ ...context, aws }); + }); +} +exports.withAws = withAws; +/** + * Higher order function to execute a block with a CDK app fixture + * + * Requires an AWS client to be passed in. + * + * For backwards compatibility with existing tests (so we don't have to change + * too much) the inner block is expecte to take a `TestFixture` object. + */ +function withCdkApp(block) { + return async (context) => { + const randy = randomString(); + const stackNamePrefix = `cdktest-${randy}`; + const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); + context.output.write(` Stack prefix: ${stackNamePrefix}\n`); + context.output.write(` Test directory: ${integTestDir}\n`); + context.output.write(` Region: ${context.aws.region}\n`); + await cloneDirectory(path.join(__dirname, 'app'), integTestDir, context.output); + const fixture = new TestFixture(integTestDir, stackNamePrefix, context.output, context.aws); + let success = true; + try { + await fixture.shell(['npm', 'install', + '@aws-cdk/core', + '@aws-cdk/aws-sns', + '@aws-cdk/aws-iam', + '@aws-cdk/aws-lambda', + '@aws-cdk/aws-ssm', + '@aws-cdk/aws-ecr-assets', + '@aws-cdk/aws-cloudformation', + '@aws-cdk/aws-ec2']); + await ensureBootstrapped(fixture); + await block(fixture); + } + catch (e) { + success = false; + throw e; + } + finally { + await fixture.dispose(success); + } + }; +} +exports.withCdkApp = withCdkApp; +/** + * Default test fixture for most (all?) integ tests + * + * It's a composition of withAws/withCdkApp, expecting the test block to take a `TestFixture` + * object. + * + * We could have put `withAws(withCdkApp(fixture => { /... actual test here.../ }))` in every + * test declaration but centralizing it is going to make it convenient to modify in the future. + */ +function withDefaultFixture(block) { + return withAws(withCdkApp(block)); + // ^~~~~~ this is disappointing TypeScript! Feels like you should have been able to derive this. +} +exports.withDefaultFixture = withDefaultFixture; +/** + * Prepare a target dir byreplicating a source directory + */ +async function cloneDirectory(source, target, output) { + await shell(['rm', '-rf', target], { output }); + await shell(['mkdir', '-p', target], { output }); + await shell(['cp', '-R', source + '/*', target], { output }); +} +exports.cloneDirectory = cloneDirectory; +class TestFixture { + constructor(integTestDir, stackNamePrefix, output, aws) { + this.integTestDir = integTestDir; + this.stackNamePrefix = stackNamePrefix; + this.output = output; + this.aws = aws; + this.qualifier = randomString().substr(0, 10); + this.bucketsToDelete = new Array(); + } + log(s) { + this.output.write(`${s}\n`); + } + async shell(command, options = {}) { + return shell(command, { + output: this.output, + cwd: this.integTestDir, + ...options, + }); + } + async cdkDeploy(stackNames, options = {}) { + var _a, _b; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + const neverRequireApproval = (_a = options.neverRequireApproval) !== null && _a !== void 0 ? _a : true; + return this.cdk(['deploy', + ...(neverRequireApproval ? ['--require-approval=never'] : []), // Default to no approval in an unattended test + ...((_b = options.options) !== null && _b !== void 0 ? _b : []), ...this.fullStackName(stackNames)], options); + } + async cdkDestroy(stackNames, options = {}) { + var _a; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + return this.cdk(['destroy', + '-f', // We never want a prompt in an unattended test + ...((_a = options.options) !== null && _a !== void 0 ? _a : []), ...this.fullStackName(stackNames)], options); + } + async cdk(args, options = {}) { + return this.shell(['cdk', '-v', ...args], { + ...options, + modEnv: { + AWS_REGION: this.aws.region, + AWS_DEFAULT_REGION: this.aws.region, + STACK_NAME_PREFIX: this.stackNamePrefix, + ...options.modEnv, + }, + }); + } + fullStackName(stackNames) { + if (typeof stackNames === 'string') { + return `${this.stackNamePrefix}-${stackNames}`; + } + else { + return stackNames.map(s => `${this.stackNamePrefix}-${s}`); + } + } + /** + * Append this to the list of buckets to potentially delete + * + * At the end of a test, we clean up buckets that may not have gotten destroyed + * (for whatever reason). + */ + rememberToDeleteBucket(bucketName) { + this.bucketsToDelete.push(bucketName); + } + /** + * Cleanup leftover stacks and buckets + */ + async dispose(success) { + const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix); + // Bootstrap stacks have buckets that need to be cleaned + const bucketNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('BucketName', stack)).filter(defined); + await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b))); + // Bootstrap stacks have ECR repositories with images which should be deleted + const imageRepositoryNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('ImageRepositoryName', stack)).filter(defined); + await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r))); + await this.aws.deleteStacks(...stacksToDelete.map(s => s.StackName)); + // We might have leaked some buckets by upgrading the bootstrap stack. Be + // sure to clean everything. + for (const bucket of this.bucketsToDelete) { + await this.aws.deleteBucket(bucket); + } + // If the tests completed successfully, happily delete the fixture + // (otherwise leave it for humans to inspect) + if (success) { + rimraf(this.integTestDir); + } + } + /** + * Return the stacks starting with our testing prefix that should be deleted + */ + async deleteableStacks(prefix) { + var _a; + const statusFilter = [ + 'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE', + 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE', + 'DELETE_FAILED', + 'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS', + 'UPDATE_ROLLBACK_FAILED', + 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_ROLLBACK_COMPLETE', 'REVIEW_IN_PROGRESS', + 'IMPORT_IN_PROGRESS', 'IMPORT_COMPLETE', + 'IMPORT_ROLLBACK_IN_PROGRESS', 'IMPORT_ROLLBACK_FAILED', + 'IMPORT_ROLLBACK_COMPLETE', + ]; + const response = await this.aws.cloudFormation('describeStacks', {}); + return ((_a = response.Stacks) !== null && _a !== void 0 ? _a : []) + .filter(s => s.StackName.startsWith(prefix)) + .filter(s => statusFilter.includes(s.StackStatus)) + .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process + } +} +exports.TestFixture = TestFixture; +/** + * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * + * If we don't do this, calls are going to fail and they'll be retried and everything will take + * forever before the user notices a simple misconfiguration. + * + * We can't check for the presence of environment variables since credentials could come from + * anywhere, so do simple account retrieval. + * + * Only do it once per process. + */ +async function sanityCheck(aws) { + if (sanityChecked === undefined) { + try { + await aws.account(); + sanityChecked = true; + } + catch (e) { + sanityChecked = false; + throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); + } + } + if (!sanityChecked) { + throw new Error('AWS credentials probably not configured, see previous error'); + } +} +let sanityChecked; +/** + * Make sure that the given environment is bootstrapped + * + * Since we go striping across regions, it's going to suck doing this + * by hand so let's just mass-automate it. + */ +async function ensureBootstrapped(fixture) { + // Old-style bootstrap stack with default name + if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { + await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); + } +} +/** + * A shell command that does what you want + * + * Is platform-aware, handles errors nicely. + */ +async function shell(command, options = {}) { + var _a, _b; + if (options.modEnv && options.env) { + throw new Error('Use either env or modEnv but not both'); + } + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(`💻 ${command.join(' ')}\n`); + const env = (_b = options.env) !== null && _b !== void 0 ? _b : (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); + const child = child_process.spawn(command[0], command.slice(1), { + ...options, + env, + // Need this for Windows where we want .cmd and .bat to be found as well. + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + return new Promise((resolve, reject) => { + const stdout = new Array(); + const stderr = new Array(); + child.stdout.on('data', chunk => { + var _a; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + stdout.push(chunk); + }); + child.stderr.on('data', chunk => { + var _a, _b; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + if ((_b = options.captureStderr) !== null && _b !== void 0 ? _b : true) { + stderr.push(chunk); + } + }); + child.once('error', reject); + child.once('close', code => { + if (code === 0 || options.allowErrExit) { + resolve((Buffer.concat(stdout).toString('utf-8') + Buffer.concat(stderr).toString('utf-8')).trim()); + } + else { + reject(new Error(`'${command.join(' ')}' exited with error code ${code}`)); + } + }); + }); +} +exports.shell = shell; +function defined(x) { + return x !== undefined; +} +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +function rimraf(fsPath) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } + else { + fs.unlinkSync(fsPath); + } + } + catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { + throw e; + } + } +} +exports.rimraf = rimraf; +function randomString() { + // Crazy + return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} +exports.randomString = randomString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsK0NBQStDO0FBQy9DLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtDQUE0RDtBQUM1RCxtREFBK0M7QUFHL0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXO0lBQ3JDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0lBQ3BDLENBQUMsQ0FBQyxhQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxtQ0FBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixtQ0FBSSxXQUFXLENBQUMsQ0FBQztBQUU5RSxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsT0FBTyxJQUFJLENBQUMsQ0FBQztBQUVwRCxNQUFNLFdBQVcsR0FBRyxJQUFJLDRCQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7QUFLOUM7Ozs7R0FJRztBQUNILFNBQWdCLE9BQU8sQ0FBd0IsS0FBaUQ7SUFDOUYsT0FBTyxDQUFDLE9BQVUsRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDeEQsTUFBTSxHQUFHLEdBQUcsTUFBTSx3QkFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQy9ELE1BQU0sV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXZCLE9BQU8sS0FBSyxDQUFDLEVBQUUsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFQRCwwQkFPQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxTQUFnQixVQUFVLENBQXFDLEtBQThD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QjtRQUN6QixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQUUsTUFBTSxDQUFDLENBQUM7U0FBRTtLQUN0QztBQUNILENBQUM7QUFoQkQsd0JBZ0JDO0FBRUQsU0FBZ0IsWUFBWTtJQUMxQixRQUFRO0lBQ1IsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDL0QsQ0FBQztBQUhELG9DQUdDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2hpbGRfcHJvY2VzcyBmcm9tICdjaGlsZF9wcm9jZXNzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBvdXRwdXRGcm9tU3RhY2ssIEF3c0NsaWVudHMgfSBmcm9tICcuL2F3cy1oZWxwZXJzJztcbmltcG9ydCB7IFJlc291cmNlUG9vbCB9IGZyb20gJy4vcmVzb3VyY2UtcG9vbCc7XG5pbXBvcnQgeyBUZXN0Q29udGV4dCB9IGZyb20gJy4vdGVzdC1oZWxwZXJzJztcblxuY29uc3QgUkVHSU9OUyA9IHByb2Nlc3MuZW52LkFXU19SRUdJT05TXG4gID8gcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTlMuc3BsaXQoJywnKVxuICA6IFtwcm9jZXNzLmVudi5BV1NfUkVHSU9OID8/IHByb2Nlc3MuZW52LkFXU19ERUZBVUxUX1JFR0lPTiA/PyAndXMtZWFzdC0xJ107XG5cbnByb2Nlc3Muc3Rkb3V0LndyaXRlKGBVc2luZyByZWdpb25zOiAke1JFR0lPTlN9XFxuYCk7XG5cbmNvbnN0IFJFR0lPTl9QT09MID0gbmV3IFJlc291cmNlUG9vbChSRUdJT05TKTtcblxuXG5leHBvcnQgdHlwZSBBd3NDb250ZXh0ID0geyByZWFkb25seSBhd3M6IEF3c0NsaWVudHMgfTtcblxuLyoqXG4gKiBIaWdoZXIgb3JkZXIgZnVuY3Rpb24gdG8gZXhlY3V0ZSBhIGJsb2NrIHdpdGggYW4gQVdTIGNsaWVudCBzZXR1cFxuICpcbiAqIEFsbG9jYXRlIHRoZSBuZXh0IHJlZ2lvbiBmcm9tIHRoZSBSRUdJT04gcG9vbCBhbmQgZGlzcG9zZSBpdCBhZnRlcndhcmRzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aEF3czxBIGV4dGVuZHMgVGVzdENvbnRleHQ+KGJsb2NrOiAoY29udGV4dDogQSAmIEF3c0NvbnRleHQpID0+IFByb21pc2U8dm9pZD4pIHtcbiAgcmV0dXJuIChjb250ZXh0OiBBKSA9PiBSRUdJT05fUE9PTC51c2luZyhhc3luYyAocmVnaW9uKSA9PiB7XG4gICAgY29uc3QgYXdzID0gYXdhaXQgQXdzQ2xpZW50cy5mb3JSZWdpb24ocmVnaW9uLCBjb250ZXh0Lm91dHB1dCk7XG4gICAgYXdhaXQgc2FuaXR5Q2hlY2soYXdzKTtcblxuICAgIHJldHVybiBibG9jayh7IC4uLmNvbnRleHQsIGF3cyB9KTtcbiAgfSk7XG59XG5cbi8qKlxuICogSGlnaGVyIG9yZGVyIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgYSBibG9jayB3aXRoIGEgQ0RLIGFwcCBmaXh0dXJlXG4gKlxuICogUmVxdWlyZXMgYW4gQVdTIGNsaWVudCB0byBiZSBwYXNzZWQgaW4uXG4gKlxuICogRm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5IHdpdGggZXhpc3RpbmcgdGVzdHMgKHNvIHdlIGRvbid0IGhhdmUgdG8gY2hhbmdlXG4gKiB0b28gbXVjaCkgdGhlIGlubmVyIGJsb2NrIGlzIGV4cGVjdGUgdG8gdGFrZSBhIGBUZXN0Rml4dHVyZWAgb2JqZWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aENka0FwcDxBIGV4dGVuZHMgVGVzdENvbnRleHQgJiBBd3NDb250ZXh0PihibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiBhc3luYyAoY29udGV4dDogQSkgPT4ge1xuICAgIGNvbnN0IHJhbmR5ID0gcmFuZG9tU3RyaW5nKCk7XG4gICAgY29uc3Qgc3RhY2tOYW1lUHJlZml4ID0gYGNka3Rlc3QtJHtyYW5keX1gO1xuICAgIGNvbnN0IGludGVnVGVzdERpciA9IHBhdGguam9pbihvcy50bXBkaXIoKSwgYGNkay1pbnRlZy0ke3JhbmR5fWApO1xuXG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBTdGFjayBwcmVmaXg6ICAgJHtzdGFja05hbWVQcmVmaXh9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBUZXN0IGRpcmVjdG9yeTogJHtpbnRlZ1Rlc3REaXJ9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBSZWdpb246ICAgICAgICAgJHtjb250ZXh0LmF3cy5yZWdpb259XFxuYCk7XG5cbiAgICBhd2FpdCBjbG9uZURpcmVjdG9yeShwYXRoLmpvaW4oX19kaXJuYW1lLCAnYXBwJyksIGludGVnVGVzdERpciwgY29udGV4dC5vdXRwdXQpO1xuICAgIGNvbnN0IGZpeHR1cmUgPSBuZXcgVGVzdEZpeHR1cmUoXG4gICAgICBpbnRlZ1Rlc3REaXIsXG4gICAgICBzdGFja05hbWVQcmVmaXgsXG4gICAgICBjb250ZXh0Lm91dHB1dCxcbiAgICAgIGNvbnRleHQuYXdzKTtcblxuICAgIGxldCBzdWNjZXNzID0gdHJ1ZTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ25wbScsICdpbnN0YWxsJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2NvcmUnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNucycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtaWFtJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNzbScsXG4gICAgICAgICdAYXdzLWNkay9hd3MtZWNyLWFzc2V0cycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtY2xvdWRmb3JtYXRpb24nLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWVjMiddKTtcblxuICAgICAgYXdhaXQgZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmUpO1xuXG4gICAgICBhd2FpdCBibG9jayhmaXh0dXJlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzdWNjZXNzID0gZmFsc2U7XG4gICAgICB0aHJvdyBlO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBhd2FpdCBmaXh0dXJlLmRpc3Bvc2Uoc3VjY2Vzcyk7XG4gICAgfVxuICB9O1xufVxuXG4vKipcbiAqIERlZmF1bHQgdGVzdCBmaXh0dXJlIGZvciBtb3N0IChhbGw/KSBpbnRlZyB0ZXN0c1xuICpcbiAqIEl0J3MgYSBjb21wb3NpdGlvbiBvZiB3aXRoQXdzL3dpdGhDZGtBcHAsIGV4cGVjdGluZyB0aGUgdGVzdCBibG9jayB0byB0YWtlIGEgYFRlc3RGaXh0dXJlYFxuICogb2JqZWN0LlxuICpcbiAqIFdlIGNvdWxkIGhhdmUgcHV0IGB3aXRoQXdzKHdpdGhDZGtBcHAoZml4dHVyZSA9PiB7IC8uLi4gYWN0dWFsIHRlc3QgaGVyZS4uLi8gfSkpYCBpbiBldmVyeVxuICogdGVzdCBkZWNsYXJhdGlvbiBidXQgY2VudHJhbGl6aW5nIGl0IGlzIGdvaW5nIHRvIG1ha2UgaXQgY29udmVuaWVudCB0byBtb2RpZnkgaW4gdGhlIGZ1dHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdpdGhEZWZhdWx0Rml4dHVyZShibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiB3aXRoQXdzPFRlc3RDb250ZXh0Pih3aXRoQ2RrQXBwKGJsb2NrKSk7XG4gIC8vICAgICAgICAgICAgICBefn5+fn4gdGhpcyBpcyBkaXNhcHBvaW50aW5nIFR5cGVTY3JpcHQhIEZlZWxzIGxpa2UgeW91IHNob3VsZCBoYXZlIGJlZW4gYWJsZSB0byBkZXJpdmUgdGhpcy5cbn1cblxuZXhwb3J0IGludGVyZmFjZSBTaGVsbE9wdGlvbnMgZXh0ZW5kcyBjaGlsZF9wcm9jZXNzLlNwYXduT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBQcm9wZXJ0aWVzIHRvIGFkZCB0byAnZW52J1xuICAgKi9cbiAgbW9kRW52PzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcblxuICAvKipcbiAgICogRG9uJ3QgZmFpbCB3aGVuIGV4aXRpbmcgd2l0aCBhbiBlcnJvclxuICAgKlxuICAgKiBAZGVmYXVsdCBmYWxzZVxuICAgKi9cbiAgYWxsb3dFcnJFeGl0PzogYm9vbGVhbjtcblxuICAvKipcbiAgICogV2hldGhlciB0byBjYXB0dXJlIHN0ZGVyclxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICBjYXB0dXJlU3RkZXJyPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogUGFzcyBvdXRwdXQgaGVyZVxuICAgKi9cbiAgb3V0cHV0PzogTm9kZUpTLldyaXRhYmxlU3RyZWFtO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENka0NsaU9wdGlvbnMgZXh0ZW5kcyBTaGVsbE9wdGlvbnMge1xuICBvcHRpb25zPzogc3RyaW5nW107XG4gIG5ldmVyUmVxdWlyZUFwcHJvdmFsPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBQcmVwYXJlIGEgdGFyZ2V0IGRpciBieXJlcGxpY2F0aW5nIGEgc291cmNlIGRpcmVjdG9yeVxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2xvbmVEaXJlY3Rvcnkoc291cmNlOiBzdHJpbmcsIHRhcmdldDogc3RyaW5nLCBvdXRwdXQ/OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0pIHtcbiAgYXdhaXQgc2hlbGwoWydybScsICctcmYnLCB0YXJnZXRdLCB7IG91dHB1dCB9KTtcbiAgYXdhaXQgc2hlbGwoWydta2RpcicsICctcCcsIHRhcmdldF0sIHsgb3V0cHV0IH0pO1xuICBhd2FpdCBzaGVsbChbJ2NwJywgJy1SJywgc291cmNlICsgJy8qJywgdGFyZ2V0XSwgeyBvdXRwdXQgfSk7XG59XG5cbmV4cG9ydCBjbGFzcyBUZXN0Rml4dHVyZSB7XG4gIHB1YmxpYyByZWFkb25seSBxdWFsaWZpZXIgPSByYW5kb21TdHJpbmcoKS5zdWJzdHIoMCwgMTApO1xuICBwcml2YXRlIHJlYWRvbmx5IGJ1Y2tldHNUb0RlbGV0ZSA9IG5ldyBBcnJheTxzdHJpbmc+KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHVibGljIHJlYWRvbmx5IGludGVnVGVzdERpcjogc3RyaW5nLFxuICAgIHB1YmxpYyByZWFkb25seSBzdGFja05hbWVQcmVmaXg6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgb3V0cHV0OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0sXG4gICAgcHVibGljIHJlYWRvbmx5IGF3czogQXdzQ2xpZW50cykge1xuICB9XG5cbiAgcHVibGljIGxvZyhzOiBzdHJpbmcpIHtcbiAgICB0aGlzLm91dHB1dC53cml0ZShgJHtzfVxcbmApO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIHNoZWxsKGNvbW1hbmQ6IHN0cmluZ1tdLCBvcHRpb25zOiBPbWl0PFNoZWxsT3B0aW9ucywgJ2N3ZCd8J291dHB1dCc+ID0ge30pOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiBzaGVsbChjb21tYW5kLCB7XG4gICAgICBvdXRwdXQ6IHRoaXMub3V0cHV0LFxuICAgICAgY3dkOiB0aGlzLmludGVnVGVzdERpcixcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgY2RrRGVwbG95KHN0YWNrTmFtZXM6IHN0cmluZyB8IHN0cmluZ1tdLCBvcHRpb25zOiBDZGtDbGlPcHRpb25zID0ge30pIHtcbiAgICBzdGFja05hbWVzID0gdHlwZW9mIHN0YWNrTmFtZXMgPT09ICdzdHJpbmcnID8gW3N0YWNrTmFtZXNdIDogc3RhY2tOYW1lcztcblxuICAgIGNvbnN0IG5ldmVyUmVxdWlyZUFwcHJvdmFsID0gb3B0aW9ucy5uZXZlclJlcXVpcmVBcHByb3ZhbCA/PyB0cnVlO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVwbG95JyxcbiAgICAgIC4uLihuZXZlclJlcXVpcmVBcHByb3ZhbCA/IFsnLS1yZXF1aXJlLWFwcHJvdmFsPW5ldmVyJ10gOiBbXSksIC8vIERlZmF1bHQgdG8gbm8gYXBwcm92YWwgaW4gYW4gdW5hdHRlbmRlZCB0ZXN0XG4gICAgICAuLi4ob3B0aW9ucy5vcHRpb25zID8/IFtdKSxcbiAgICAgIC4uLnRoaXMuZnVsbFN0YWNrTmFtZShzdGFja05hbWVzKV0sIG9wdGlvbnMpO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIGNka0Rlc3Ryb3koc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHN0YWNrTmFtZXMgPSB0eXBlb2Ygc3RhY2tOYW1lcyA9PT0gJ3N0cmluZycgPyBbc3RhY2tOYW1lc10gOiBzdGFja05hbWVzO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVzdHJveScsXG4gICAgICAnLWYnLCAvLyBXZSBuZXZlciB3YW50IGEgcHJvbXB0IGluIGFuIHVuYXR0ZW5kZWQgdGVzdFxuICAgICAgLi4uKG9wdGlvbnMub3B0aW9ucyA/PyBbXSksXG4gICAgICAuLi50aGlzLmZ1bGxTdGFja05hbWUoc3RhY2tOYW1lcyldLCBvcHRpb25zKTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBjZGsoYXJnczogc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHJldHVybiB0aGlzLnNoZWxsKFsnY2RrJywgLi4uYXJnc10sIHtcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgICBtb2RFbnY6IHtcbiAgICAgICAgQVdTX1JFR0lPTjogdGhpcy5hd3MucmVnaW9uLFxuICAgICAgICBBV1NfREVGQVVMVF9SRUdJT046IHRoaXMuYXdzLnJlZ2lvbixcbiAgICAgICAgU1RBQ0tfTkFNRV9QUkVGSVg6IHRoaXMuc3RhY2tOYW1lUHJlZml4LFxuICAgICAgICAuLi5vcHRpb25zLm1vZEVudixcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgZnVsbFN0YWNrTmFtZShzdGFja05hbWU6IHN0cmluZyk6IHN0cmluZztcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nW10pOiBzdHJpbmdbXTtcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10pOiBzdHJpbmcgfCBzdHJpbmdbXSB7XG4gICAgaWYgKHR5cGVvZiBzdGFja05hbWVzID09PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuIGAke3RoaXMuc3RhY2tOYW1lUHJlZml4fS0ke3N0YWNrTmFtZXN9YDtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHN0YWNrTmFtZXMubWFwKHMgPT4gYCR7dGhpcy5zdGFja05hbWVQcmVmaXh9LSR7c31gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQXBwZW5kIHRoaXMgdG8gdGhlIGxpc3Qgb2YgYnVja2V0cyB0byBwb3RlbnRpYWxseSBkZWxldGVcbiAgICpcbiAgICogQXQgdGhlIGVuZCBvZiBhIHRlc3QsIHdlIGNsZWFuIHVwIGJ1Y2tldHMgdGhhdCBtYXkgbm90IGhhdmUgZ290dGVuIGRlc3Ryb3llZFxuICAgKiAoZm9yIHdoYXRldmVyIHJlYXNvbikuXG4gICAqL1xuICBwdWJsaWMgcmVtZW1iZXJUb0RlbGV0ZUJ1Y2tldChidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgICB0aGlzLmJ1Y2tldHNUb0RlbGV0ZS5wdXNoKGJ1Y2tldE5hbWUpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFudXAgbGVmdG92ZXIgc3RhY2tzIGFuZCBidWNrZXRzXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgZGlzcG9zZShzdWNjZXNzOiBib29sZWFuKSB7XG4gICAgY29uc3Qgc3RhY2tzVG9EZWxldGUgPSBhd2FpdCB0aGlzLmRlbGV0ZWFibGVTdGFja3ModGhpcy5zdGFja05hbWVQcmVmaXgpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIGJ1Y2tldHMgdGhhdCBuZWVkIHRvIGJlIGNsZWFuZWRcbiAgICBjb25zdCBidWNrZXROYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0J1Y2tldE5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChidWNrZXROYW1lcy5tYXAoYiA9PiB0aGlzLmF3cy5lbXB0eUJ1Y2tldChiKSkpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIEVDUiByZXBvc2l0b3JpZXMgd2l0aCBpbWFnZXMgd2hpY2ggc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICBjb25zdCBpbWFnZVJlcG9zaXRvcnlOYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0ltYWdlUmVwb3NpdG9yeU5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChpbWFnZVJlcG9zaXRvcnlOYW1lcy5tYXAociA9PiB0aGlzLmF3cy5kZWxldGVJbWFnZVJlcG9zaXRvcnkocikpKTtcblxuICAgIGF3YWl0IHRoaXMuYXdzLmRlbGV0ZVN0YWNrcyguLi5zdGFja3NUb0RlbGV0ZS5tYXAocyA9PiBzLlN0YWNrTmFtZSkpO1xuXG4gICAgLy8gV2UgbWlnaHQgaGF2ZSBsZWFrZWQgc29tZSBidWNrZXRzIGJ5IHVwZ3JhZGluZyB0aGUgYm9vdHN0cmFwIHN0YWNrLiBCZVxuICAgIC8vIHN1cmUgdG8gY2xlYW4gZXZlcnl0aGluZy5cbiAgICBmb3IgKGNvbnN0IGJ1Y2tldCBvZiB0aGlzLmJ1Y2tldHNUb0RlbGV0ZSkge1xuICAgICAgYXdhaXQgdGhpcy5hd3MuZGVsZXRlQnVja2V0KGJ1Y2tldCk7XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIHRlc3RzIGNvbXBsZXRlZCBzdWNjZXNzZnVsbHksIGhhcHBpbHkgZGVsZXRlIHRoZSBmaXh0dXJlXG4gICAgLy8gKG90aGVyd2lzZSBsZWF2ZSBpdCBmb3IgaHVtYW5zIHRvIGluc3BlY3QpXG4gICAgaWYgKHN1Y2Nlc3MpIHtcbiAgICAgIHJpbXJhZih0aGlzLmludGVnVGVzdERpcik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiB0aGUgc3RhY2tzIHN0YXJ0aW5nIHdpdGggb3VyIHRlc3RpbmcgcHJlZml4IHRoYXQgc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZGVsZXRlYWJsZVN0YWNrcyhwcmVmaXg6IHN0cmluZyk6IFByb21pc2U8QVdTLkNsb3VkRm9ybWF0aW9uLlN0YWNrW10+IHtcbiAgICBjb25zdCBzdGF0dXNGaWx0ZXIgPSBbXG4gICAgICAnQ1JFQVRFX0lOX1BST0dSRVNTJywgJ0NSRUFURV9GQUlMRUQnLCAnQ1JFQVRFX0NPTVBMRVRFJyxcbiAgICAgICdST0xMQkFDS19JTl9QUk9HUkVTUycsICdST0xMQkFDS19GQUlMRUQnLCAnUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgICAgJ0RFTEVURV9GQUlMRUQnLFxuICAgICAgJ1VQREFURV9JTl9QUk9HUkVTUycsICdVUERBVEVfQ09NUExFVEVfQ0xFQU5VUF9JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX0NPTVBMRVRFJywgJ1VQREFURV9ST0xMQkFDS19JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0ZBSUxFRCcsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFX0NMRUFOVVBfSU5fUFJPR1JFU1MnLFxuICAgICAgJ1VQREFURV9ST0xMQkFDS19DT01QTEVURScsICdSRVZJRVdfSU5fUFJPR1JFU1MnLFxuICAgICAgJ0lNUE9SVF9JTl9QUk9HUkVTUycsICdJTVBPUlRfQ09NUExFVEUnLFxuICAgICAgJ0lNUE9SVF9ST0xMQkFDS19JTl9QUk9HUkVTUycsICdJTVBPUlRfUk9MTEJBQ0tfRkFJTEVEJyxcbiAgICAgICdJTVBPUlRfUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgIF07XG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRoaXMuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHt9KTtcblxuICAgIHJldHVybiAocmVzcG9uc2UuU3RhY2tzID8/IFtdKVxuICAgICAgLmZpbHRlcihzID0+IHMuU3RhY2tOYW1lLnN0YXJ0c1dpdGgocHJlZml4KSlcbiAgICAgIC5maWx0ZXIocyA9PiBzdGF0dXNGaWx0ZXIuaW5jbHVkZXMocy5TdGFja1N0YXR1cykpXG4gICAgICAuZmlsdGVyKHMgPT4gcy5Sb290SWQgPT09IHVuZGVmaW5lZCk7IC8vIE9ubHkgZGVsZXRlIHBhcmVudCBzdGFja3MuIE5lc3RlZCBzdGFja3MgYXJlIGRlbGV0ZWQgaW4gdGhlIHByb2Nlc3NcbiAgfVxufVxuXG4vKipcbiAqIFBlcmZvcm0gYSBvbmUtdGltZSBxdWljayBzYW5pdHkgY2hlY2sgdGhhdCB0aGUgQVdTIGNsaWVudHMgaGFzIHByb3Blcmx5IGNvbmZpZ3VyZWQgY3JlZGVudGlhbHNcbiAqXG4gKiBJZiB3ZSBkb24ndCBkbyB0aGlzLCBjYWxscyBhcmUgZ29pbmcgdG8gZmFpbCBhbmQgdGhleSdsbCBiZSByZXRyaWVkIGFuZCBldmVyeXRoaW5nIHdpbGwgdGFrZVxuICogZm9yZXZlciBiZWZvcmUgdGhlIHVzZXIgbm90aWNlcyBhIHNpbXBsZSBtaXNjb25maWd1cmF0aW9uLlxuICpcbiAqIFdlIGNhbid0IGNoZWNrIGZvciB0aGUgcHJlc2VuY2Ugb2YgZW52aXJvbm1lbnQgdmFyaWFibGVzIHNpbmNlIGNyZWRlbnRpYWxzIGNvdWxkIGNvbWUgZnJvbVxuICogYW55d2hlcmUsIHNvIGRvIHNpbXBsZSBhY2NvdW50IHJldHJpZXZhbC5cbiAqXG4gKiBPbmx5IGRvIGl0IG9uY2UgcGVyIHByb2Nlc3MuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHNhbml0eUNoZWNrKGF3czogQXdzQ2xpZW50cykge1xuICBpZiAoc2FuaXR5Q2hlY2tlZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGF3cy5hY2NvdW50KCk7XG4gICAgICBzYW5pdHlDaGVja2VkID0gdHJ1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzYW5pdHlDaGVja2VkID0gZmFsc2U7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEFXUyBjcmVkZW50aWFscyBwcm9iYWJseSBub3QgY29uZmlndXJlZCwgZ290IGVycm9yOiAke2UubWVzc2FnZX1gKTtcbiAgICB9XG4gIH1cbiAgaWYgKCFzYW5pdHlDaGVja2VkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdBV1MgY3JlZGVudGlhbHMgcHJvYmFibHkgbm90IGNvbmZpZ3VyZWQsIHNlZSBwcmV2aW91cyBlcnJvcicpO1xuICB9XG59XG5sZXQgc2FuaXR5Q2hlY2tlZDogYm9vbGVhbiB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBNYWtlIHN1cmUgdGhhdCB0aGUgZ2l2ZW4gZW52aXJvbm1lbnQgaXMgYm9vdHN0cmFwcGVkXG4gKlxuICogU2luY2Ugd2UgZ28gc3RyaXBpbmcgYWNyb3NzIHJlZ2lvbnMsIGl0J3MgZ29pbmcgdG8gc3VjayBkb2luZyB0aGlzXG4gKiBieSBoYW5kIHNvIGxldCdzIGp1c3QgbWFzcy1hdXRvbWF0ZSBpdC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmU6IFRlc3RGaXh0dXJlKSB7XG4gIC8vIE9sZC1zdHlsZSBib290c3RyYXAgc3RhY2sgd2l0aCBkZWZhdWx0IG5hbWVcbiAgaWYgKGF3YWl0IGZpeHR1cmUuYXdzLnN0YWNrU3RhdHVzKCdDREtUb29sa2l0JykgPT09IHVuZGVmaW5lZCkge1xuICAgIGF3YWl0IGZpeHR1cmUuY2RrKFsnYm9vdHN0cmFwJywgYGF3czovLyR7YXdhaXQgZml4dHVyZS5hd3MuYWNjb3VudCgpfS8ke2ZpeHR1cmUuYXdzLnJlZ2lvbn1gXSk7XG4gIH1cbn1cblxuLyoqXG4gKiBBIHNoZWxsIGNvbW1hbmQgdGhhdCBkb2VzIHdoYXQgeW91IHdhbnRcbiAqXG4gKiBJcyBwbGF0Zm9ybS1hd2FyZSwgaGFuZGxlcyBlcnJvcnMgbmljZWx5LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2hlbGwoY29tbWFuZDogc3RyaW5nW10sIG9wdGlvbnM6IFNoZWxsT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgaWYgKG9wdGlvbnMubW9kRW52ICYmIG9wdGlvbnMuZW52KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdVc2UgZWl0aGVyIGVudiBvciBtb2RFbnYgYnV0IG5vdCBib3RoJyk7XG4gIH1cblxuICBvcHRpb25zLm91dHB1dD8ud3JpdGUoYPCfkrsgJHtjb21tYW5kLmpvaW4oJyAnKX1cXG5gKTtcblxuICBjb25zdCBlbnYgPSBvcHRpb25zLmVudiA/PyAob3B0aW9ucy5tb2RFbnYgPyB7IC4uLnByb2Nlc3MuZW52LCAuLi5vcHRpb25zLm1vZEVudiB9IDogdW5kZWZpbmVkKTtcblxuICBjb25zdCBjaGlsZCA9IGNoaWxkX3Byb2Nlc3Muc3Bhd24oY29tbWFuZFswXSwgY29tbWFuZC5zbGljZSgxKSwge1xuICAgIC4uLm9wdGlvbnMsXG4gICAgZW52LFxuICAgIC8vIE5lZWQgdGhpcyBmb3IgV2luZG93cyB3aGVyZSB3ZSB3YW50IC5jbWQgYW5kIC5iYXQgdG8gYmUgZm91bmQgYXMgd2VsbC5cbiAgICBzaGVsbDogdHJ1ZSxcbiAgICBzdGRpbzogWydpZ25vcmUnLCAncGlwZScsICdwaXBlJ10sXG4gIH0pO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZTxzdHJpbmc+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICBjb25zdCBzdGRvdXQgPSBuZXcgQXJyYXk8QnVmZmVyPigpO1xuICAgIGNvbnN0IHN0ZGVyciA9IG5ldyBBcnJheTxCdWZmZXI+KCk7XG5cbiAgICBjaGlsZC5zdGRvdXQhLm9uKCdkYXRhJywgY2h1bmsgPT4ge1xuICAgICAgb3B0aW9ucy5vdXRwdXQ/LndyaXRlKGNodW5rKTtcbiAgICAgIHN0ZG91dC5wdXNoKGNodW5rKTtcbiAgICB9KTtcblxuICAgIGNoaWxkLnN0ZGVyciEub24oJ2RhdGEnLCBjaHVuayA9PiB7XG4gICAgICBvcHRpb25zLm91dHB1dD8ud3JpdGUoY2h1bmspO1xuICAgICAgaWYgKG9wdGlvbnMuY2FwdHVyZVN0ZGVyciA/PyB0cnVlKSB7XG4gICAgICAgIHN0ZGVyci5wdXNoKGNodW5rKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Vycm9yJywgcmVqZWN0KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Nsb3NlJywgY29kZSA9PiB7XG4gICAgICBpZiAoY29kZSA9PT0gMCB8fCBvcHRpb25zLmFsbG93RXJyRXhpdCkge1xuICAgICAgICByZXNvbHZlKChCdWZmZXIuY29uY2F0KHN0ZG91dCkudG9TdHJpbmcoJ3V0Zi04JykgKyBCdWZmZXIuY29uY2F0KHN0ZGVycikudG9TdHJpbmcoJ3V0Zi04JykpLnRyaW0oKSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGAnJHtjb21tYW5kLmpvaW4oJyAnKX0nIGV4aXRlZCB3aXRoIGVycm9yIGNvZGUgJHtjb2RlfWApKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIGRlZmluZWQ8QT4oeDogQSk6IHggaXMgTm9uTnVsbGFibGU8QT4ge1xuICByZXR1cm4geCAhPT0gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIHJtIC1yZiByZWltcGxlbWVudGF0aW9uLCBkb24ndCB3YW50IHRvIGRlcGVuZCBvbiBhbiBOUE0gcGFja2FnZSBmb3IgdGhpc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcmltcmFmKGZzUGF0aDogc3RyaW5nKSB7XG4gIHRyeSB7XG4gICAgY29uc3QgaXNEaXIgPSBmcy5sc3RhdFN5bmMoZnNQYXRoKS5pc0RpcmVjdG9yeSgpO1xuXG4gICAgaWYgKGlzRGlyKSB7XG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZnMucmVhZGRpclN5bmMoZnNQYXRoKSkge1xuICAgICAgICByaW1yYWYocGF0aC5qb2luKGZzUGF0aCwgZmlsZSkpO1xuICAgICAgfVxuICAgICAgZnMucm1kaXJTeW5jKGZzUGF0aCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZzLnVubGlua1N5bmMoZnNQYXRoKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICAvLyBXZSB3aWxsIHN1cnZpdmUgRU5PRU5UXG4gICAgaWYgKGUuY29kZSAhPT0gJ0VOT0VOVCcpIHsgdGhyb3cgZTsgfVxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByYW5kb21TdHJpbmcoKSB7XG4gIC8vIENyYXp5XG4gIHJldHVybiBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5yZXBsYWNlKC9bXmEtejAtOV0rL2csICcnKTtcbn0iXX0= diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index 66c6799164fd8..30828ce0f7522 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -178,7 +178,7 @@ export class TestFixture { } public async cdk(args: string[], options: CdkCliOptions = {}) { - return this.shell(['cdk', ...args], { + return this.shell(['cdk', '-v', ...args], { ...options, modEnv: { AWS_REGION: this.aws.region, From e9b5b8c16c11b6dab37d8d9c7fdba2265621eae7 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 24 Sep 2020 15:14:20 +0200 Subject: [PATCH 05/46] fix(core): bundling with staging disabled returns a relative path (#10507) The change introduced in #9576 did not handle the "staging disabled" case. As a consequence, when bundling the staged path was always relative. Revert to the behavior that was present before this change: when staging is disabled the staged path is absolute (whether bundling or not). Closes #10367 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/asset-staging.ts | 14 +++++++------- packages/@aws-cdk/core/test/test.staging.ts | 10 +++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index e5878c2a31365..53f607f9588a0 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -120,14 +120,14 @@ export class AssetStaging extends Construct { } } else { this.assetHash = this.calculateHash(hashType, props.assetHash); + this.relativePath = renderAssetFilename(this.assetHash, path.extname(this.sourcePath)); + this.stagedPath = this.relativePath; + } - const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); - if (stagingDisabled) { - this.stagedPath = this.sourcePath; - } else { - this.relativePath = renderAssetFilename(this.assetHash, path.extname(this.sourcePath)); - this.stagedPath = this.relativePath; - } + const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); + if (stagingDisabled) { + this.relativePath = undefined; + this.stagedPath = this.bundleDir ?? this.sourcePath; } this.sourceHash = this.assetHash; diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index e652c884935f4..6d649325db94e 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -148,7 +148,7 @@ export = { const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); // WHEN - new AssetStaging(stack, 'Asset', { + const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), @@ -167,6 +167,14 @@ export = { 'tree.json', ]); + test.equal(asset.sourceHash, 'b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4'); + test.equal(asset.sourcePath, directory); + + const resolvedStagePath = stack.resolve(asset.stagedPath); + // absolute path ending with bundling dir + test.ok(path.isAbsolute(resolvedStagePath)); + test.ok(new RegExp('asset.b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4$').test(resolvedStagePath)); + test.done(); }, From 8ec1cfe67cc03f34f9ba436d5092209649bb73e3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 24 Sep 2020 15:43:32 +0200 Subject: [PATCH 06/46] chore(integ): revert run all CDK integ tests with `-v` (#10511) Reverts aws/aws-cdk#10503 We can't actually do this. There are tests that check that the output of the `cdk` command is *exactly* "some value", and adding the logging in breaks the expectation. Revert the `-v` to allow the tests to go back to passing 90% of the time. --- .../cli-regression-patches/v1.64.0/NOTES.md | 3 - .../v1.64.0/cdk-helpers.js | 322 ------------------ .../aws-cdk/test/integ/cli/cdk-helpers.ts | 2 +- 3 files changed, 1 insertion(+), 326 deletions(-) delete mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md delete mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md deleted file mode 100644 index ade25e2ca159c..0000000000000 --- a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md +++ /dev/null @@ -1,3 +0,0 @@ -Added a `-v` switch to the cdk executions that also needs to be -applied to the regression tesets so we have a better chance -of catching sporadically failing tests in the act. diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js deleted file mode 100644 index 1b8bea33bf320..0000000000000 --- a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js +++ /dev/null @@ -1,322 +0,0 @@ -"use strict"; -var _a, _b; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.randomString = exports.rimraf = exports.shell = exports.TestFixture = exports.cloneDirectory = exports.withDefaultFixture = exports.withCdkApp = exports.withAws = void 0; -const child_process = require("child_process"); -const fs = require("fs"); -const os = require("os"); -const path = require("path"); -const aws_helpers_1 = require("./aws-helpers"); -const resource_pool_1 = require("./resource-pool"); -const REGIONS = process.env.AWS_REGIONS - ? process.env.AWS_REGIONS.split(',') - : [(_b = (_a = process.env.AWS_REGION) !== null && _a !== void 0 ? _a : process.env.AWS_DEFAULT_REGION) !== null && _b !== void 0 ? _b : 'us-east-1']; -process.stdout.write(`Using regions: ${REGIONS}\n`); -const REGION_POOL = new resource_pool_1.ResourcePool(REGIONS); -/** - * Higher order function to execute a block with an AWS client setup - * - * Allocate the next region from the REGION pool and dispose it afterwards. - */ -function withAws(block) { - return (context) => REGION_POOL.using(async (region) => { - const aws = await aws_helpers_1.AwsClients.forRegion(region, context.output); - await sanityCheck(aws); - return block({ ...context, aws }); - }); -} -exports.withAws = withAws; -/** - * Higher order function to execute a block with a CDK app fixture - * - * Requires an AWS client to be passed in. - * - * For backwards compatibility with existing tests (so we don't have to change - * too much) the inner block is expecte to take a `TestFixture` object. - */ -function withCdkApp(block) { - return async (context) => { - const randy = randomString(); - const stackNamePrefix = `cdktest-${randy}`; - const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); - context.output.write(` Stack prefix: ${stackNamePrefix}\n`); - context.output.write(` Test directory: ${integTestDir}\n`); - context.output.write(` Region: ${context.aws.region}\n`); - await cloneDirectory(path.join(__dirname, 'app'), integTestDir, context.output); - const fixture = new TestFixture(integTestDir, stackNamePrefix, context.output, context.aws); - let success = true; - try { - await fixture.shell(['npm', 'install', - '@aws-cdk/core', - '@aws-cdk/aws-sns', - '@aws-cdk/aws-iam', - '@aws-cdk/aws-lambda', - '@aws-cdk/aws-ssm', - '@aws-cdk/aws-ecr-assets', - '@aws-cdk/aws-cloudformation', - '@aws-cdk/aws-ec2']); - await ensureBootstrapped(fixture); - await block(fixture); - } - catch (e) { - success = false; - throw e; - } - finally { - await fixture.dispose(success); - } - }; -} -exports.withCdkApp = withCdkApp; -/** - * Default test fixture for most (all?) integ tests - * - * It's a composition of withAws/withCdkApp, expecting the test block to take a `TestFixture` - * object. - * - * We could have put `withAws(withCdkApp(fixture => { /... actual test here.../ }))` in every - * test declaration but centralizing it is going to make it convenient to modify in the future. - */ -function withDefaultFixture(block) { - return withAws(withCdkApp(block)); - // ^~~~~~ this is disappointing TypeScript! Feels like you should have been able to derive this. -} -exports.withDefaultFixture = withDefaultFixture; -/** - * Prepare a target dir byreplicating a source directory - */ -async function cloneDirectory(source, target, output) { - await shell(['rm', '-rf', target], { output }); - await shell(['mkdir', '-p', target], { output }); - await shell(['cp', '-R', source + '/*', target], { output }); -} -exports.cloneDirectory = cloneDirectory; -class TestFixture { - constructor(integTestDir, stackNamePrefix, output, aws) { - this.integTestDir = integTestDir; - this.stackNamePrefix = stackNamePrefix; - this.output = output; - this.aws = aws; - this.qualifier = randomString().substr(0, 10); - this.bucketsToDelete = new Array(); - } - log(s) { - this.output.write(`${s}\n`); - } - async shell(command, options = {}) { - return shell(command, { - output: this.output, - cwd: this.integTestDir, - ...options, - }); - } - async cdkDeploy(stackNames, options = {}) { - var _a, _b; - stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; - const neverRequireApproval = (_a = options.neverRequireApproval) !== null && _a !== void 0 ? _a : true; - return this.cdk(['deploy', - ...(neverRequireApproval ? ['--require-approval=never'] : []), // Default to no approval in an unattended test - ...((_b = options.options) !== null && _b !== void 0 ? _b : []), ...this.fullStackName(stackNames)], options); - } - async cdkDestroy(stackNames, options = {}) { - var _a; - stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; - return this.cdk(['destroy', - '-f', // We never want a prompt in an unattended test - ...((_a = options.options) !== null && _a !== void 0 ? _a : []), ...this.fullStackName(stackNames)], options); - } - async cdk(args, options = {}) { - return this.shell(['cdk', '-v', ...args], { - ...options, - modEnv: { - AWS_REGION: this.aws.region, - AWS_DEFAULT_REGION: this.aws.region, - STACK_NAME_PREFIX: this.stackNamePrefix, - ...options.modEnv, - }, - }); - } - fullStackName(stackNames) { - if (typeof stackNames === 'string') { - return `${this.stackNamePrefix}-${stackNames}`; - } - else { - return stackNames.map(s => `${this.stackNamePrefix}-${s}`); - } - } - /** - * Append this to the list of buckets to potentially delete - * - * At the end of a test, we clean up buckets that may not have gotten destroyed - * (for whatever reason). - */ - rememberToDeleteBucket(bucketName) { - this.bucketsToDelete.push(bucketName); - } - /** - * Cleanup leftover stacks and buckets - */ - async dispose(success) { - const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix); - // Bootstrap stacks have buckets that need to be cleaned - const bucketNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('BucketName', stack)).filter(defined); - await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b))); - // Bootstrap stacks have ECR repositories with images which should be deleted - const imageRepositoryNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('ImageRepositoryName', stack)).filter(defined); - await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r))); - await this.aws.deleteStacks(...stacksToDelete.map(s => s.StackName)); - // We might have leaked some buckets by upgrading the bootstrap stack. Be - // sure to clean everything. - for (const bucket of this.bucketsToDelete) { - await this.aws.deleteBucket(bucket); - } - // If the tests completed successfully, happily delete the fixture - // (otherwise leave it for humans to inspect) - if (success) { - rimraf(this.integTestDir); - } - } - /** - * Return the stacks starting with our testing prefix that should be deleted - */ - async deleteableStacks(prefix) { - var _a; - const statusFilter = [ - 'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE', - 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE', - 'DELETE_FAILED', - 'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS', - 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS', - 'UPDATE_ROLLBACK_FAILED', - 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', - 'UPDATE_ROLLBACK_COMPLETE', 'REVIEW_IN_PROGRESS', - 'IMPORT_IN_PROGRESS', 'IMPORT_COMPLETE', - 'IMPORT_ROLLBACK_IN_PROGRESS', 'IMPORT_ROLLBACK_FAILED', - 'IMPORT_ROLLBACK_COMPLETE', - ]; - const response = await this.aws.cloudFormation('describeStacks', {}); - return ((_a = response.Stacks) !== null && _a !== void 0 ? _a : []) - .filter(s => s.StackName.startsWith(prefix)) - .filter(s => statusFilter.includes(s.StackStatus)) - .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process - } -} -exports.TestFixture = TestFixture; -/** - * Perform a one-time quick sanity check that the AWS clients has properly configured credentials - * - * If we don't do this, calls are going to fail and they'll be retried and everything will take - * forever before the user notices a simple misconfiguration. - * - * We can't check for the presence of environment variables since credentials could come from - * anywhere, so do simple account retrieval. - * - * Only do it once per process. - */ -async function sanityCheck(aws) { - if (sanityChecked === undefined) { - try { - await aws.account(); - sanityChecked = true; - } - catch (e) { - sanityChecked = false; - throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); - } - } - if (!sanityChecked) { - throw new Error('AWS credentials probably not configured, see previous error'); - } -} -let sanityChecked; -/** - * Make sure that the given environment is bootstrapped - * - * Since we go striping across regions, it's going to suck doing this - * by hand so let's just mass-automate it. - */ -async function ensureBootstrapped(fixture) { - // Old-style bootstrap stack with default name - if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { - await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); - } -} -/** - * A shell command that does what you want - * - * Is platform-aware, handles errors nicely. - */ -async function shell(command, options = {}) { - var _a, _b; - if (options.modEnv && options.env) { - throw new Error('Use either env or modEnv but not both'); - } - (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(`💻 ${command.join(' ')}\n`); - const env = (_b = options.env) !== null && _b !== void 0 ? _b : (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); - const child = child_process.spawn(command[0], command.slice(1), { - ...options, - env, - // Need this for Windows where we want .cmd and .bat to be found as well. - shell: true, - stdio: ['ignore', 'pipe', 'pipe'], - }); - return new Promise((resolve, reject) => { - const stdout = new Array(); - const stderr = new Array(); - child.stdout.on('data', chunk => { - var _a; - (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); - stdout.push(chunk); - }); - child.stderr.on('data', chunk => { - var _a, _b; - (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); - if ((_b = options.captureStderr) !== null && _b !== void 0 ? _b : true) { - stderr.push(chunk); - } - }); - child.once('error', reject); - child.once('close', code => { - if (code === 0 || options.allowErrExit) { - resolve((Buffer.concat(stdout).toString('utf-8') + Buffer.concat(stderr).toString('utf-8')).trim()); - } - else { - reject(new Error(`'${command.join(' ')}' exited with error code ${code}`)); - } - }); - }); -} -exports.shell = shell; -function defined(x) { - return x !== undefined; -} -/** - * rm -rf reimplementation, don't want to depend on an NPM package for this - */ -function rimraf(fsPath) { - try { - const isDir = fs.lstatSync(fsPath).isDirectory(); - if (isDir) { - for (const file of fs.readdirSync(fsPath)) { - rimraf(path.join(fsPath, file)); - } - fs.rmdirSync(fsPath); - } - else { - fs.unlinkSync(fsPath); - } - } - catch (e) { - // We will survive ENOENT - if (e.code !== 'ENOENT') { - throw e; - } - } -} -exports.rimraf = rimraf; -function randomString() { - // Crazy - return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); -} -exports.randomString = randomString; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsK0NBQStDO0FBQy9DLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtDQUE0RDtBQUM1RCxtREFBK0M7QUFHL0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXO0lBQ3JDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0lBQ3BDLENBQUMsQ0FBQyxhQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxtQ0FBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixtQ0FBSSxXQUFXLENBQUMsQ0FBQztBQUU5RSxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsT0FBTyxJQUFJLENBQUMsQ0FBQztBQUVwRCxNQUFNLFdBQVcsR0FBRyxJQUFJLDRCQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7QUFLOUM7Ozs7R0FJRztBQUNILFNBQWdCLE9BQU8sQ0FBd0IsS0FBaUQ7SUFDOUYsT0FBTyxDQUFDLE9BQVUsRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDeEQsTUFBTSxHQUFHLEdBQUcsTUFBTSx3QkFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQy9ELE1BQU0sV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXZCLE9BQU8sS0FBSyxDQUFDLEVBQUUsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFQRCwwQkFPQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxTQUFnQixVQUFVLENBQXFDLEtBQThD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QjtRQUN6QixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQUUsTUFBTSxDQUFDLENBQUM7U0FBRTtLQUN0QztBQUNILENBQUM7QUFoQkQsd0JBZ0JDO0FBRUQsU0FBZ0IsWUFBWTtJQUMxQixRQUFRO0lBQ1IsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDL0QsQ0FBQztBQUhELG9DQUdDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2hpbGRfcHJvY2VzcyBmcm9tICdjaGlsZF9wcm9jZXNzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBvdXRwdXRGcm9tU3RhY2ssIEF3c0NsaWVudHMgfSBmcm9tICcuL2F3cy1oZWxwZXJzJztcbmltcG9ydCB7IFJlc291cmNlUG9vbCB9IGZyb20gJy4vcmVzb3VyY2UtcG9vbCc7XG5pbXBvcnQgeyBUZXN0Q29udGV4dCB9IGZyb20gJy4vdGVzdC1oZWxwZXJzJztcblxuY29uc3QgUkVHSU9OUyA9IHByb2Nlc3MuZW52LkFXU19SRUdJT05TXG4gID8gcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTlMuc3BsaXQoJywnKVxuICA6IFtwcm9jZXNzLmVudi5BV1NfUkVHSU9OID8/IHByb2Nlc3MuZW52LkFXU19ERUZBVUxUX1JFR0lPTiA/PyAndXMtZWFzdC0xJ107XG5cbnByb2Nlc3Muc3Rkb3V0LndyaXRlKGBVc2luZyByZWdpb25zOiAke1JFR0lPTlN9XFxuYCk7XG5cbmNvbnN0IFJFR0lPTl9QT09MID0gbmV3IFJlc291cmNlUG9vbChSRUdJT05TKTtcblxuXG5leHBvcnQgdHlwZSBBd3NDb250ZXh0ID0geyByZWFkb25seSBhd3M6IEF3c0NsaWVudHMgfTtcblxuLyoqXG4gKiBIaWdoZXIgb3JkZXIgZnVuY3Rpb24gdG8gZXhlY3V0ZSBhIGJsb2NrIHdpdGggYW4gQVdTIGNsaWVudCBzZXR1cFxuICpcbiAqIEFsbG9jYXRlIHRoZSBuZXh0IHJlZ2lvbiBmcm9tIHRoZSBSRUdJT04gcG9vbCBhbmQgZGlzcG9zZSBpdCBhZnRlcndhcmRzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aEF3czxBIGV4dGVuZHMgVGVzdENvbnRleHQ+KGJsb2NrOiAoY29udGV4dDogQSAmIEF3c0NvbnRleHQpID0+IFByb21pc2U8dm9pZD4pIHtcbiAgcmV0dXJuIChjb250ZXh0OiBBKSA9PiBSRUdJT05fUE9PTC51c2luZyhhc3luYyAocmVnaW9uKSA9PiB7XG4gICAgY29uc3QgYXdzID0gYXdhaXQgQXdzQ2xpZW50cy5mb3JSZWdpb24ocmVnaW9uLCBjb250ZXh0Lm91dHB1dCk7XG4gICAgYXdhaXQgc2FuaXR5Q2hlY2soYXdzKTtcblxuICAgIHJldHVybiBibG9jayh7IC4uLmNvbnRleHQsIGF3cyB9KTtcbiAgfSk7XG59XG5cbi8qKlxuICogSGlnaGVyIG9yZGVyIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgYSBibG9jayB3aXRoIGEgQ0RLIGFwcCBmaXh0dXJlXG4gKlxuICogUmVxdWlyZXMgYW4gQVdTIGNsaWVudCB0byBiZSBwYXNzZWQgaW4uXG4gKlxuICogRm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5IHdpdGggZXhpc3RpbmcgdGVzdHMgKHNvIHdlIGRvbid0IGhhdmUgdG8gY2hhbmdlXG4gKiB0b28gbXVjaCkgdGhlIGlubmVyIGJsb2NrIGlzIGV4cGVjdGUgdG8gdGFrZSBhIGBUZXN0Rml4dHVyZWAgb2JqZWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aENka0FwcDxBIGV4dGVuZHMgVGVzdENvbnRleHQgJiBBd3NDb250ZXh0PihibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiBhc3luYyAoY29udGV4dDogQSkgPT4ge1xuICAgIGNvbnN0IHJhbmR5ID0gcmFuZG9tU3RyaW5nKCk7XG4gICAgY29uc3Qgc3RhY2tOYW1lUHJlZml4ID0gYGNka3Rlc3QtJHtyYW5keX1gO1xuICAgIGNvbnN0IGludGVnVGVzdERpciA9IHBhdGguam9pbihvcy50bXBkaXIoKSwgYGNkay1pbnRlZy0ke3JhbmR5fWApO1xuXG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBTdGFjayBwcmVmaXg6ICAgJHtzdGFja05hbWVQcmVmaXh9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBUZXN0IGRpcmVjdG9yeTogJHtpbnRlZ1Rlc3REaXJ9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBSZWdpb246ICAgICAgICAgJHtjb250ZXh0LmF3cy5yZWdpb259XFxuYCk7XG5cbiAgICBhd2FpdCBjbG9uZURpcmVjdG9yeShwYXRoLmpvaW4oX19kaXJuYW1lLCAnYXBwJyksIGludGVnVGVzdERpciwgY29udGV4dC5vdXRwdXQpO1xuICAgIGNvbnN0IGZpeHR1cmUgPSBuZXcgVGVzdEZpeHR1cmUoXG4gICAgICBpbnRlZ1Rlc3REaXIsXG4gICAgICBzdGFja05hbWVQcmVmaXgsXG4gICAgICBjb250ZXh0Lm91dHB1dCxcbiAgICAgIGNvbnRleHQuYXdzKTtcblxuICAgIGxldCBzdWNjZXNzID0gdHJ1ZTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ25wbScsICdpbnN0YWxsJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2NvcmUnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNucycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtaWFtJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNzbScsXG4gICAgICAgICdAYXdzLWNkay9hd3MtZWNyLWFzc2V0cycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtY2xvdWRmb3JtYXRpb24nLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWVjMiddKTtcblxuICAgICAgYXdhaXQgZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmUpO1xuXG4gICAgICBhd2FpdCBibG9jayhmaXh0dXJlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzdWNjZXNzID0gZmFsc2U7XG4gICAgICB0aHJvdyBlO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBhd2FpdCBmaXh0dXJlLmRpc3Bvc2Uoc3VjY2Vzcyk7XG4gICAgfVxuICB9O1xufVxuXG4vKipcbiAqIERlZmF1bHQgdGVzdCBmaXh0dXJlIGZvciBtb3N0IChhbGw/KSBpbnRlZyB0ZXN0c1xuICpcbiAqIEl0J3MgYSBjb21wb3NpdGlvbiBvZiB3aXRoQXdzL3dpdGhDZGtBcHAsIGV4cGVjdGluZyB0aGUgdGVzdCBibG9jayB0byB0YWtlIGEgYFRlc3RGaXh0dXJlYFxuICogb2JqZWN0LlxuICpcbiAqIFdlIGNvdWxkIGhhdmUgcHV0IGB3aXRoQXdzKHdpdGhDZGtBcHAoZml4dHVyZSA9PiB7IC8uLi4gYWN0dWFsIHRlc3QgaGVyZS4uLi8gfSkpYCBpbiBldmVyeVxuICogdGVzdCBkZWNsYXJhdGlvbiBidXQgY2VudHJhbGl6aW5nIGl0IGlzIGdvaW5nIHRvIG1ha2UgaXQgY29udmVuaWVudCB0byBtb2RpZnkgaW4gdGhlIGZ1dHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdpdGhEZWZhdWx0Rml4dHVyZShibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiB3aXRoQXdzPFRlc3RDb250ZXh0Pih3aXRoQ2RrQXBwKGJsb2NrKSk7XG4gIC8vICAgICAgICAgICAgICBefn5+fn4gdGhpcyBpcyBkaXNhcHBvaW50aW5nIFR5cGVTY3JpcHQhIEZlZWxzIGxpa2UgeW91IHNob3VsZCBoYXZlIGJlZW4gYWJsZSB0byBkZXJpdmUgdGhpcy5cbn1cblxuZXhwb3J0IGludGVyZmFjZSBTaGVsbE9wdGlvbnMgZXh0ZW5kcyBjaGlsZF9wcm9jZXNzLlNwYXduT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBQcm9wZXJ0aWVzIHRvIGFkZCB0byAnZW52J1xuICAgKi9cbiAgbW9kRW52PzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcblxuICAvKipcbiAgICogRG9uJ3QgZmFpbCB3aGVuIGV4aXRpbmcgd2l0aCBhbiBlcnJvclxuICAgKlxuICAgKiBAZGVmYXVsdCBmYWxzZVxuICAgKi9cbiAgYWxsb3dFcnJFeGl0PzogYm9vbGVhbjtcblxuICAvKipcbiAgICogV2hldGhlciB0byBjYXB0dXJlIHN0ZGVyclxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICBjYXB0dXJlU3RkZXJyPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogUGFzcyBvdXRwdXQgaGVyZVxuICAgKi9cbiAgb3V0cHV0PzogTm9kZUpTLldyaXRhYmxlU3RyZWFtO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENka0NsaU9wdGlvbnMgZXh0ZW5kcyBTaGVsbE9wdGlvbnMge1xuICBvcHRpb25zPzogc3RyaW5nW107XG4gIG5ldmVyUmVxdWlyZUFwcHJvdmFsPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBQcmVwYXJlIGEgdGFyZ2V0IGRpciBieXJlcGxpY2F0aW5nIGEgc291cmNlIGRpcmVjdG9yeVxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2xvbmVEaXJlY3Rvcnkoc291cmNlOiBzdHJpbmcsIHRhcmdldDogc3RyaW5nLCBvdXRwdXQ/OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0pIHtcbiAgYXdhaXQgc2hlbGwoWydybScsICctcmYnLCB0YXJnZXRdLCB7IG91dHB1dCB9KTtcbiAgYXdhaXQgc2hlbGwoWydta2RpcicsICctcCcsIHRhcmdldF0sIHsgb3V0cHV0IH0pO1xuICBhd2FpdCBzaGVsbChbJ2NwJywgJy1SJywgc291cmNlICsgJy8qJywgdGFyZ2V0XSwgeyBvdXRwdXQgfSk7XG59XG5cbmV4cG9ydCBjbGFzcyBUZXN0Rml4dHVyZSB7XG4gIHB1YmxpYyByZWFkb25seSBxdWFsaWZpZXIgPSByYW5kb21TdHJpbmcoKS5zdWJzdHIoMCwgMTApO1xuICBwcml2YXRlIHJlYWRvbmx5IGJ1Y2tldHNUb0RlbGV0ZSA9IG5ldyBBcnJheTxzdHJpbmc+KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHVibGljIHJlYWRvbmx5IGludGVnVGVzdERpcjogc3RyaW5nLFxuICAgIHB1YmxpYyByZWFkb25seSBzdGFja05hbWVQcmVmaXg6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgb3V0cHV0OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0sXG4gICAgcHVibGljIHJlYWRvbmx5IGF3czogQXdzQ2xpZW50cykge1xuICB9XG5cbiAgcHVibGljIGxvZyhzOiBzdHJpbmcpIHtcbiAgICB0aGlzLm91dHB1dC53cml0ZShgJHtzfVxcbmApO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIHNoZWxsKGNvbW1hbmQ6IHN0cmluZ1tdLCBvcHRpb25zOiBPbWl0PFNoZWxsT3B0aW9ucywgJ2N3ZCd8J291dHB1dCc+ID0ge30pOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiBzaGVsbChjb21tYW5kLCB7XG4gICAgICBvdXRwdXQ6IHRoaXMub3V0cHV0LFxuICAgICAgY3dkOiB0aGlzLmludGVnVGVzdERpcixcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgY2RrRGVwbG95KHN0YWNrTmFtZXM6IHN0cmluZyB8IHN0cmluZ1tdLCBvcHRpb25zOiBDZGtDbGlPcHRpb25zID0ge30pIHtcbiAgICBzdGFja05hbWVzID0gdHlwZW9mIHN0YWNrTmFtZXMgPT09ICdzdHJpbmcnID8gW3N0YWNrTmFtZXNdIDogc3RhY2tOYW1lcztcblxuICAgIGNvbnN0IG5ldmVyUmVxdWlyZUFwcHJvdmFsID0gb3B0aW9ucy5uZXZlclJlcXVpcmVBcHByb3ZhbCA/PyB0cnVlO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVwbG95JyxcbiAgICAgIC4uLihuZXZlclJlcXVpcmVBcHByb3ZhbCA/IFsnLS1yZXF1aXJlLWFwcHJvdmFsPW5ldmVyJ10gOiBbXSksIC8vIERlZmF1bHQgdG8gbm8gYXBwcm92YWwgaW4gYW4gdW5hdHRlbmRlZCB0ZXN0XG4gICAgICAuLi4ob3B0aW9ucy5vcHRpb25zID8/IFtdKSxcbiAgICAgIC4uLnRoaXMuZnVsbFN0YWNrTmFtZShzdGFja05hbWVzKV0sIG9wdGlvbnMpO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIGNka0Rlc3Ryb3koc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHN0YWNrTmFtZXMgPSB0eXBlb2Ygc3RhY2tOYW1lcyA9PT0gJ3N0cmluZycgPyBbc3RhY2tOYW1lc10gOiBzdGFja05hbWVzO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVzdHJveScsXG4gICAgICAnLWYnLCAvLyBXZSBuZXZlciB3YW50IGEgcHJvbXB0IGluIGFuIHVuYXR0ZW5kZWQgdGVzdFxuICAgICAgLi4uKG9wdGlvbnMub3B0aW9ucyA/PyBbXSksXG4gICAgICAuLi50aGlzLmZ1bGxTdGFja05hbWUoc3RhY2tOYW1lcyldLCBvcHRpb25zKTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBjZGsoYXJnczogc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHJldHVybiB0aGlzLnNoZWxsKFsnY2RrJywgLi4uYXJnc10sIHtcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgICBtb2RFbnY6IHtcbiAgICAgICAgQVdTX1JFR0lPTjogdGhpcy5hd3MucmVnaW9uLFxuICAgICAgICBBV1NfREVGQVVMVF9SRUdJT046IHRoaXMuYXdzLnJlZ2lvbixcbiAgICAgICAgU1RBQ0tfTkFNRV9QUkVGSVg6IHRoaXMuc3RhY2tOYW1lUHJlZml4LFxuICAgICAgICAuLi5vcHRpb25zLm1vZEVudixcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgZnVsbFN0YWNrTmFtZShzdGFja05hbWU6IHN0cmluZyk6IHN0cmluZztcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nW10pOiBzdHJpbmdbXTtcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10pOiBzdHJpbmcgfCBzdHJpbmdbXSB7XG4gICAgaWYgKHR5cGVvZiBzdGFja05hbWVzID09PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuIGAke3RoaXMuc3RhY2tOYW1lUHJlZml4fS0ke3N0YWNrTmFtZXN9YDtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHN0YWNrTmFtZXMubWFwKHMgPT4gYCR7dGhpcy5zdGFja05hbWVQcmVmaXh9LSR7c31gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQXBwZW5kIHRoaXMgdG8gdGhlIGxpc3Qgb2YgYnVja2V0cyB0byBwb3RlbnRpYWxseSBkZWxldGVcbiAgICpcbiAgICogQXQgdGhlIGVuZCBvZiBhIHRlc3QsIHdlIGNsZWFuIHVwIGJ1Y2tldHMgdGhhdCBtYXkgbm90IGhhdmUgZ290dGVuIGRlc3Ryb3llZFxuICAgKiAoZm9yIHdoYXRldmVyIHJlYXNvbikuXG4gICAqL1xuICBwdWJsaWMgcmVtZW1iZXJUb0RlbGV0ZUJ1Y2tldChidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgICB0aGlzLmJ1Y2tldHNUb0RlbGV0ZS5wdXNoKGJ1Y2tldE5hbWUpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFudXAgbGVmdG92ZXIgc3RhY2tzIGFuZCBidWNrZXRzXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgZGlzcG9zZShzdWNjZXNzOiBib29sZWFuKSB7XG4gICAgY29uc3Qgc3RhY2tzVG9EZWxldGUgPSBhd2FpdCB0aGlzLmRlbGV0ZWFibGVTdGFja3ModGhpcy5zdGFja05hbWVQcmVmaXgpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIGJ1Y2tldHMgdGhhdCBuZWVkIHRvIGJlIGNsZWFuZWRcbiAgICBjb25zdCBidWNrZXROYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0J1Y2tldE5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChidWNrZXROYW1lcy5tYXAoYiA9PiB0aGlzLmF3cy5lbXB0eUJ1Y2tldChiKSkpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIEVDUiByZXBvc2l0b3JpZXMgd2l0aCBpbWFnZXMgd2hpY2ggc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICBjb25zdCBpbWFnZVJlcG9zaXRvcnlOYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0ltYWdlUmVwb3NpdG9yeU5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChpbWFnZVJlcG9zaXRvcnlOYW1lcy5tYXAociA9PiB0aGlzLmF3cy5kZWxldGVJbWFnZVJlcG9zaXRvcnkocikpKTtcblxuICAgIGF3YWl0IHRoaXMuYXdzLmRlbGV0ZVN0YWNrcyguLi5zdGFja3NUb0RlbGV0ZS5tYXAocyA9PiBzLlN0YWNrTmFtZSkpO1xuXG4gICAgLy8gV2UgbWlnaHQgaGF2ZSBsZWFrZWQgc29tZSBidWNrZXRzIGJ5IHVwZ3JhZGluZyB0aGUgYm9vdHN0cmFwIHN0YWNrLiBCZVxuICAgIC8vIHN1cmUgdG8gY2xlYW4gZXZlcnl0aGluZy5cbiAgICBmb3IgKGNvbnN0IGJ1Y2tldCBvZiB0aGlzLmJ1Y2tldHNUb0RlbGV0ZSkge1xuICAgICAgYXdhaXQgdGhpcy5hd3MuZGVsZXRlQnVja2V0KGJ1Y2tldCk7XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIHRlc3RzIGNvbXBsZXRlZCBzdWNjZXNzZnVsbHksIGhhcHBpbHkgZGVsZXRlIHRoZSBmaXh0dXJlXG4gICAgLy8gKG90aGVyd2lzZSBsZWF2ZSBpdCBmb3IgaHVtYW5zIHRvIGluc3BlY3QpXG4gICAgaWYgKHN1Y2Nlc3MpIHtcbiAgICAgIHJpbXJhZih0aGlzLmludGVnVGVzdERpcik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiB0aGUgc3RhY2tzIHN0YXJ0aW5nIHdpdGggb3VyIHRlc3RpbmcgcHJlZml4IHRoYXQgc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZGVsZXRlYWJsZVN0YWNrcyhwcmVmaXg6IHN0cmluZyk6IFByb21pc2U8QVdTLkNsb3VkRm9ybWF0aW9uLlN0YWNrW10+IHtcbiAgICBjb25zdCBzdGF0dXNGaWx0ZXIgPSBbXG4gICAgICAnQ1JFQVRFX0lOX1BST0dSRVNTJywgJ0NSRUFURV9GQUlMRUQnLCAnQ1JFQVRFX0NPTVBMRVRFJyxcbiAgICAgICdST0xMQkFDS19JTl9QUk9HUkVTUycsICdST0xMQkFDS19GQUlMRUQnLCAnUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgICAgJ0RFTEVURV9GQUlMRUQnLFxuICAgICAgJ1VQREFURV9JTl9QUk9HUkVTUycsICdVUERBVEVfQ09NUExFVEVfQ0xFQU5VUF9JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX0NPTVBMRVRFJywgJ1VQREFURV9ST0xMQkFDS19JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0ZBSUxFRCcsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFX0NMRUFOVVBfSU5fUFJPR1JFU1MnLFxuICAgICAgJ1VQREFURV9ST0xMQkFDS19DT01QTEVURScsICdSRVZJRVdfSU5fUFJPR1JFU1MnLFxuICAgICAgJ0lNUE9SVF9JTl9QUk9HUkVTUycsICdJTVBPUlRfQ09NUExFVEUnLFxuICAgICAgJ0lNUE9SVF9ST0xMQkFDS19JTl9QUk9HUkVTUycsICdJTVBPUlRfUk9MTEJBQ0tfRkFJTEVEJyxcbiAgICAgICdJTVBPUlRfUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgIF07XG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRoaXMuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHt9KTtcblxuICAgIHJldHVybiAocmVzcG9uc2UuU3RhY2tzID8/IFtdKVxuICAgICAgLmZpbHRlcihzID0+IHMuU3RhY2tOYW1lLnN0YXJ0c1dpdGgocHJlZml4KSlcbiAgICAgIC5maWx0ZXIocyA9PiBzdGF0dXNGaWx0ZXIuaW5jbHVkZXMocy5TdGFja1N0YXR1cykpXG4gICAgICAuZmlsdGVyKHMgPT4gcy5Sb290SWQgPT09IHVuZGVmaW5lZCk7IC8vIE9ubHkgZGVsZXRlIHBhcmVudCBzdGFja3MuIE5lc3RlZCBzdGFja3MgYXJlIGRlbGV0ZWQgaW4gdGhlIHByb2Nlc3NcbiAgfVxufVxuXG4vKipcbiAqIFBlcmZvcm0gYSBvbmUtdGltZSBxdWljayBzYW5pdHkgY2hlY2sgdGhhdCB0aGUgQVdTIGNsaWVudHMgaGFzIHByb3Blcmx5IGNvbmZpZ3VyZWQgY3JlZGVudGlhbHNcbiAqXG4gKiBJZiB3ZSBkb24ndCBkbyB0aGlzLCBjYWxscyBhcmUgZ29pbmcgdG8gZmFpbCBhbmQgdGhleSdsbCBiZSByZXRyaWVkIGFuZCBldmVyeXRoaW5nIHdpbGwgdGFrZVxuICogZm9yZXZlciBiZWZvcmUgdGhlIHVzZXIgbm90aWNlcyBhIHNpbXBsZSBtaXNjb25maWd1cmF0aW9uLlxuICpcbiAqIFdlIGNhbid0IGNoZWNrIGZvciB0aGUgcHJlc2VuY2Ugb2YgZW52aXJvbm1lbnQgdmFyaWFibGVzIHNpbmNlIGNyZWRlbnRpYWxzIGNvdWxkIGNvbWUgZnJvbVxuICogYW55d2hlcmUsIHNvIGRvIHNpbXBsZSBhY2NvdW50IHJldHJpZXZhbC5cbiAqXG4gKiBPbmx5IGRvIGl0IG9uY2UgcGVyIHByb2Nlc3MuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHNhbml0eUNoZWNrKGF3czogQXdzQ2xpZW50cykge1xuICBpZiAoc2FuaXR5Q2hlY2tlZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGF3cy5hY2NvdW50KCk7XG4gICAgICBzYW5pdHlDaGVja2VkID0gdHJ1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzYW5pdHlDaGVja2VkID0gZmFsc2U7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEFXUyBjcmVkZW50aWFscyBwcm9iYWJseSBub3QgY29uZmlndXJlZCwgZ290IGVycm9yOiAke2UubWVzc2FnZX1gKTtcbiAgICB9XG4gIH1cbiAgaWYgKCFzYW5pdHlDaGVja2VkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdBV1MgY3JlZGVudGlhbHMgcHJvYmFibHkgbm90IGNvbmZpZ3VyZWQsIHNlZSBwcmV2aW91cyBlcnJvcicpO1xuICB9XG59XG5sZXQgc2FuaXR5Q2hlY2tlZDogYm9vbGVhbiB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBNYWtlIHN1cmUgdGhhdCB0aGUgZ2l2ZW4gZW52aXJvbm1lbnQgaXMgYm9vdHN0cmFwcGVkXG4gKlxuICogU2luY2Ugd2UgZ28gc3RyaXBpbmcgYWNyb3NzIHJlZ2lvbnMsIGl0J3MgZ29pbmcgdG8gc3VjayBkb2luZyB0aGlzXG4gKiBieSBoYW5kIHNvIGxldCdzIGp1c3QgbWFzcy1hdXRvbWF0ZSBpdC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmU6IFRlc3RGaXh0dXJlKSB7XG4gIC8vIE9sZC1zdHlsZSBib290c3RyYXAgc3RhY2sgd2l0aCBkZWZhdWx0IG5hbWVcbiAgaWYgKGF3YWl0IGZpeHR1cmUuYXdzLnN0YWNrU3RhdHVzKCdDREtUb29sa2l0JykgPT09IHVuZGVmaW5lZCkge1xuICAgIGF3YWl0IGZpeHR1cmUuY2RrKFsnYm9vdHN0cmFwJywgYGF3czovLyR7YXdhaXQgZml4dHVyZS5hd3MuYWNjb3VudCgpfS8ke2ZpeHR1cmUuYXdzLnJlZ2lvbn1gXSk7XG4gIH1cbn1cblxuLyoqXG4gKiBBIHNoZWxsIGNvbW1hbmQgdGhhdCBkb2VzIHdoYXQgeW91IHdhbnRcbiAqXG4gKiBJcyBwbGF0Zm9ybS1hd2FyZSwgaGFuZGxlcyBlcnJvcnMgbmljZWx5LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2hlbGwoY29tbWFuZDogc3RyaW5nW10sIG9wdGlvbnM6IFNoZWxsT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgaWYgKG9wdGlvbnMubW9kRW52ICYmIG9wdGlvbnMuZW52KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdVc2UgZWl0aGVyIGVudiBvciBtb2RFbnYgYnV0IG5vdCBib3RoJyk7XG4gIH1cblxuICBvcHRpb25zLm91dHB1dD8ud3JpdGUoYPCfkrsgJHtjb21tYW5kLmpvaW4oJyAnKX1cXG5gKTtcblxuICBjb25zdCBlbnYgPSBvcHRpb25zLmVudiA/PyAob3B0aW9ucy5tb2RFbnYgPyB7IC4uLnByb2Nlc3MuZW52LCAuLi5vcHRpb25zLm1vZEVudiB9IDogdW5kZWZpbmVkKTtcblxuICBjb25zdCBjaGlsZCA9IGNoaWxkX3Byb2Nlc3Muc3Bhd24oY29tbWFuZFswXSwgY29tbWFuZC5zbGljZSgxKSwge1xuICAgIC4uLm9wdGlvbnMsXG4gICAgZW52LFxuICAgIC8vIE5lZWQgdGhpcyBmb3IgV2luZG93cyB3aGVyZSB3ZSB3YW50IC5jbWQgYW5kIC5iYXQgdG8gYmUgZm91bmQgYXMgd2VsbC5cbiAgICBzaGVsbDogdHJ1ZSxcbiAgICBzdGRpbzogWydpZ25vcmUnLCAncGlwZScsICdwaXBlJ10sXG4gIH0pO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZTxzdHJpbmc+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICBjb25zdCBzdGRvdXQgPSBuZXcgQXJyYXk8QnVmZmVyPigpO1xuICAgIGNvbnN0IHN0ZGVyciA9IG5ldyBBcnJheTxCdWZmZXI+KCk7XG5cbiAgICBjaGlsZC5zdGRvdXQhLm9uKCdkYXRhJywgY2h1bmsgPT4ge1xuICAgICAgb3B0aW9ucy5vdXRwdXQ/LndyaXRlKGNodW5rKTtcbiAgICAgIHN0ZG91dC5wdXNoKGNodW5rKTtcbiAgICB9KTtcblxuICAgIGNoaWxkLnN0ZGVyciEub24oJ2RhdGEnLCBjaHVuayA9PiB7XG4gICAgICBvcHRpb25zLm91dHB1dD8ud3JpdGUoY2h1bmspO1xuICAgICAgaWYgKG9wdGlvbnMuY2FwdHVyZVN0ZGVyciA/PyB0cnVlKSB7XG4gICAgICAgIHN0ZGVyci5wdXNoKGNodW5rKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Vycm9yJywgcmVqZWN0KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Nsb3NlJywgY29kZSA9PiB7XG4gICAgICBpZiAoY29kZSA9PT0gMCB8fCBvcHRpb25zLmFsbG93RXJyRXhpdCkge1xuICAgICAgICByZXNvbHZlKChCdWZmZXIuY29uY2F0KHN0ZG91dCkudG9TdHJpbmcoJ3V0Zi04JykgKyBCdWZmZXIuY29uY2F0KHN0ZGVycikudG9TdHJpbmcoJ3V0Zi04JykpLnRyaW0oKSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGAnJHtjb21tYW5kLmpvaW4oJyAnKX0nIGV4aXRlZCB3aXRoIGVycm9yIGNvZGUgJHtjb2RlfWApKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIGRlZmluZWQ8QT4oeDogQSk6IHggaXMgTm9uTnVsbGFibGU8QT4ge1xuICByZXR1cm4geCAhPT0gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIHJtIC1yZiByZWltcGxlbWVudGF0aW9uLCBkb24ndCB3YW50IHRvIGRlcGVuZCBvbiBhbiBOUE0gcGFja2FnZSBmb3IgdGhpc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcmltcmFmKGZzUGF0aDogc3RyaW5nKSB7XG4gIHRyeSB7XG4gICAgY29uc3QgaXNEaXIgPSBmcy5sc3RhdFN5bmMoZnNQYXRoKS5pc0RpcmVjdG9yeSgpO1xuXG4gICAgaWYgKGlzRGlyKSB7XG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZnMucmVhZGRpclN5bmMoZnNQYXRoKSkge1xuICAgICAgICByaW1yYWYocGF0aC5qb2luKGZzUGF0aCwgZmlsZSkpO1xuICAgICAgfVxuICAgICAgZnMucm1kaXJTeW5jKGZzUGF0aCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZzLnVubGlua1N5bmMoZnNQYXRoKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICAvLyBXZSB3aWxsIHN1cnZpdmUgRU5PRU5UXG4gICAgaWYgKGUuY29kZSAhPT0gJ0VOT0VOVCcpIHsgdGhyb3cgZTsgfVxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByYW5kb21TdHJpbmcoKSB7XG4gIC8vIENyYXp5XG4gIHJldHVybiBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5yZXBsYWNlKC9bXmEtejAtOV0rL2csICcnKTtcbn0iXX0= diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index 30828ce0f7522..66c6799164fd8 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -178,7 +178,7 @@ export class TestFixture { } public async cdk(args: string[], options: CdkCliOptions = {}) { - return this.shell(['cdk', '-v', ...args], { + return this.shell(['cdk', ...args], { ...options, modEnv: { AWS_REGION: this.aws.region, From 4a4c154aa433a5c99994398c5b6798aaea75b7b6 Mon Sep 17 00:00:00 2001 From: Hiroyoshi HOUCHI Date: Fri, 25 Sep 2020 03:27:15 +0900 Subject: [PATCH 07/46] feat(rds): add support for update and backup properties to Cluster instances (#10324) fixes #9926 Added the following parameters to DatabaseCluster. * AutoMinorVersionUpgrade * AllowMajorVersionUpgrade * DeleteAutomatedBackups #10092 as a reference, only defined simple parameters. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/lib/cluster.ts | 3 + packages/@aws-cdk/aws-rds/lib/props.ts | 21 +++++++ .../@aws-cdk/aws-rds/test/test.cluster.ts | 63 +++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index f3f9300788e1c..6ed9b4da06b68 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -716,6 +716,9 @@ function createInstances(cluster: DatabaseClusterNew, props: DatabaseClusterBase dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(), monitoringRoleArn: monitoringRole && monitoringRole.roleArn, + autoMinorVersionUpgrade: props.instanceProps.autoMinorVersionUpgrade, + allowMajorVersionUpgrade: props.instanceProps.allowMajorVersionUpgrade, + deleteAutomatedBackups: props.instanceProps.deleteAutomatedBackups, }); // If removalPolicy isn't explicitly set, diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 81a27a1e05d70..e9be6be583af9 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -63,6 +63,27 @@ export interface InstanceProps { * @default - default master key */ readonly performanceInsightEncryptionKey?: kms.IKey; + + /** + * Whether to enable automatic upgrade of minor version for the DB instance. + * + * @default - true + */ + readonly autoMinorVersionUpgrade?: boolean; + + /** + * Whether to allow upgrade of major version for the DB instance. + * + * @default - false + */ + readonly allowMajorVersionUpgrade?: boolean; + + /** + * Whether to remove automated backups immediately after the DB instance is deleted for the DB instance. + * + * @default - true + */ + readonly deleteAutomatedBackups?: boolean; } /** diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 82dce160e7128..47b9ce619fe17 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -373,6 +373,69 @@ export = { }, }, + 'cluster with disable automatic upgrade of minor version'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + instanceProps: { + autoMinorVersionUpgrade: false, + vpc, + }, + }); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + AutoMinorVersionUpgrade: false, + })); + + test.done(); + }, + + 'cluster with allow upgrade of major version'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + instanceProps: { + allowMajorVersionUpgrade: true, + vpc, + }, + }); + + expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { + AllowMajorVersionUpgrade: true, + })); + + test.done(); + }, + + 'cluster with disallow remove backups'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + instanceProps: { + deleteAutomatedBackups: false, + vpc, + }, + }); + + expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { + DeleteAutomatedBackups: false, + })); + + test.done(); + }, + 'create a cluster using a specific version of MySQL'(test: Test) { // GIVEN const stack = testStack(); From e349004a522e2123c1e93bd3402dd7c3f9c5c17c Mon Sep 17 00:00:00 2001 From: Clement Allen Date: Thu, 24 Sep 2020 21:52:33 +0100 Subject: [PATCH 08/46] feat(ecs-patterns): allow passthrough of security groups to service (#10501) Closes #8953 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ecs-patterns/README.md | 2 + ...plication-load-balanced-fargate-service.ts | 9 ++++ .../test.load-balanced-fargate-service.ts | 44 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index ffee66fe6cc96..cd88ea5133292 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -62,6 +62,8 @@ You can customize the health check for your target group; otherwise it defaults Fargate services will use the `LATEST` platform version by default, but you can override by providing a value for the `platformVersion` property in the constructor. +Fargate services use the default VPC Security Group unless one or more are provided using the `securityGroups` property in the constructor. + By setting `redirectHTTP` to true, CDK will automatically create a listener on port 80 that redirects HTTP traffic to the HTTPS port. Additionally, if more than one application target group are needed, instantiate one of the following: 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 baa8a191410dc..d2b6ed7219fb0 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 @@ -1,3 +1,4 @@ +import { ISecurityGroup } from '@aws-cdk/aws-ec2'; import { FargatePlatformVersion, FargateService, FargateTaskDefinition } from '@aws-cdk/aws-ecs'; import { Construct } from '@aws-cdk/core'; import { ApplicationLoadBalancedServiceBase, ApplicationLoadBalancedServiceBaseProps } from '../base/application-load-balanced-service-base'; @@ -75,6 +76,13 @@ export interface ApplicationLoadBalancedFargateServiceProps extends ApplicationL * @default Latest */ readonly platformVersion?: FargatePlatformVersion; + + /** + * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * + * @default - A new security group is created. + */ + readonly securityGroups?: ISecurityGroup[]; } /** @@ -151,6 +159,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, + securityGroups: props.securityGroups, }); this.addServiceAsTarget(this.service); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 60559f4be1120..12b3adea9dcc1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -637,4 +637,48 @@ export = { test.done(); }, + 'passing in previously created security groups to ALB Fargate Service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + allowAllOutbound: false, + description: 'Example', + securityGroupName: 'Rolly', + vpc, + }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + securityGroups: [securityGroup], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + LaunchType: 'FARGATE', + })); + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Example', + GroupName: 'Rolly', + SecurityGroupEgress: [ + { + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + FromPort: 252, + IpProtocol: 'icmp', + ToPort: 86, + }, + ], + VpcId: { + Ref: 'Vpc8378EB38', + }, + })); + test.done(); + }, + }; From b23ce03121466b686dfdd25731ea107e4e27d17b Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Fri, 25 Sep 2020 17:19:43 +0300 Subject: [PATCH 09/46] fix(eks): `KubernetesPatch` and `FargateCluster` creates a circular dependency and breaks deployment (#10536) In version [`1.62.0`](https://github.com/aws/aws-cdk/releases/tag/v1.62.0) we introduced the ability to run `kubectl` commands on imported clusters. (See https://github.com/aws/aws-cdk/pull/9802). Part of this change included some refactoring with regards to how we use and create the `KubectlProvider`. Looks like we didn't consistently apply the same logic across all constructs that use it. Case in point: https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-manifest.ts#L58 Notice that here we use `this` as the scope to the `getOrCreate` call. Same goes for: https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-object-value.ts#L64 However, `KubernetesPatch` use `scope` instead. https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-patch.ts#L74 This means that the entire `scope` of the `KubernetesPatch` now depends, among others, on the `kubectlBarrier`. The scope will usually be either the cluster itself (when using `FargateCluster`), or the entire stack (when using `new KubernetesPatch`). In any case, the scope will most likely contain the cluster VPC. This creates the following dependency cycle: `Cluster => ClusterVpc => KubectlBarrier => Cluster`. The fix aligns the `KubernetesPatch` behavior to all other `kubectl` constructs and uses `this` as the scope, which will only add dependency on the barrier to the custom resource representing the patch. Fixes https://github.com/aws/aws-cdk/issues/10528 Fixes https://github.com/aws/aws-cdk/issues/10537 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/lib/k8s-patch.ts | 2 +- .../test/integ.fargate-cluster.expected.json | 1388 +++++++++++++++++ .../aws-eks/test/integ.fargate-cluster.ts | 21 + .../@aws-cdk/aws-eks/test/test.k8s-patch.ts | 6 +- 4 files changed, 1415 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json create mode 100644 packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts index d35de30a18fe5..88db0f4352f11 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts @@ -71,7 +71,7 @@ export class KubernetesPatch extends Construct { super(scope, id); const stack = Stack.of(this); - const provider = KubectlProvider.getOrCreate(scope, props.cluster); + const provider = KubectlProvider.getOrCreate(this, props.cluster); new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json new file mode 100644 index 0000000000000..de489ef72836e --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json @@ -0,0 +1,1388 @@ +{ + "Resources": { + "FargateClusterDefaultVpcE69D3A13": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F" + } + } + }, + "FargateClusterDefaultVpcIGWFD9278DA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc" + } + ] + } + }, + "FargateClusterDefaultVpcVPCGWA7F012E1": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "InternetGatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + } + }, + "FargateClusterRole8E36B33A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + } + ] + } + }, + "FargateClusterControlPlaneSecurityGroup1021A150": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + } + } + }, + "FargateClusterCreationRole8C524AD8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterCreationRoleDefaultPolicy629049D0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "FargateClusterRole8E36B33A", + "Arn" + ] + } + }, + { + "Action": [ + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DescribeUpdate", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig", + "eks:CreateFargateProfile", + "eks:TagResource", + "eks:UntagResource" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "eks:DescribeFargateProfile", + "eks:DeleteFargateProfile" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:GetRole", + "iam:listAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "ec2:DescribeVpcs", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:test-region:12345678:vpc/", + { + "Ref": "FargateClusterDefaultVpcE69D3A13" + } + ] + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterCreationRoleDefaultPolicy629049D0", + "Roles": [ + { + "Ref": "FargateClusterCreationRole8C524AD8" + } + ] + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateCluster019F03E8": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksfargateclustertestawscdkawseksClusterResourceProviderframeworkonEventC85EBDF3Arn" + ] + }, + "Config": { + "version": "1.17", + "roleArn": { + "Fn::GetAtt": [ + "FargateClusterRole8E36B33A", + "Arn" + ] + }, + "resourcesVpcConfig": { + "subnetIds": [ + { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + }, + { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + }, + { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + } + ], + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "FargateClusterControlPlaneSecurityGroup1021A150", + "GroupId" + ] + } + ], + "endpointPublicAccess": true, + "endpointPrivateAccess": true + } + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "AttributesRevision": 2 + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1", + "FargateClusterCreationRoleDefaultPolicy629049D0", + "FargateClusterCreationRole8C524AD8" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterKubectlReadyBarrier93746934": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "FargateClusterfargateprofiledefault10E54561", + "FargateClusterCreationRoleDefaultPolicy629049D0", + "FargateClusterCreationRole8C524AD8", + "FargateCluster019F03E8" + ] + }, + "FargateClusterMastersRole50BAF9FD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterAwsAuthmanifest1F7A5553": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksfargateclustertestawscdkawseksKubectlProviderframeworkonEvent33B2ACA4Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{SessionName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\",\\\"system:node-proxier\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + }, + "ClusterName": { + "Ref": "FargateCluster019F03E8" + }, + "RoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + } + }, + "DependsOn": [ + "FargateClusterKubectlReadyBarrier93746934" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterCoreDnsComputeTypePatch711BF1B2": { + "Type": "Custom::AWSCDK-EKS-KubernetesPatch", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksfargateclustertestawscdkawseksKubectlProviderframeworkonEvent33B2ACA4Arn" + ] + }, + "ResourceName": "deployment/coredns", + "ResourceNamespace": "kube-system", + "ApplyPatchJson": "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"eks.amazonaws.com/compute-type\":\"fargate\"}}}}}", + "RestorePatchJson": "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"eks.amazonaws.com/compute-type\":\"ec2\"}}}}}", + "ClusterName": { + "Ref": "FargateCluster019F03E8" + }, + "RoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "PatchType": "strategic" + }, + "DependsOn": [ + "FargateClusterKubectlReadyBarrier93746934" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks-fargate-pods.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + ] + ] + } + ] + } + }, + "FargateClusterfargateprofiledefault10E54561": { + "Type": "Custom::AWSCDK-EKS-FargateProfile", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksfargateclustertestawscdkawseksClusterResourceProviderframeworkonEventC85EBDF3Arn" + ] + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "Config": { + "clusterName": { + "Ref": "FargateCluster019F03E8" + }, + "podExecutionRoleArn": { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + }, + "selectors": [ + { + "namespace": "default" + }, + { + "namespace": "kube-system" + } + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3Bucket60C2BF28" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksfargateclustertestFargateClusterCreationRoleFB2229CFArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket122A6EA8Ref": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9" + }, + "referencetoawscdkeksfargateclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKey56570425Ref": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket7ABEDF68Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey810DC943Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A" + } + } + } + }, + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3Bucket1E579A0A" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksfargateclustertestFargateCluster8588769EArn": { + "Fn::GetAtt": [ + "FargateCluster019F03E8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestFargateClusterCreationRoleFB2229CFArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3BucketF3D15942Ref": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2" + }, + "referencetoawscdkeksfargateclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey362BF04DRef": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet1Subnet0278E6BCRef": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet2Subnet1F1EC575Ref": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet3Subnet3A4CBF94Ref": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + }, + "referencetoawscdkeksfargateclustertestFargateCluster8588769EClusterSecurityGroupId": { + "Fn::GetAtt": [ + "FargateCluster019F03E8", + "ClusterSecurityGroupId" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket7ABEDF68Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey810DC943Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A" + } + } + } + } + }, + "Outputs": { + "FargateClusterConfigCommand46D4A6C7": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "FargateCluster019F03E8" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + } + ] + ] + } + }, + "FargateClusterGetTokenCommand4ADED7BB": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "FargateCluster019F03E8" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + } + ] + ] + } + } + }, + "Parameters": { + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9": { + "Type": "String", + "Description": "S3 bucket for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F": { + "Type": "String", + "Description": "S3 key for asset version \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaArtifactHash54822A43": { + "Type": "String", + "Description": "Artifact hash for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0": { + "Type": "String", + "Description": "S3 bucket for asset \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A": { + "Type": "String", + "Description": "S3 key for asset version \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cArtifactHash67988836": { + "Type": "String", + "Description": "Artifact hash for asset \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { + "Type": "String", + "Description": "S3 bucket for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A": { + "Type": "String", + "Description": "S3 key for asset version \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2ArtifactHashE86B38C7": { + "Type": "String", + "Description": "Artifact hash for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3Bucket60C2BF28": { + "Type": "String", + "Description": "S3 bucket for asset \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166": { + "Type": "String", + "Description": "S3 key for asset version \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89ArtifactHashC2E43922": { + "Type": "String", + "Description": "Artifact hash for asset \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3Bucket1E579A0A": { + "Type": "String", + "Description": "S3 bucket for asset \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13": { + "Type": "String", + "Description": "S3 key for asset version \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cArtifactHash4D9F989B": { + "Type": "String", + "Description": "Artifact hash for asset \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts new file mode 100644 index 0000000000000..870b3059b1a2b --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts @@ -0,0 +1,21 @@ +/// !cdk-integ pragma:ignore-assets +import { App } from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class EksFargateClusterStack extends TestStack { + + constructor(scope: App, id: string) { + super(scope, id); + + new eks.FargateCluster(this, 'FargateCluster', { + version: eks.KubernetesVersion.V1_17, + }); + } +} + +const app = new App(); + +new EksFargateClusterStack(app, 'aws-cdk-eks-fargate-cluster-test'); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts index e77035688b19e..c4defdf107606 100644 --- a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts @@ -13,7 +13,7 @@ export = { const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN - new KubernetesPatch(stack, 'MyPatch', { + const patch = new KubernetesPatch(stack, 'MyPatch', { cluster, applyPatch: { patch: { to: 'apply' } }, restorePatch: { restore: { patch: 123 } }, @@ -42,6 +42,10 @@ export = { ], }, })); + + // also make sure a dependency on the barrier is added to the patch construct. + test.deepEqual(patch.node.dependencies.map(d => d.target.node.uniqueId), ['MyClusterKubectlReadyBarrier7547948A']); + test.done(); }, 'defaults to "strategic" patch type if no patchType is specified'(test: Test) { From ddcf3e5e5582f86fcaf45d4cde3541deceb33518 Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Fri, 25 Sep 2020 07:56:15 -0700 Subject: [PATCH 10/46] chore: run integ test with 'v' (#10525) Following up on https://github.com/aws/aws-cdk/pull/10503, enabling verbose logging for integ tests. opt out for tests that relies on exact match of the output: * 'cdk synth' - match the output of `synth`. * 'Two ways of shoing the version' - This one is tricker. Since `--version` is implemnted using `.version()` of `yargs` it ignores the `-v` argument, but `version` (no dash) which is our implementation respect it. ``` $cdk version -v CDK toolkit version: 1.63.0 (build 7a68125) .... blah blah ``` vs: ``` $cdk --version -v 1.63.0 (build 7a68125) ``` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cli-regression-patches/v1.64.0/NOTES.md | 3 + .../v1.64.0/cdk-helpers.js | 325 ++++++++++++++++++ .../aws-cdk/test/integ/cli/cdk-helpers.ts | 7 +- .../aws-cdk/test/integ/cli/cli.integtest.ts | 8 +- 4 files changed, 337 insertions(+), 6 deletions(-) create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md new file mode 100644 index 0000000000000..1cb31072ab5de --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/NOTES.md @@ -0,0 +1,3 @@ +Added a `-v` switch to the cdk executions that also needs to be +applied to the regression tests so we have a better chance +of catching sporadically failing tests in the act. \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js new file mode 100644 index 0000000000000..da45aebb27469 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cdk-helpers.js @@ -0,0 +1,325 @@ +"use strict"; +var _a, _b; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const resource_pool_1 = require("./resource-pool"); +const REGIONS = process.env.AWS_REGIONS +? process.env.AWS_REGIONS.split(',') +: [(_b = (_a = process.env.AWS_REGION) !== null && _a !== void 0 ? _a : process.env.AWS_DEFAULT_REGION) !== null && _b !== void 0 ? _b : 'us-east-1']; +process.stdout.write(`Using regions: ${REGIONS}\n`); +const REGION_POOL = new resource_pool_1.ResourcePool(REGIONS); +/** + * Higher order function to execute a block with an AWS client setup + * + * Allocate the next region from the REGION pool and dispose it afterwards. + */ +function withAws(block) { +return (context) => REGION_POOL.using(async (region) => { + const aws = await aws_helpers_1.AwsClients.forRegion(region, context.output); + await sanityCheck(aws); + return block({ ...context, aws }); +}); +} +exports.withAws = withAws; +/** + * Higher order function to execute a block with a CDK app fixture + * + * Requires an AWS client to be passed in. + * + * For backwards compatibility with existing tests (so we don't have to change + * too much) the inner block is expected to take a `TestFixture` object. + */ +function withCdkApp(block) { +return async (context) => { + const randy = randomString(); + const stackNamePrefix = `cdktest-${randy}`; + const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); + context.output.write(` Stack prefix: ${stackNamePrefix}\n`); + context.output.write(` Test directory: ${integTestDir}\n`); + context.output.write(` Region: ${context.aws.region}\n`); + await cloneDirectory(path.join(__dirname, 'app'), integTestDir, context.output); + const fixture = new TestFixture(integTestDir, stackNamePrefix, context.output, context.aws); + let success = true; + try { + await fixture.shell(['npm', 'install', + '@aws-cdk/core', + '@aws-cdk/aws-sns', + '@aws-cdk/aws-iam', + '@aws-cdk/aws-lambda', + '@aws-cdk/aws-ssm', + '@aws-cdk/aws-ecr-assets', + '@aws-cdk/aws-cloudformation', + '@aws-cdk/aws-ec2']); + await ensureBootstrapped(fixture); + await block(fixture); + } + catch (e) { + success = false; + throw e; + } + finally { + await fixture.dispose(success); + } +}; +} +exports.withCdkApp = withCdkApp; +/** + * Default test fixture for most (all?) integ tests + * + * It's a composition of withAws/withCdkApp, expecting the test block to take a `TestFixture` + * object. + * + * We could have put `withAws(withCdkApp(fixture => { /... actual test here.../ }))` in every + * test declaration but centralizing it is going to make it convenient to modify in the future. + */ +function withDefaultFixture(block) { +return withAws(withCdkApp(block)); +// ^~~~~~ this is disappointing TypeScript! Feels like you should have been able to derive this. +} +exports.withDefaultFixture = withDefaultFixture; +/** + * Prepare a target dir byreplicating a source directory + */ +async function cloneDirectory(source, target, output) { +await shell(['rm', '-rf', target], { output }); +await shell(['mkdir', '-p', target], { output }); +await shell(['cp', '-R', source + '/*', target], { output }); +} +exports.cloneDirectory = cloneDirectory; +class TestFixture { +constructor(integTestDir, stackNamePrefix, output, aws) { + this.integTestDir = integTestDir; + this.stackNamePrefix = stackNamePrefix; + this.output = output; + this.aws = aws; + this.qualifier = randomString().substr(0, 10); + this.bucketsToDelete = new Array(); +} +log(s) { + this.output.write(`${s}\n`); +} +async shell(command, options = {}) { + return shell(command, { + output: this.output, + cwd: this.integTestDir, + ...options, + }); +} +async cdkDeploy(stackNames, options = {}) { + var _a, _b; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + const neverRequireApproval = (_a = options.neverRequireApproval) !== null && _a !== void 0 ? _a : true; + return this.cdk(['deploy', + ...(neverRequireApproval ? ['--require-approval=never'] : []), + ...((_b = options.options) !== null && _b !== void 0 ? _b : []), + ...this.fullStackName(stackNames)], options); +} +async cdkDestroy(stackNames, options = {}) { + var _a; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + return this.cdk(['destroy', + '-f', + ...((_a = options.options) !== null && _a !== void 0 ? _a : []), + ...this.fullStackName(stackNames)], options); +} +async cdk(args, options = {}) { + var _a; + const verbose = (_a = options.verbose) !== null && _a !== void 0 ? _a : true; + return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], { + ...options, + modEnv: { + AWS_REGION: this.aws.region, + AWS_DEFAULT_REGION: this.aws.region, + STACK_NAME_PREFIX: this.stackNamePrefix, + ...options.modEnv, + }, + }); +} +fullStackName(stackNames) { + if (typeof stackNames === 'string') { + return `${this.stackNamePrefix}-${stackNames}`; + } + else { + return stackNames.map(s => `${this.stackNamePrefix}-${s}`); + } +} +/** + * Append this to the list of buckets to potentially delete + * + * At the end of a test, we clean up buckets that may not have gotten destroyed + * (for whatever reason). + */ +rememberToDeleteBucket(bucketName) { + this.bucketsToDelete.push(bucketName); +} +/** + * Cleanup leftover stacks and buckets + */ +async dispose(success) { + const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix); + // Bootstrap stacks have buckets that need to be cleaned + const bucketNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('BucketName', stack)).filter(defined); + await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b))); + // Bootstrap stacks have ECR repositories with images which should be deleted + const imageRepositoryNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('ImageRepositoryName', stack)).filter(defined); + await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r))); + await this.aws.deleteStacks(...stacksToDelete.map(s => s.StackName)); + // We might have leaked some buckets by upgrading the bootstrap stack. Be + // sure to clean everything. + for (const bucket of this.bucketsToDelete) { + await this.aws.deleteBucket(bucket); + } + // If the tests completed successfully, happily delete the fixture + // (otherwise leave it for humans to inspect) + if (success) { + rimraf(this.integTestDir); + } +} +/** + * Return the stacks starting with our testing prefix that should be deleted + */ +async deleteableStacks(prefix) { + var _a; + const statusFilter = [ + 'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE', + 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE', + 'DELETE_FAILED', + 'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS', + 'UPDATE_ROLLBACK_FAILED', + 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_ROLLBACK_COMPLETE', 'REVIEW_IN_PROGRESS', + 'IMPORT_IN_PROGRESS', 'IMPORT_COMPLETE', + 'IMPORT_ROLLBACK_IN_PROGRESS', 'IMPORT_ROLLBACK_FAILED', + 'IMPORT_ROLLBACK_COMPLETE', + ]; + const response = await this.aws.cloudFormation('describeStacks', {}); + return ((_a = response.Stacks) !== null && _a !== void 0 ? _a : []) + .filter(s => s.StackName.startsWith(prefix)) + .filter(s => statusFilter.includes(s.StackStatus)) + .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process +} +} +exports.TestFixture = TestFixture; +/** + * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * + * If we don't do this, calls are going to fail and they'll be retried and everything will take + * forever before the user notices a simple misconfiguration. + * + * We can't check for the presence of environment variables since credentials could come from + * anywhere, so do simple account retrieval. + * + * Only do it once per process. + */ +async function sanityCheck(aws) { +if (sanityChecked === undefined) { + try { + await aws.account(); + sanityChecked = true; + } + catch (e) { + sanityChecked = false; + throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); + } +} +if (!sanityChecked) { + throw new Error('AWS credentials probably not configured, see previous error'); +} +} +let sanityChecked; +/** + * Make sure that the given environment is bootstrapped + * + * Since we go striping across regions, it's going to suck doing this + * by hand so let's just mass-automate it. + */ +async function ensureBootstrapped(fixture) { +// Old-style bootstrap stack with default name +if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { + await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); +} +} +/** + * A shell command that does what you want + * + * Is platform-aware, handles errors nicely. + */ +async function shell(command, options = {}) { +var _a, _b; +if (options.modEnv && options.env) { + throw new Error('Use either env or modEnv but not both'); +} +(_a = options.output) === null || _a === void 0 ? void 0 : _a.write(`💻 ${command.join(' ')}\n`); +const env = (_b = options.env) !== null && _b !== void 0 ? _b : (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); +const child = child_process.spawn(command[0], command.slice(1), { + ...options, + env, + // Need this for Windows where we want .cmd and .bat to be found as well. + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], +}); +return new Promise((resolve, reject) => { + const stdout = new Array(); + const stderr = new Array(); + child.stdout.on('data', chunk => { + var _a; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + stdout.push(chunk); + }); + child.stderr.on('data', chunk => { + var _a, _b; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + if ((_b = options.captureStderr) !== null && _b !== void 0 ? _b : true) { + stderr.push(chunk); + } + }); + child.once('error', reject); + child.once('close', code => { + if (code === 0 || options.allowErrExit) { + resolve((Buffer.concat(stdout).toString('utf-8') + Buffer.concat(stderr).toString('utf-8')).trim()); + } + else { + reject(new Error(`'${command.join(' ')}' exited with error code ${code}`)); + } + }); +}); +} +exports.shell = shell; +function defined(x) { +return x !== undefined; +} +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +function rimraf(fsPath) { +try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } + else { + fs.unlinkSync(fsPath); + } +} +catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { + throw e; + } +} +} +exports.rimraf = rimraf; +function randomString() { +// Crazy +return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} +exports.randomString = randomString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQ0FBK0M7QUFDL0MseUJBQXlCO0FBQ3pCLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IsK0NBQTREO0FBQzVELG1EQUErQztBQUcvQyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVc7SUFDckMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7SUFDcEMsQ0FBQyxDQUFDLGFBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLG1DQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLG1DQUFJLFdBQVcsQ0FBQyxDQUFDO0FBRTlFLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLGtCQUFrQixPQUFPLElBQUksQ0FBQyxDQUFDO0FBRXBELE1BQU0sV0FBVyxHQUFHLElBQUksNEJBQVksQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUs5Qzs7OztHQUlHO0FBQ0gsU0FBZ0IsT0FBTyxDQUF3QixLQUFpRDtJQUM5RixPQUFPLENBQUMsT0FBVSxFQUFFLEVBQUUsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUN4RCxNQUFNLEdBQUcsR0FBRyxNQUFNLHdCQUFVLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDL0QsTUFBTSxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdkIsT0FBTyxLQUFLLENBQUMsRUFBRSxHQUFHLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQ3BDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQVBELDBCQU9DO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQWdCLFVBQVUsQ0FBcUMsS0FBOEM7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Q0FBQyxDQUFDO1NBQ3RCO2FBQU07WUFDTCxFQUFFLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1NBQ3ZCO0tBQ0Y7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLHlCQUF5QjtRQUN6QixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQUUsTUFBTSxDQUFDLENBQUM7U0FBRTtLQUN0QztBQUNILENBQUM7QUFoQkQsd0JBZ0JDO0FBRUQsU0FBZ0IsWUFBWTtJQUMxQixRQUFRO0lBQ1IsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDL0QsQ0FBQztBQUhELG9DQUdDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2hpbGRfcHJvY2VzcyBmcm9tICdjaGlsZF9wcm9jZXNzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBvdXRwdXRGcm9tU3RhY2ssIEF3c0NsaWVudHMgfSBmcm9tICcuL2F3cy1oZWxwZXJzJztcbmltcG9ydCB7IFJlc291cmNlUG9vbCB9IGZyb20gJy4vcmVzb3VyY2UtcG9vbCc7XG5pbXBvcnQgeyBUZXN0Q29udGV4dCB9IGZyb20gJy4vdGVzdC1oZWxwZXJzJztcblxuY29uc3QgUkVHSU9OUyA9IHByb2Nlc3MuZW52LkFXU19SRUdJT05TXG4gID8gcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTlMuc3BsaXQoJywnKVxuICA6IFtwcm9jZXNzLmVudi5BV1NfUkVHSU9OID8/IHByb2Nlc3MuZW52LkFXU19ERUZBVUxUX1JFR0lPTiA/PyAndXMtZWFzdC0xJ107XG5cbnByb2Nlc3Muc3Rkb3V0LndyaXRlKGBVc2luZyByZWdpb25zOiAke1JFR0lPTlN9XFxuYCk7XG5cbmNvbnN0IFJFR0lPTl9QT09MID0gbmV3IFJlc291cmNlUG9vbChSRUdJT05TKTtcblxuXG5leHBvcnQgdHlwZSBBd3NDb250ZXh0ID0geyByZWFkb25seSBhd3M6IEF3c0NsaWVudHMgfTtcblxuLyoqXG4gKiBIaWdoZXIgb3JkZXIgZnVuY3Rpb24gdG8gZXhlY3V0ZSBhIGJsb2NrIHdpdGggYW4gQVdTIGNsaWVudCBzZXR1cFxuICpcbiAqIEFsbG9jYXRlIHRoZSBuZXh0IHJlZ2lvbiBmcm9tIHRoZSBSRUdJT04gcG9vbCBhbmQgZGlzcG9zZSBpdCBhZnRlcndhcmRzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aEF3czxBIGV4dGVuZHMgVGVzdENvbnRleHQ+KGJsb2NrOiAoY29udGV4dDogQSAmIEF3c0NvbnRleHQpID0+IFByb21pc2U8dm9pZD4pIHtcbiAgcmV0dXJuIChjb250ZXh0OiBBKSA9PiBSRUdJT05fUE9PTC51c2luZyhhc3luYyAocmVnaW9uKSA9PiB7XG4gICAgY29uc3QgYXdzID0gYXdhaXQgQXdzQ2xpZW50cy5mb3JSZWdpb24ocmVnaW9uLCBjb250ZXh0Lm91dHB1dCk7XG4gICAgYXdhaXQgc2FuaXR5Q2hlY2soYXdzKTtcblxuICAgIHJldHVybiBibG9jayh7IC4uLmNvbnRleHQsIGF3cyB9KTtcbiAgfSk7XG59XG5cbi8qKlxuICogSGlnaGVyIG9yZGVyIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgYSBibG9jayB3aXRoIGEgQ0RLIGFwcCBmaXh0dXJlXG4gKlxuICogUmVxdWlyZXMgYW4gQVdTIGNsaWVudCB0byBiZSBwYXNzZWQgaW4uXG4gKlxuICogRm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5IHdpdGggZXhpc3RpbmcgdGVzdHMgKHNvIHdlIGRvbid0IGhhdmUgdG8gY2hhbmdlXG4gKiB0b28gbXVjaCkgdGhlIGlubmVyIGJsb2NrIGlzIGV4cGVjdGVkIHRvIHRha2UgYSBgVGVzdEZpeHR1cmVgIG9iamVjdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdpdGhDZGtBcHA8QSBleHRlbmRzIFRlc3RDb250ZXh0ICYgQXdzQ29udGV4dD4oYmxvY2s6IChjb250ZXh0OiBUZXN0Rml4dHVyZSkgPT4gUHJvbWlzZTx2b2lkPikge1xuICByZXR1cm4gYXN5bmMgKGNvbnRleHQ6IEEpID0+IHtcbiAgICBjb25zdCByYW5keSA9IHJhbmRvbVN0cmluZygpO1xuICAgIGNvbnN0IHN0YWNrTmFtZVByZWZpeCA9IGBjZGt0ZXN0LSR7cmFuZHl9YDtcbiAgICBjb25zdCBpbnRlZ1Rlc3REaXIgPSBwYXRoLmpvaW4ob3MudG1wZGlyKCksIGBjZGstaW50ZWctJHtyYW5keX1gKTtcblxuICAgIGNvbnRleHQub3V0cHV0LndyaXRlKGAgU3RhY2sgcHJlZml4OiAgICR7c3RhY2tOYW1lUHJlZml4fVxcbmApO1xuICAgIGNvbnRleHQub3V0cHV0LndyaXRlKGAgVGVzdCBkaXJlY3Rvcnk6ICR7aW50ZWdUZXN0RGlyfVxcbmApO1xuICAgIGNvbnRleHQub3V0cHV0LndyaXRlKGAgUmVnaW9uOiAgICAgICAgICR7Y29udGV4dC5hd3MucmVnaW9ufVxcbmApO1xuXG4gICAgYXdhaXQgY2xvbmVEaXJlY3RvcnkocGF0aC5qb2luKF9fZGlybmFtZSwgJ2FwcCcpLCBpbnRlZ1Rlc3REaXIsIGNvbnRleHQub3V0cHV0KTtcbiAgICBjb25zdCBmaXh0dXJlID0gbmV3IFRlc3RGaXh0dXJlKFxuICAgICAgaW50ZWdUZXN0RGlyLFxuICAgICAgc3RhY2tOYW1lUHJlZml4LFxuICAgICAgY29udGV4dC5vdXRwdXQsXG4gICAgICBjb250ZXh0LmF3cyk7XG5cbiAgICBsZXQgc3VjY2VzcyA9IHRydWU7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGZpeHR1cmUuc2hlbGwoWyducG0nLCAnaW5zdGFsbCcsXG4gICAgICAgICdAYXdzLWNkay9jb3JlJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1zbnMnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWlhbScsXG4gICAgICAgICdAYXdzLWNkay9hd3MtbGFtYmRhJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1zc20nLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWVjci1hc3NldHMnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWNsb3VkZm9ybWF0aW9uJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1lYzInXSk7XG5cbiAgICAgIGF3YWl0IGVuc3VyZUJvb3RzdHJhcHBlZChmaXh0dXJlKTtcblxuICAgICAgYXdhaXQgYmxvY2soZml4dHVyZSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgc3VjY2VzcyA9IGZhbHNlO1xuICAgICAgdGhyb3cgZTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgYXdhaXQgZml4dHVyZS5kaXNwb3NlKHN1Y2Nlc3MpO1xuICAgIH1cbiAgfTtcbn1cblxuLyoqXG4gKiBEZWZhdWx0IHRlc3QgZml4dHVyZSBmb3IgbW9zdCAoYWxsPykgaW50ZWcgdGVzdHNcbiAqXG4gKiBJdCdzIGEgY29tcG9zaXRpb24gb2Ygd2l0aEF3cy93aXRoQ2RrQXBwLCBleHBlY3RpbmcgdGhlIHRlc3QgYmxvY2sgdG8gdGFrZSBhIGBUZXN0Rml4dHVyZWBcbiAqIG9iamVjdC5cbiAqXG4gKiBXZSBjb3VsZCBoYXZlIHB1dCBgd2l0aEF3cyh3aXRoQ2RrQXBwKGZpeHR1cmUgPT4geyAvLi4uIGFjdHVhbCB0ZXN0IGhlcmUuLi4vIH0pKWAgaW4gZXZlcnlcbiAqIHRlc3QgZGVjbGFyYXRpb24gYnV0IGNlbnRyYWxpemluZyBpdCBpcyBnb2luZyB0byBtYWtlIGl0IGNvbnZlbmllbnQgdG8gbW9kaWZ5IGluIHRoZSBmdXR1cmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3aXRoRGVmYXVsdEZpeHR1cmUoYmxvY2s6IChjb250ZXh0OiBUZXN0Rml4dHVyZSkgPT4gUHJvbWlzZTx2b2lkPikge1xuICByZXR1cm4gd2l0aEF3czxUZXN0Q29udGV4dD4od2l0aENka0FwcChibG9jaykpO1xuICAvLyAgICAgICAgICAgICAgXn5+fn5+IHRoaXMgaXMgZGlzYXBwb2ludGluZyBUeXBlU2NyaXB0ISBGZWVscyBsaWtlIHlvdSBzaG91bGQgaGF2ZSBiZWVuIGFibGUgdG8gZGVyaXZlIHRoaXMuXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2hlbGxPcHRpb25zIGV4dGVuZHMgY2hpbGRfcHJvY2Vzcy5TcGF3bk9wdGlvbnMge1xuICAvKipcbiAgICogUHJvcGVydGllcyB0byBhZGQgdG8gJ2VudidcbiAgICovXG4gIG1vZEVudj86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG5cbiAgLyoqXG4gICAqIERvbid0IGZhaWwgd2hlbiBleGl0aW5nIHdpdGggYW4gZXJyb3JcbiAgICpcbiAgICogQGRlZmF1bHQgZmFsc2VcbiAgICovXG4gIGFsbG93RXJyRXhpdD86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgdG8gY2FwdHVyZSBzdGRlcnJcbiAgICpcbiAgICogQGRlZmF1bHQgdHJ1ZVxuICAgKi9cbiAgY2FwdHVyZVN0ZGVycj86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFBhc3Mgb3V0cHV0IGhlcmVcbiAgICovXG4gIG91dHB1dD86IE5vZGVKUy5Xcml0YWJsZVN0cmVhbTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBDZGtDbGlPcHRpb25zIGV4dGVuZHMgU2hlbGxPcHRpb25zIHtcbiAgb3B0aW9ucz86IHN0cmluZ1tdO1xuICBuZXZlclJlcXVpcmVBcHByb3ZhbD86IGJvb2xlYW47XG4gIHZlcmJvc2U/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIFByZXBhcmUgYSB0YXJnZXQgZGlyIGJ5cmVwbGljYXRpbmcgYSBzb3VyY2UgZGlyZWN0b3J5XG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjbG9uZURpcmVjdG9yeShzb3VyY2U6IHN0cmluZywgdGFyZ2V0OiBzdHJpbmcsIG91dHB1dD86IE5vZGVKUy5Xcml0YWJsZVN0cmVhbSkge1xuICBhd2FpdCBzaGVsbChbJ3JtJywgJy1yZicsIHRhcmdldF0sIHsgb3V0cHV0IH0pO1xuICBhd2FpdCBzaGVsbChbJ21rZGlyJywgJy1wJywgdGFyZ2V0XSwgeyBvdXRwdXQgfSk7XG4gIGF3YWl0IHNoZWxsKFsnY3AnLCAnLVInLCBzb3VyY2UgKyAnLyonLCB0YXJnZXRdLCB7IG91dHB1dCB9KTtcbn1cblxuZXhwb3J0IGNsYXNzIFRlc3RGaXh0dXJlIHtcbiAgcHVibGljIHJlYWRvbmx5IHF1YWxpZmllciA9IHJhbmRvbVN0cmluZygpLnN1YnN0cigwLCAxMCk7XG4gIHByaXZhdGUgcmVhZG9ubHkgYnVja2V0c1RvRGVsZXRlID0gbmV3IEFycmF5PHN0cmluZz4oKTtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwdWJsaWMgcmVhZG9ubHkgaW50ZWdUZXN0RGlyOiBzdHJpbmcsXG4gICAgcHVibGljIHJlYWRvbmx5IHN0YWNrTmFtZVByZWZpeDogc3RyaW5nLFxuICAgIHB1YmxpYyByZWFkb25seSBvdXRwdXQ6IE5vZGVKUy5Xcml0YWJsZVN0cmVhbSxcbiAgICBwdWJsaWMgcmVhZG9ubHkgYXdzOiBBd3NDbGllbnRzKSB7XG4gIH1cblxuICBwdWJsaWMgbG9nKHM6IHN0cmluZykge1xuICAgIHRoaXMub3V0cHV0LndyaXRlKGAke3N9XFxuYCk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgc2hlbGwoY29tbWFuZDogc3RyaW5nW10sIG9wdGlvbnM6IE9taXQ8U2hlbGxPcHRpb25zLCAnY3dkJ3wnb3V0cHV0Jz4gPSB7fSk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIHNoZWxsKGNvbW1hbmQsIHtcbiAgICAgIG91dHB1dDogdGhpcy5vdXRwdXQsXG4gICAgICBjd2Q6IHRoaXMuaW50ZWdUZXN0RGlyLFxuICAgICAgLi4ub3B0aW9ucyxcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBjZGtEZXBsb3koc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHN0YWNrTmFtZXMgPSB0eXBlb2Ygc3RhY2tOYW1lcyA9PT0gJ3N0cmluZycgPyBbc3RhY2tOYW1lc10gOiBzdGFja05hbWVzO1xuXG4gICAgY29uc3QgbmV2ZXJSZXF1aXJlQXBwcm92YWwgPSBvcHRpb25zLm5ldmVyUmVxdWlyZUFwcHJvdmFsID8/IHRydWU7XG5cbiAgICByZXR1cm4gdGhpcy5jZGsoWydkZXBsb3knLFxuICAgICAgLi4uKG5ldmVyUmVxdWlyZUFwcHJvdmFsID8gWyctLXJlcXVpcmUtYXBwcm92YWw9bmV2ZXInXSA6IFtdKSwgLy8gRGVmYXVsdCB0byBubyBhcHByb3ZhbCBpbiBhbiB1bmF0dGVuZGVkIHRlc3RcbiAgICAgIC4uLihvcHRpb25zLm9wdGlvbnMgPz8gW10pLFxuICAgICAgLi4udGhpcy5mdWxsU3RhY2tOYW1lKHN0YWNrTmFtZXMpXSwgb3B0aW9ucyk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgY2RrRGVzdHJveShzdGFja05hbWVzOiBzdHJpbmcgfCBzdHJpbmdbXSwgb3B0aW9uczogQ2RrQ2xpT3B0aW9ucyA9IHt9KSB7XG4gICAgc3RhY2tOYW1lcyA9IHR5cGVvZiBzdGFja05hbWVzID09PSAnc3RyaW5nJyA/IFtzdGFja05hbWVzXSA6IHN0YWNrTmFtZXM7XG5cbiAgICByZXR1cm4gdGhpcy5jZGsoWydkZXN0cm95JyxcbiAgICAgICctZicsIC8vIFdlIG5ldmVyIHdhbnQgYSBwcm9tcHQgaW4gYW4gdW5hdHRlbmRlZCB0ZXN0XG4gICAgICAuLi4ob3B0aW9ucy5vcHRpb25zID8/IFtdKSxcbiAgICAgIC4uLnRoaXMuZnVsbFN0YWNrTmFtZShzdGFja05hbWVzKV0sIG9wdGlvbnMpO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIGNkayhhcmdzOiBzdHJpbmdbXSwgb3B0aW9uczogQ2RrQ2xpT3B0aW9ucyA9IHt9KSB7XG4gICAgY29uc3QgdmVyYm9zZSA9IG9wdGlvbnMudmVyYm9zZSA/PyB0cnVlO1xuXG4gICAgcmV0dXJuIHRoaXMuc2hlbGwoWydjZGsnLCAuLi4odmVyYm9zZSA/IFsnLXYnXSA6IFtdKSwgLi4uYXJnc10sIHtcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgICBtb2RFbnY6IHtcbiAgICAgICAgQVdTX1JFR0lPTjogdGhpcy5hd3MucmVnaW9uLFxuICAgICAgICBBV1NfREVGQVVMVF9SRUdJT046IHRoaXMuYXdzLnJlZ2lvbixcbiAgICAgICAgU1RBQ0tfTkFNRV9QUkVGSVg6IHRoaXMuc3RhY2tOYW1lUHJlZml4LFxuICAgICAgICAuLi5vcHRpb25zLm1vZEVudixcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgZnVsbFN0YWNrTmFtZShzdGFja05hbWU6IHN0cmluZyk6IHN0cmluZztcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nW10pOiBzdHJpbmdbXTtcbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10pOiBzdHJpbmcgfCBzdHJpbmdbXSB7XG4gICAgaWYgKHR5cGVvZiBzdGFja05hbWVzID09PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuIGAke3RoaXMuc3RhY2tOYW1lUHJlZml4fS0ke3N0YWNrTmFtZXN9YDtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHN0YWNrTmFtZXMubWFwKHMgPT4gYCR7dGhpcy5zdGFja05hbWVQcmVmaXh9LSR7c31gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQXBwZW5kIHRoaXMgdG8gdGhlIGxpc3Qgb2YgYnVja2V0cyB0byBwb3RlbnRpYWxseSBkZWxldGVcbiAgICpcbiAgICogQXQgdGhlIGVuZCBvZiBhIHRlc3QsIHdlIGNsZWFuIHVwIGJ1Y2tldHMgdGhhdCBtYXkgbm90IGhhdmUgZ290dGVuIGRlc3Ryb3llZFxuICAgKiAoZm9yIHdoYXRldmVyIHJlYXNvbikuXG4gICAqL1xuICBwdWJsaWMgcmVtZW1iZXJUb0RlbGV0ZUJ1Y2tldChidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgICB0aGlzLmJ1Y2tldHNUb0RlbGV0ZS5wdXNoKGJ1Y2tldE5hbWUpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFudXAgbGVmdG92ZXIgc3RhY2tzIGFuZCBidWNrZXRzXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgZGlzcG9zZShzdWNjZXNzOiBib29sZWFuKSB7XG4gICAgY29uc3Qgc3RhY2tzVG9EZWxldGUgPSBhd2FpdCB0aGlzLmRlbGV0ZWFibGVTdGFja3ModGhpcy5zdGFja05hbWVQcmVmaXgpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIGJ1Y2tldHMgdGhhdCBuZWVkIHRvIGJlIGNsZWFuZWRcbiAgICBjb25zdCBidWNrZXROYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0J1Y2tldE5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChidWNrZXROYW1lcy5tYXAoYiA9PiB0aGlzLmF3cy5lbXB0eUJ1Y2tldChiKSkpO1xuXG4gICAgLy8gQm9vdHN0cmFwIHN0YWNrcyBoYXZlIEVDUiByZXBvc2l0b3JpZXMgd2l0aCBpbWFnZXMgd2hpY2ggc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICBjb25zdCBpbWFnZVJlcG9zaXRvcnlOYW1lcyA9IHN0YWNrc1RvRGVsZXRlLm1hcChzdGFjayA9PiBvdXRwdXRGcm9tU3RhY2soJ0ltYWdlUmVwb3NpdG9yeU5hbWUnLCBzdGFjaykpLmZpbHRlcihkZWZpbmVkKTtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChpbWFnZVJlcG9zaXRvcnlOYW1lcy5tYXAociA9PiB0aGlzLmF3cy5kZWxldGVJbWFnZVJlcG9zaXRvcnkocikpKTtcblxuICAgIGF3YWl0IHRoaXMuYXdzLmRlbGV0ZVN0YWNrcyguLi5zdGFja3NUb0RlbGV0ZS5tYXAocyA9PiBzLlN0YWNrTmFtZSkpO1xuXG4gICAgLy8gV2UgbWlnaHQgaGF2ZSBsZWFrZWQgc29tZSBidWNrZXRzIGJ5IHVwZ3JhZGluZyB0aGUgYm9vdHN0cmFwIHN0YWNrLiBCZVxuICAgIC8vIHN1cmUgdG8gY2xlYW4gZXZlcnl0aGluZy5cbiAgICBmb3IgKGNvbnN0IGJ1Y2tldCBvZiB0aGlzLmJ1Y2tldHNUb0RlbGV0ZSkge1xuICAgICAgYXdhaXQgdGhpcy5hd3MuZGVsZXRlQnVja2V0KGJ1Y2tldCk7XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIHRlc3RzIGNvbXBsZXRlZCBzdWNjZXNzZnVsbHksIGhhcHBpbHkgZGVsZXRlIHRoZSBmaXh0dXJlXG4gICAgLy8gKG90aGVyd2lzZSBsZWF2ZSBpdCBmb3IgaHVtYW5zIHRvIGluc3BlY3QpXG4gICAgaWYgKHN1Y2Nlc3MpIHtcbiAgICAgIHJpbXJhZih0aGlzLmludGVnVGVzdERpcik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiB0aGUgc3RhY2tzIHN0YXJ0aW5nIHdpdGggb3VyIHRlc3RpbmcgcHJlZml4IHRoYXQgc2hvdWxkIGJlIGRlbGV0ZWRcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZGVsZXRlYWJsZVN0YWNrcyhwcmVmaXg6IHN0cmluZyk6IFByb21pc2U8QVdTLkNsb3VkRm9ybWF0aW9uLlN0YWNrW10+IHtcbiAgICBjb25zdCBzdGF0dXNGaWx0ZXIgPSBbXG4gICAgICAnQ1JFQVRFX0lOX1BST0dSRVNTJywgJ0NSRUFURV9GQUlMRUQnLCAnQ1JFQVRFX0NPTVBMRVRFJyxcbiAgICAgICdST0xMQkFDS19JTl9QUk9HUkVTUycsICdST0xMQkFDS19GQUlMRUQnLCAnUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgICAgJ0RFTEVURV9GQUlMRUQnLFxuICAgICAgJ1VQREFURV9JTl9QUk9HUkVTUycsICdVUERBVEVfQ09NUExFVEVfQ0xFQU5VUF9JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX0NPTVBMRVRFJywgJ1VQREFURV9ST0xMQkFDS19JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0ZBSUxFRCcsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFX0NMRUFOVVBfSU5fUFJPR1JFU1MnLFxuICAgICAgJ1VQREFURV9ST0xMQkFDS19DT01QTEVURScsICdSRVZJRVdfSU5fUFJPR1JFU1MnLFxuICAgICAgJ0lNUE9SVF9JTl9QUk9HUkVTUycsICdJTVBPUlRfQ09NUExFVEUnLFxuICAgICAgJ0lNUE9SVF9ST0xMQkFDS19JTl9QUk9HUkVTUycsICdJTVBPUlRfUk9MTEJBQ0tfRkFJTEVEJyxcbiAgICAgICdJTVBPUlRfUk9MTEJBQ0tfQ09NUExFVEUnLFxuICAgIF07XG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRoaXMuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHt9KTtcblxuICAgIHJldHVybiAocmVzcG9uc2UuU3RhY2tzID8/IFtdKVxuICAgICAgLmZpbHRlcihzID0+IHMuU3RhY2tOYW1lLnN0YXJ0c1dpdGgocHJlZml4KSlcbiAgICAgIC5maWx0ZXIocyA9PiBzdGF0dXNGaWx0ZXIuaW5jbHVkZXMocy5TdGFja1N0YXR1cykpXG4gICAgICAuZmlsdGVyKHMgPT4gcy5Sb290SWQgPT09IHVuZGVmaW5lZCk7IC8vIE9ubHkgZGVsZXRlIHBhcmVudCBzdGFja3MuIE5lc3RlZCBzdGFja3MgYXJlIGRlbGV0ZWQgaW4gdGhlIHByb2Nlc3NcbiAgfVxufVxuXG4vKipcbiAqIFBlcmZvcm0gYSBvbmUtdGltZSBxdWljayBzYW5pdHkgY2hlY2sgdGhhdCB0aGUgQVdTIGNsaWVudHMgaGFzIHByb3Blcmx5IGNvbmZpZ3VyZWQgY3JlZGVudGlhbHNcbiAqXG4gKiBJZiB3ZSBkb24ndCBkbyB0aGlzLCBjYWxscyBhcmUgZ29pbmcgdG8gZmFpbCBhbmQgdGhleSdsbCBiZSByZXRyaWVkIGFuZCBldmVyeXRoaW5nIHdpbGwgdGFrZVxuICogZm9yZXZlciBiZWZvcmUgdGhlIHVzZXIgbm90aWNlcyBhIHNpbXBsZSBtaXNjb25maWd1cmF0aW9uLlxuICpcbiAqIFdlIGNhbid0IGNoZWNrIGZvciB0aGUgcHJlc2VuY2Ugb2YgZW52aXJvbm1lbnQgdmFyaWFibGVzIHNpbmNlIGNyZWRlbnRpYWxzIGNvdWxkIGNvbWUgZnJvbVxuICogYW55d2hlcmUsIHNvIGRvIHNpbXBsZSBhY2NvdW50IHJldHJpZXZhbC5cbiAqXG4gKiBPbmx5IGRvIGl0IG9uY2UgcGVyIHByb2Nlc3MuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHNhbml0eUNoZWNrKGF3czogQXdzQ2xpZW50cykge1xuICBpZiAoc2FuaXR5Q2hlY2tlZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGF3cy5hY2NvdW50KCk7XG4gICAgICBzYW5pdHlDaGVja2VkID0gdHJ1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzYW5pdHlDaGVja2VkID0gZmFsc2U7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEFXUyBjcmVkZW50aWFscyBwcm9iYWJseSBub3QgY29uZmlndXJlZCwgZ290IGVycm9yOiAke2UubWVzc2FnZX1gKTtcbiAgICB9XG4gIH1cbiAgaWYgKCFzYW5pdHlDaGVja2VkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdBV1MgY3JlZGVudGlhbHMgcHJvYmFibHkgbm90IGNvbmZpZ3VyZWQsIHNlZSBwcmV2aW91cyBlcnJvcicpO1xuICB9XG59XG5sZXQgc2FuaXR5Q2hlY2tlZDogYm9vbGVhbiB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBNYWtlIHN1cmUgdGhhdCB0aGUgZ2l2ZW4gZW52aXJvbm1lbnQgaXMgYm9vdHN0cmFwcGVkXG4gKlxuICogU2luY2Ugd2UgZ28gc3RyaXBpbmcgYWNyb3NzIHJlZ2lvbnMsIGl0J3MgZ29pbmcgdG8gc3VjayBkb2luZyB0aGlzXG4gKiBieSBoYW5kIHNvIGxldCdzIGp1c3QgbWFzcy1hdXRvbWF0ZSBpdC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmU6IFRlc3RGaXh0dXJlKSB7XG4gIC8vIE9sZC1zdHlsZSBib290c3RyYXAgc3RhY2sgd2l0aCBkZWZhdWx0IG5hbWVcbiAgaWYgKGF3YWl0IGZpeHR1cmUuYXdzLnN0YWNrU3RhdHVzKCdDREtUb29sa2l0JykgPT09IHVuZGVmaW5lZCkge1xuICAgIGF3YWl0IGZpeHR1cmUuY2RrKFsnYm9vdHN0cmFwJywgYGF3czovLyR7YXdhaXQgZml4dHVyZS5hd3MuYWNjb3VudCgpfS8ke2ZpeHR1cmUuYXdzLnJlZ2lvbn1gXSk7XG4gIH1cbn1cblxuLyoqXG4gKiBBIHNoZWxsIGNvbW1hbmQgdGhhdCBkb2VzIHdoYXQgeW91IHdhbnRcbiAqXG4gKiBJcyBwbGF0Zm9ybS1hd2FyZSwgaGFuZGxlcyBlcnJvcnMgbmljZWx5LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2hlbGwoY29tbWFuZDogc3RyaW5nW10sIG9wdGlvbnM6IFNoZWxsT3B0aW9ucyA9IHt9KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgaWYgKG9wdGlvbnMubW9kRW52ICYmIG9wdGlvbnMuZW52KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdVc2UgZWl0aGVyIGVudiBvciBtb2RFbnYgYnV0IG5vdCBib3RoJyk7XG4gIH1cblxuICBvcHRpb25zLm91dHB1dD8ud3JpdGUoYPCfkrsgJHtjb21tYW5kLmpvaW4oJyAnKX1cXG5gKTtcblxuICBjb25zdCBlbnYgPSBvcHRpb25zLmVudiA/PyAob3B0aW9ucy5tb2RFbnYgPyB7IC4uLnByb2Nlc3MuZW52LCAuLi5vcHRpb25zLm1vZEVudiB9IDogdW5kZWZpbmVkKTtcblxuICBjb25zdCBjaGlsZCA9IGNoaWxkX3Byb2Nlc3Muc3Bhd24oY29tbWFuZFswXSwgY29tbWFuZC5zbGljZSgxKSwge1xuICAgIC4uLm9wdGlvbnMsXG4gICAgZW52LFxuICAgIC8vIE5lZWQgdGhpcyBmb3IgV2luZG93cyB3aGVyZSB3ZSB3YW50IC5jbWQgYW5kIC5iYXQgdG8gYmUgZm91bmQgYXMgd2VsbC5cbiAgICBzaGVsbDogdHJ1ZSxcbiAgICBzdGRpbzogWydpZ25vcmUnLCAncGlwZScsICdwaXBlJ10sXG4gIH0pO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZTxzdHJpbmc+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICBjb25zdCBzdGRvdXQgPSBuZXcgQXJyYXk8QnVmZmVyPigpO1xuICAgIGNvbnN0IHN0ZGVyciA9IG5ldyBBcnJheTxCdWZmZXI+KCk7XG5cbiAgICBjaGlsZC5zdGRvdXQhLm9uKCdkYXRhJywgY2h1bmsgPT4ge1xuICAgICAgb3B0aW9ucy5vdXRwdXQ/LndyaXRlKGNodW5rKTtcbiAgICAgIHN0ZG91dC5wdXNoKGNodW5rKTtcbiAgICB9KTtcblxuICAgIGNoaWxkLnN0ZGVyciEub24oJ2RhdGEnLCBjaHVuayA9PiB7XG4gICAgICBvcHRpb25zLm91dHB1dD8ud3JpdGUoY2h1bmspO1xuICAgICAgaWYgKG9wdGlvbnMuY2FwdHVyZVN0ZGVyciA/PyB0cnVlKSB7XG4gICAgICAgIHN0ZGVyci5wdXNoKGNodW5rKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Vycm9yJywgcmVqZWN0KTtcblxuICAgIGNoaWxkLm9uY2UoJ2Nsb3NlJywgY29kZSA9PiB7XG4gICAgICBpZiAoY29kZSA9PT0gMCB8fCBvcHRpb25zLmFsbG93RXJyRXhpdCkge1xuICAgICAgICByZXNvbHZlKChCdWZmZXIuY29uY2F0KHN0ZG91dCkudG9TdHJpbmcoJ3V0Zi04JykgKyBCdWZmZXIuY29uY2F0KHN0ZGVycikudG9TdHJpbmcoJ3V0Zi04JykpLnRyaW0oKSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGAnJHtjb21tYW5kLmpvaW4oJyAnKX0nIGV4aXRlZCB3aXRoIGVycm9yIGNvZGUgJHtjb2RlfWApKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIGRlZmluZWQ8QT4oeDogQSk6IHggaXMgTm9uTnVsbGFibGU8QT4ge1xuICByZXR1cm4geCAhPT0gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIHJtIC1yZiByZWltcGxlbWVudGF0aW9uLCBkb24ndCB3YW50IHRvIGRlcGVuZCBvbiBhbiBOUE0gcGFja2FnZSBmb3IgdGhpc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcmltcmFmKGZzUGF0aDogc3RyaW5nKSB7XG4gIHRyeSB7XG4gICAgY29uc3QgaXNEaXIgPSBmcy5sc3RhdFN5bmMoZnNQYXRoKS5pc0RpcmVjdG9yeSgpO1xuXG4gICAgaWYgKGlzRGlyKSB7XG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZnMucmVhZGRpclN5bmMoZnNQYXRoKSkge1xuICAgICAgICByaW1yYWYocGF0aC5qb2luKGZzUGF0aCwgZmlsZSkpO1xuICAgICAgfVxuICAgICAgZnMucm1kaXJTeW5jKGZzUGF0aCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZzLnVubGlua1N5bmMoZnNQYXRoKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICAvLyBXZSB3aWxsIHN1cnZpdmUgRU5PRU5UXG4gICAgaWYgKGUuY29kZSAhPT0gJ0VOT0VOVCcpIHsgdGhyb3cgZTsgfVxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByYW5kb21TdHJpbmcoKSB7XG4gIC8vIENyYXp5XG4gIHJldHVybiBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5yZXBsYWNlKC9bXmEtejAtOV0rL2csICcnKTtcbn0iXX0= \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index 66c6799164fd8..01829ebff413f 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -37,7 +37,7 @@ export function withAws(block: (context: A & AwsContext) * Requires an AWS client to be passed in. * * For backwards compatibility with existing tests (so we don't have to change - * too much) the inner block is expecte to take a `TestFixture` object. + * too much) the inner block is expected to take a `TestFixture` object. */ export function withCdkApp(block: (context: TestFixture) => Promise) { return async (context: A) => { @@ -123,6 +123,7 @@ export interface ShellOptions extends child_process.SpawnOptions { export interface CdkCliOptions extends ShellOptions { options?: string[]; neverRequireApproval?: boolean; + verbose?: boolean; } /** @@ -178,7 +179,9 @@ export class TestFixture { } public async cdk(args: string[], options: CdkCliOptions = {}) { - return this.shell(['cdk', ...args], { + const verbose = options.verbose ?? true; + + return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], { ...options, modEnv: { AWS_REGION: this.aws.region, diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index d203c0f66e605..079a560175a8d 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -20,8 +20,8 @@ integTest('VPC Lookup', withDefaultFixture(async (fixture) => { })); integTest('Two ways of shoing the version', withDefaultFixture(async (fixture) => { - const version1 = await fixture.cdk(['version']); - const version2 = await fixture.cdk(['--version']); + const version1 = await fixture.cdk(['version'], { verbose: false }); + const version2 = await fixture.cdk(['--version'], { verbose: false }); expect(version1).toEqual(version2); })); @@ -39,14 +39,14 @@ integTest('Termination protection', withDefaultFixture(async (fixture) => { })); integTest('cdk synth', withDefaultFixture(async (fixture) => { - await expect(fixture.cdk(['synth', fixture.fullStackName('test-1')])).resolves.toEqual( + await expect(fixture.cdk(['synth', fixture.fullStackName('test-1')], { verbose: false })).resolves.toEqual( `Resources: topic69831491: Type: AWS::SNS::Topic Metadata: aws:cdk:path: ${fixture.stackNamePrefix}-test-1/topic/Resource`); - await expect(fixture.cdk(['synth', fixture.fullStackName('test-2')])).resolves.toEqual( + await expect(fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false })).resolves.toEqual( `Resources: topic152D84A37: Type: AWS::SNS::Topic From f0f8a63c98e8a7ff5bedcf271a78fcb417988378 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Fri, 25 Sep 2020 17:19:43 +0300 Subject: [PATCH 11/46] fix(eks): `KubernetesPatch` and `FargateCluster` creates a circular dependency and breaks deployment (#10536) In version [`1.62.0`](https://github.com/aws/aws-cdk/releases/tag/v1.62.0) we introduced the ability to run `kubectl` commands on imported clusters. (See https://github.com/aws/aws-cdk/pull/9802). Part of this change included some refactoring with regards to how we use and create the `KubectlProvider`. Looks like we didn't consistently apply the same logic across all constructs that use it. Case in point: https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-manifest.ts#L58 Notice that here we use `this` as the scope to the `getOrCreate` call. Same goes for: https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-object-value.ts#L64 However, `KubernetesPatch` use `scope` instead. https://github.com/aws/aws-cdk/blob/e349004a522e2123c1e93bd3402dd7c3f9c5c17c/packages/%40aws-cdk/aws-eks/lib/k8s-patch.ts#L74 This means that the entire `scope` of the `KubernetesPatch` now depends, among others, on the `kubectlBarrier`. The scope will usually be either the cluster itself (when using `FargateCluster`), or the entire stack (when using `new KubernetesPatch`). In any case, the scope will most likely contain the cluster VPC. This creates the following dependency cycle: `Cluster => ClusterVpc => KubectlBarrier => Cluster`. The fix aligns the `KubernetesPatch` behavior to all other `kubectl` constructs and uses `this` as the scope, which will only add dependency on the barrier to the custom resource representing the patch. Fixes https://github.com/aws/aws-cdk/issues/10528 Fixes https://github.com/aws/aws-cdk/issues/10537 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/lib/k8s-patch.ts | 2 +- .../test/integ.fargate-cluster.expected.json | 1388 +++++++++++++++++ .../aws-eks/test/integ.fargate-cluster.ts | 21 + .../@aws-cdk/aws-eks/test/test.k8s-patch.ts | 6 +- 4 files changed, 1415 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json create mode 100644 packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts index d35de30a18fe5..88db0f4352f11 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts @@ -71,7 +71,7 @@ export class KubernetesPatch extends Construct { super(scope, id); const stack = Stack.of(this); - const provider = KubectlProvider.getOrCreate(scope, props.cluster); + const provider = KubectlProvider.getOrCreate(this, props.cluster); new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json new file mode 100644 index 0000000000000..de489ef72836e --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json @@ -0,0 +1,1388 @@ +{ + "Resources": { + "FargateClusterDefaultVpcE69D3A13": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + } + } + }, + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PublicSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet1" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet2" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc/PrivateSubnet3" + } + ] + } + }, + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96" + }, + "SubnetId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + } + } + }, + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F" + } + } + }, + "FargateClusterDefaultVpcIGWFD9278DA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-fargate-cluster-test/FargateCluster/DefaultVpc" + } + ] + } + }, + "FargateClusterDefaultVpcVPCGWA7F012E1": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + }, + "InternetGatewayId": { + "Ref": "FargateClusterDefaultVpcIGWFD9278DA" + } + } + }, + "FargateClusterRole8E36B33A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + } + ] + } + }, + "FargateClusterControlPlaneSecurityGroup1021A150": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "FargateClusterDefaultVpcE69D3A13" + } + } + }, + "FargateClusterCreationRole8C524AD8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateClusterCreationRoleDefaultPolicy629049D0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "FargateClusterRole8E36B33A", + "Arn" + ] + } + }, + { + "Action": [ + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DescribeUpdate", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig", + "eks:CreateFargateProfile", + "eks:TagResource", + "eks:UntagResource" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "eks:DescribeFargateProfile", + "eks:DeleteFargateProfile" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:GetRole", + "iam:listAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "ec2:DescribeVpcs", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:test-region:12345678:vpc/", + { + "Ref": "FargateClusterDefaultVpcE69D3A13" + } + ] + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterCreationRoleDefaultPolicy629049D0", + "Roles": [ + { + "Ref": "FargateClusterCreationRole8C524AD8" + } + ] + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1" + ] + }, + "FargateCluster019F03E8": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksfargateclustertestawscdkawseksClusterResourceProviderframeworkonEventC85EBDF3Arn" + ] + }, + "Config": { + "version": "1.17", + "roleArn": { + "Fn::GetAtt": [ + "FargateClusterRole8E36B33A", + "Arn" + ] + }, + "resourcesVpcConfig": { + "subnetIds": [ + { + "Ref": "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC" + }, + { + "Ref": "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93" + }, + { + "Ref": "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + }, + { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + } + ], + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "FargateClusterControlPlaneSecurityGroup1021A150", + "GroupId" + ] + } + ], + "endpointPublicAccess": true, + "endpointPrivateAccess": true + } + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "AttributesRevision": 2 + }, + "DependsOn": [ + "FargateClusterDefaultVpcIGWFD9278DA", + "FargateClusterDefaultVpcPrivateSubnet1DefaultRouteE93D7B93", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableA42013EB", + "FargateClusterDefaultVpcPrivateSubnet1RouteTableAssociationDC34627F", + "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA", + "FargateClusterDefaultVpcPrivateSubnet2DefaultRouteABCE20FF", + "FargateClusterDefaultVpcPrivateSubnet2RouteTable1691B33C", + "FargateClusterDefaultVpcPrivateSubnet2RouteTableAssociation6C0234FE", + "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154", + "FargateClusterDefaultVpcPrivateSubnet3DefaultRouteEFE144B5", + "FargateClusterDefaultVpcPrivateSubnet3RouteTable7D0EEC96", + "FargateClusterDefaultVpcPrivateSubnet3RouteTableAssociationCC0949D8", + "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1", + "FargateClusterDefaultVpcPublicSubnet1DefaultRouteA0A93C70", + "FargateClusterDefaultVpcPublicSubnet1EIP0093A4E0", + "FargateClusterDefaultVpcPublicSubnet1NATGatewayEC4DEB51", + "FargateClusterDefaultVpcPublicSubnet1RouteTableC2D2B434", + "FargateClusterDefaultVpcPublicSubnet1RouteTableAssociation43821F5B", + "FargateClusterDefaultVpcPublicSubnet1Subnet96AFDABC", + "FargateClusterDefaultVpcPublicSubnet2DefaultRouteABE51CF2", + "FargateClusterDefaultVpcPublicSubnet2EIPA4C07B68", + "FargateClusterDefaultVpcPublicSubnet2NATGateway77D6A579", + "FargateClusterDefaultVpcPublicSubnet2RouteTableEDDB89D9", + "FargateClusterDefaultVpcPublicSubnet2RouteTableAssociationCF18C87A", + "FargateClusterDefaultVpcPublicSubnet2Subnet92A9CC93", + "FargateClusterDefaultVpcPublicSubnet3DefaultRoute8341F833", + "FargateClusterDefaultVpcPublicSubnet3EIP46E028EF", + "FargateClusterDefaultVpcPublicSubnet3NATGateway0AAE540F", + "FargateClusterDefaultVpcPublicSubnet3RouteTable4E802182", + "FargateClusterDefaultVpcPublicSubnet3RouteTableAssociation93B95514", + "FargateClusterDefaultVpcPublicSubnet3SubnetB408ADA9", + "FargateClusterDefaultVpcE69D3A13", + "FargateClusterDefaultVpcVPCGWA7F012E1", + "FargateClusterCreationRoleDefaultPolicy629049D0", + "FargateClusterCreationRole8C524AD8" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterKubectlReadyBarrier93746934": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "FargateClusterfargateprofiledefault10E54561", + "FargateClusterCreationRoleDefaultPolicy629049D0", + "FargateClusterCreationRole8C524AD8", + "FargateCluster019F03E8" + ] + }, + "FargateClusterMastersRole50BAF9FD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterAwsAuthmanifest1F7A5553": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksfargateclustertestawscdkawseksKubectlProviderframeworkonEvent33B2ACA4Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{SessionName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\",\\\"system:node-proxier\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + }, + "ClusterName": { + "Ref": "FargateCluster019F03E8" + }, + "RoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + } + }, + "DependsOn": [ + "FargateClusterKubectlReadyBarrier93746934" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterCoreDnsComputeTypePatch711BF1B2": { + "Type": "Custom::AWSCDK-EKS-KubernetesPatch", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksfargateclustertestawscdkawseksKubectlProviderframeworkonEvent33B2ACA4Arn" + ] + }, + "ResourceName": "deployment/coredns", + "ResourceNamespace": "kube-system", + "ApplyPatchJson": "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"eks.amazonaws.com/compute-type\":\"fargate\"}}}}}", + "RestorePatchJson": "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"eks.amazonaws.com/compute-type\":\"ec2\"}}}}}", + "ClusterName": { + "Ref": "FargateCluster019F03E8" + }, + "RoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "PatchType": "strategic" + }, + "DependsOn": [ + "FargateClusterKubectlReadyBarrier93746934" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks-fargate-pods.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + ] + ] + } + ] + } + }, + "FargateClusterfargateprofiledefault10E54561": { + "Type": "Custom::AWSCDK-EKS-FargateProfile", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksfargateclustertestawscdkawseksClusterResourceProviderframeworkonEventC85EBDF3Arn" + ] + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "Config": { + "clusterName": { + "Ref": "FargateCluster019F03E8" + }, + "podExecutionRoleArn": { + "Fn::GetAtt": [ + "FargateClusterfargateprofiledefaultPodExecutionRole66F2610E", + "Arn" + ] + }, + "selectors": [ + { + "namespace": "default" + }, + { + "namespace": "kube-system" + } + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3Bucket60C2BF28" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksfargateclustertestFargateClusterCreationRoleFB2229CFArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket122A6EA8Ref": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9" + }, + "referencetoawscdkeksfargateclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKey56570425Ref": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket7ABEDF68Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey810DC943Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A" + } + } + } + }, + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3Bucket1E579A0A" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksfargateclustertestFargateCluster8588769EArn": { + "Fn::GetAtt": [ + "FargateCluster019F03E8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestFargateClusterCreationRoleFB2229CFArn": { + "Fn::GetAtt": [ + "FargateClusterCreationRole8C524AD8", + "Arn" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3BucketF3D15942Ref": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2" + }, + "referencetoawscdkeksfargateclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey362BF04DRef": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet1Subnet0278E6BCRef": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet1Subnet50EA43AA" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet2Subnet1F1EC575Ref": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet2Subnet0C9D6154" + }, + "referencetoawscdkeksfargateclustertestFargateClusterDefaultVpcPrivateSubnet3Subnet3A4CBF94Ref": { + "Ref": "FargateClusterDefaultVpcPrivateSubnet3Subnet1F8A52F1" + }, + "referencetoawscdkeksfargateclustertestFargateCluster8588769EClusterSecurityGroupId": { + "Fn::GetAtt": [ + "FargateCluster019F03E8", + "ClusterSecurityGroupId" + ] + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket7ABEDF68Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0" + }, + "referencetoawscdkeksfargateclustertestAssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey810DC943Ref": { + "Ref": "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A" + } + } + } + } + }, + "Outputs": { + "FargateClusterConfigCommand46D4A6C7": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "FargateCluster019F03E8" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + } + ] + ] + } + }, + "FargateClusterGetTokenCommand4ADED7BB": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "FargateCluster019F03E8" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "FargateClusterMastersRole50BAF9FD", + "Arn" + ] + } + ] + ] + } + } + }, + "Parameters": { + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9": { + "Type": "String", + "Description": "S3 bucket for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F": { + "Type": "String", + "Description": "S3 key for asset version \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaArtifactHash54822A43": { + "Type": "String", + "Description": "Artifact hash for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3Bucket8132A6E0": { + "Type": "String", + "Description": "S3 bucket for asset \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cS3VersionKey722E831A": { + "Type": "String", + "Description": "S3 key for asset version \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParameters5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0cArtifactHash67988836": { + "Type": "String", + "Description": "Artifact hash for asset \"5db52e19f1f79cac27e817fa59d0b1f73d524301b679e2e7354122e474fcba0c\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { + "Type": "String", + "Description": "S3 bucket for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A": { + "Type": "String", + "Description": "S3 key for asset version \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2ArtifactHashE86B38C7": { + "Type": "String", + "Description": "Artifact hash for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3Bucket60C2BF28": { + "Type": "String", + "Description": "S3 bucket for asset \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89S3VersionKey81C20166": { + "Type": "String", + "Description": "S3 key for asset version \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersbedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89ArtifactHashC2E43922": { + "Type": "String", + "Description": "Artifact hash for asset \"bedcab6ba6cdb530ab9574b630651abdee4c67fb22c69ad17ab3b4369d3b7c89\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3Bucket1E579A0A": { + "Type": "String", + "Description": "S3 bucket for asset \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cS3VersionKey91701E13": { + "Type": "String", + "Description": "S3 key for asset version \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + }, + "AssetParametersc3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7cArtifactHash4D9F989B": { + "Type": "String", + "Description": "Artifact hash for asset \"c3e7ad226d0efc3c9ecf3b4c84ea434556c67008349c6cc43c1cdb58323ddf7c\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts new file mode 100644 index 0000000000000..870b3059b1a2b --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts @@ -0,0 +1,21 @@ +/// !cdk-integ pragma:ignore-assets +import { App } from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class EksFargateClusterStack extends TestStack { + + constructor(scope: App, id: string) { + super(scope, id); + + new eks.FargateCluster(this, 'FargateCluster', { + version: eks.KubernetesVersion.V1_17, + }); + } +} + +const app = new App(); + +new EksFargateClusterStack(app, 'aws-cdk-eks-fargate-cluster-test'); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts index e77035688b19e..c4defdf107606 100644 --- a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts @@ -13,7 +13,7 @@ export = { const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN - new KubernetesPatch(stack, 'MyPatch', { + const patch = new KubernetesPatch(stack, 'MyPatch', { cluster, applyPatch: { patch: { to: 'apply' } }, restorePatch: { restore: { patch: 123 } }, @@ -42,6 +42,10 @@ export = { ], }, })); + + // also make sure a dependency on the barrier is added to the patch construct. + test.deepEqual(patch.node.dependencies.map(d => d.target.node.uniqueId), ['MyClusterKubectlReadyBarrier7547948A']); + test.done(); }, 'defaults to "strategic" patch type if no patchType is specified'(test: Test) { From c0602d71bf0f13c9be4044905bf765fe29e72525 Mon Sep 17 00:00:00 2001 From: epolon Date: Fri, 25 Sep 2020 19:33:31 +0300 Subject: [PATCH 12/46] chore(release): 1.64.1 --- CHANGELOG.md | 7 +++++++ lerna.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77867355f3560..3e73ff4585a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.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. +### [1.64.1](https://github.com/aws/aws-cdk/compare/v1.64.0...v1.64.1) (2020-09-25) + + +### Bug Fixes + +* **eks:** `KubernetesPatch` and `FargateCluster` creates a circular dependency and breaks deployment ([#10536](https://github.com/aws/aws-cdk/issues/10536)) ([f0f8a63](https://github.com/aws/aws-cdk/commit/f0f8a63c98e8a7ff5bedcf271a78fcb417988378)), closes [40aws-cdk/aws-eks/lib/k8s-manifest.ts#L58](https://github.com/40aws-cdk/aws-eks/lib/k8s-manifest.ts/issues/L58) [40aws-cdk/aws-eks/lib/k8s-object-value.ts#L64](https://github.com/40aws-cdk/aws-eks/lib/k8s-object-value.ts/issues/L64) [40aws-cdk/aws-eks/lib/k8s-patch.ts#L74](https://github.com/40aws-cdk/aws-eks/lib/k8s-patch.ts/issues/L74) + ## [1.64.0](https://github.com/aws/aws-cdk/compare/v1.63.0...v1.64.0) (2020-09-22) diff --git a/lerna.json b/lerna.json index 7b4680783877f..dab86c14f2229 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.64.0" + "version": "1.64.1" } From 423d704f1ecfedf3756ce6b995fed4f5d3e87da6 Mon Sep 17 00:00:00 2001 From: epolon Date: Fri, 25 Sep 2020 19:35:48 +0300 Subject: [PATCH 13/46] Fix CHANGELOG header --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e73ff4585a48..bfbe72373287c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ 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. -### [1.64.1](https://github.com/aws/aws-cdk/compare/v1.64.0...v1.64.1) (2020-09-25) +## [1.64.1](https://github.com/aws/aws-cdk/compare/v1.64.0...v1.64.1) (2020-09-25) ### Bug Fixes From 10c7b497525ffb2acf79215140d699a56117aa15 Mon Sep 17 00:00:00 2001 From: epolon Date: Fri, 25 Sep 2020 19:42:45 +0300 Subject: [PATCH 14/46] Fix CHANGELOG entry corruption --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfbe72373287c..408ea09b12e14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. See [standa ### Bug Fixes -* **eks:** `KubernetesPatch` and `FargateCluster` creates a circular dependency and breaks deployment ([#10536](https://github.com/aws/aws-cdk/issues/10536)) ([f0f8a63](https://github.com/aws/aws-cdk/commit/f0f8a63c98e8a7ff5bedcf271a78fcb417988378)), closes [40aws-cdk/aws-eks/lib/k8s-manifest.ts#L58](https://github.com/40aws-cdk/aws-eks/lib/k8s-manifest.ts/issues/L58) [40aws-cdk/aws-eks/lib/k8s-object-value.ts#L64](https://github.com/40aws-cdk/aws-eks/lib/k8s-object-value.ts/issues/L64) [40aws-cdk/aws-eks/lib/k8s-patch.ts#L74](https://github.com/40aws-cdk/aws-eks/lib/k8s-patch.ts/issues/L74) +* **eks:** `KubernetesPatch` and `FargateCluster` creates a circular dependency and breaks deployment ([#10536](https://github.com/aws/aws-cdk/issues/10536)) ([f0f8a63](https://github.com/aws/aws-cdk/commit/f0f8a63c98e8a7ff5bedcf271a78fcb417988378)), closes [#10528](https://github.com/aws/aws-cdk/issues/10528) ## [1.64.0](https://github.com/aws/aws-cdk/compare/v1.63.0...v1.64.0) (2020-09-22) From bd8e07de8b4cb002a0af8ef5be21fde77fce86d7 Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Fri, 25 Sep 2020 10:23:46 -0700 Subject: [PATCH 15/46] chore: add patch for regression integ tests v1.64 (#10542) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../v1.64.0/cli.integtest.js | 599 ++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js new file mode 100644 index 0000000000000..a63578ecfaee1 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.0/cli.integtest.js @@ -0,0 +1,599 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs_1 = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const cdk_helpers_1 = require("./cdk-helpers"); +const test_helpers_1 = require("./test-helpers"); +jest.setTimeout(600 * 1000); +test_helpers_1.integTest('VPC Lookup', cdk_helpers_1.withDefaultFixture(async (fixture) => { + fixture.log('Making sure we are clean before starting.'); + await fixture.cdkDestroy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); + fixture.log('Setting up: creating a VPC with known tags'); + await fixture.cdkDeploy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); + fixture.log('Setup complete!'); + fixture.log('Verifying we can now import that VPC'); + await fixture.cdkDeploy('import-vpc', { modEnv: { ENABLE_VPC_TESTING: 'IMPORT' } }); +})); +test_helpers_1.integTest('Two ways of shoing the version', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const version1 = await fixture.cdk(['version'], { verbose: false }); + const version2 = await fixture.cdk(['--version'], { verbose: false }); + expect(version1).toEqual(version2); +})); +test_helpers_1.integTest('Termination protection', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const stackName = 'termination-protection'; + await fixture.cdkDeploy(stackName); + // Try a destroy that should fail + await expect(fixture.cdkDestroy(stackName)).rejects.toThrow('exited with error'); + // Can update termination protection even though the change set doesn't contain changes + await fixture.cdkDeploy(stackName, { modEnv: { TERMINATION_PROTECTION: 'FALSE' } }); + await fixture.cdkDestroy(stackName); +})); +test_helpers_1.integTest('cdk synth', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await expect(fixture.cdk(['synth', fixture.fullStackName('test-1')], { verbose: false })).resolves.toEqual(`Resources: + topic69831491: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-1/topic/Resource`); + await expect(fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false })).resolves.toEqual(`Resources: + topic152D84A37: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-2/topic1/Resource + topic2A4FB547F: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-2/topic2/Resource`); +})); +test_helpers_1.integTest('ssm parameter provider error', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await expect(fixture.cdk(['synth', + fixture.fullStackName('missing-ssm-parameter'), + '-c', 'test:ssm-parameter-name=/does/not/exist'], { + allowErrExit: true, + })).resolves.toContain('SSM parameter not available in account'); +})); +test_helpers_1.integTest('automatic ordering', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Deploy the consuming stack which will include the producing stack + await fixture.cdkDeploy('order-consuming'); + // Destroy the providing stack which will include the consuming stack + await fixture.cdkDestroy('order-providing'); +})); +test_helpers_1.integTest('context setting', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fs_1.promises.writeFile(path.join(fixture.integTestDir, 'cdk.context.json'), JSON.stringify({ + contextkey: 'this is the context value', + })); + try { + await expect(fixture.cdk(['context'])).resolves.toContain('this is the context value'); + // Test that deleting the contextkey works + await fixture.cdk(['context', '--reset', 'contextkey']); + await expect(fixture.cdk(['context'])).resolves.not.toContain('this is the context value'); + // Test that forced delete of the context key does not throw + await fixture.cdk(['context', '-f', '--reset', 'contextkey']); + } + finally { + await fs_1.promises.unlink(path.join(fixture.integTestDir, 'cdk.context.json')); + } +})); +test_helpers_1.integTest('deploy', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('test-2', { captureStderr: false }); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect((_a = response.StackResources) === null || _a === void 0 ? void 0 : _a.length).toEqual(2); +})); +test_helpers_1.integTest('deploy all', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const arns = await fixture.cdkDeploy('test-*', { captureStderr: false }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(arns.split('\n').length).toEqual(2); +})); +test_helpers_1.integTest('nested stack with parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + // STACK_NAME_PREFIX is used in MyTopicParam to allow multiple instances + // of this test to run in parallel, othewise they will attempt to create the same SNS topic. + const stackArn = await fixture.cdkDeploy('with-nested-stack-using-parameters', { + options: ['--parameters', `MyTopicParam=${fixture.stackNamePrefix}ThereIsNoSpoon`], + captureStderr: false, + }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect((_a = response.StackResources) === null || _a === void 0 ? void 0 : _a.length).toEqual(1); +})); +test_helpers_1.integTest('deploy without execute', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('test-2', { + options: ['--no-execute'], + captureStderr: false, + }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); +})); +test_helpers_1.integTest('security related changes without a CLI are expected to fail', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // redirect /dev/null to stdin, which means there will not be tty attached + // since this stack includes security-related changes, the deployment should + // immediately fail because we can't confirm the changes + const stackName = 'iam-test'; + await expect(fixture.cdkDeploy(stackName, { + options: ['<', '/dev/null'], + neverRequireApproval: false, + })).rejects.toThrow('exited with error'); + // Ensure stack was not deployed + await expect(fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName(stackName), + })).rejects.toThrow('does not exist'); +})); +test_helpers_1.integTest('deploy wildcard with outputs', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs_1.promises.mkdir(path.dirname(outputsFile), { recursive: true }); + await fixture.cdkDeploy(['outputs-test-*'], { + options: ['--outputs-file', outputsFile], + }); + const outputs = JSON.parse((await fs_1.promises.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + expect(outputs).toEqual({ + [`${fixture.stackNamePrefix}-outputs-test-1`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-1MyTopic`, + }, + [`${fixture.stackNamePrefix}-outputs-test-2`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-2MyOtherTopic`, + }, + }); +})); +test_helpers_1.integTest('deploy with parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}bazinga`, + ], + captureStderr: false, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}bazinga`, + }, + ]); +})); +test_helpers_1.integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b, _c, _d; + // GIVEN + await expect(fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error'); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('param-test-1'), + }); + const stackArn = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackId; + expect((_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].StackStatus).toEqual('ROLLBACK_COMPLETE'); + // WHEN + const newStackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, + ], + captureStderr: false, + }); + const newStackResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: newStackArn, + }); + // THEN + expect(stackArn).not.toEqual(newStackArn); // new stack was created + expect((_c = newStackResponse.Stacks) === null || _c === void 0 ? void 0 : _c[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect((_d = newStackResponse.Stacks) === null || _d === void 0 ? void 0 : _d[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}allgood`, + }, + ]); +})); +test_helpers_1.integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b, _c, _d; + // GIVEN + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}nice`, + ], + captureStderr: false, + }); + let response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('CREATE_COMPLETE'); + // bad parameter name with @ will put stack into UPDATE_ROLLBACK_COMPLETE + await expect(fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error'); + ; + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].StackStatus).toEqual('UPDATE_ROLLBACK_COMPLETE'); + // WHEN + await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, + ], + captureStderr: false, + }); + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + // THEN + expect((_c = response.Stacks) === null || _c === void 0 ? void 0 : _c[0].StackStatus).toEqual('UPDATE_COMPLETE'); + expect((_d = response.Stacks) === null || _d === void 0 ? void 0 : _d[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}allgood`, + }, + ]); +})); +test_helpers_1.integTest('deploy with wildcard and parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('param-test-*', { + options: [ + '--parameters', `${fixture.stackNamePrefix}-param-test-1:TopicNameParam=${fixture.stackNamePrefix}bazinga`, + '--parameters', `${fixture.stackNamePrefix}-param-test-2:OtherTopicNameParam=${fixture.stackNamePrefix}ThatsMySpot`, + '--parameters', `${fixture.stackNamePrefix}-param-test-3:DisplayNameParam=${fixture.stackNamePrefix}HeyThere`, + '--parameters', `${fixture.stackNamePrefix}-param-test-3:OtherDisplayNameParam=${fixture.stackNamePrefix}AnotherOne`, + ], + }); +})); +test_helpers_1.integTest('deploy with parameters multi', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const paramVal1 = `${fixture.stackNamePrefix}bazinga`; + const paramVal2 = `${fixture.stackNamePrefix}=jagshemash`; + const stackArn = await fixture.cdkDeploy('param-test-3', { + options: [ + '--parameters', `DisplayNameParam=${paramVal1}`, + '--parameters', `OtherDisplayNameParam=${paramVal2}`, + ], + captureStderr: false, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Parameters).toEqual([ + { + ParameterKey: 'DisplayNameParam', + ParameterValue: paramVal1, + }, + { + ParameterKey: 'OtherDisplayNameParam', + ParameterValue: paramVal2, + }, + ]); +})); +test_helpers_1.integTest('deploy with notification ARN', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const topicName = `${fixture.stackNamePrefix}-test-topic`; + const response = await fixture.aws.sns('createTopic', { Name: topicName }); + const topicArn = response.TopicArn; + try { + await fixture.cdkDeploy('test-2', { + options: ['--notification-arns', topicArn], + }); + // verify that the stack we deployed has our notification ARN + const describeResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('test-2'), + }); + expect((_a = describeResponse.Stacks) === null || _a === void 0 ? void 0 : _a[0].NotificationARNs).toEqual([topicArn]); + } + finally { + await fixture.aws.sns('deleteTopic', { + TopicArn: topicArn, + }); + } +})); +test_helpers_1.integTest('deploy with role', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const roleName = `${fixture.stackNamePrefix}-test-role`; + await deleteRole(); + const createResponse = await fixture.aws.iam('createRole', { + RoleName: roleName, + AssumeRolePolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, + Effect: 'Allow', + }], + }), + }); + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { + RoleName: roleName, + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: '*', + Resource: '*', + Effect: 'Allow', + }], + }), + }); + await aws_helpers_1.retry(fixture.output, 'Trying to assume fresh role', aws_helpers_1.retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', + }); + }); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await aws_helpers_1.sleep(5000); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); + } + finally { + await deleteRole(); + } + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); + } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } + catch (e) { + if (e.message.indexOf('cannot be found') > -1) { + return; + } + throw e; + } + } +})); +test_helpers_1.integTest('cdk diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + // We can make it fail by passing --fail + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1')])) + .rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('cdk diff --fail on multiple stacks exits with error if any of the stacks contains a diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // GIVEN + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + await fixture.cdkDeploy('test-2'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('There were no differences'); + // WHEN / THEN + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('cdk diff --fail with multiple stack exits with if any of the stacks contains a diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('test-1'); + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('There were no differences'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + // WHEN / THEN + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('deploy stack with docker asset', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('docker'); +})); +test_helpers_1.integTest('deploy and test stack with lambda asset', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b; + const stackArn = await fixture.cdkDeploy('lambda', { captureStderr: false }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const lambdaArn = (_b = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Outputs) === null || _b === void 0 ? void 0 : _b[0].OutputValue; + if (lambdaArn === undefined) { + throw new Error('Stack did not have expected Lambda ARN output'); + } + const output = await fixture.aws.lambda('invoke', { + FunctionName: lambdaArn, + }); + expect(JSON.stringify(output.Payload)).toContain('dear asset'); +})); +test_helpers_1.integTest('cdk ls', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const listing = await fixture.cdk(['ls'], { captureStderr: false }); + const expectedStacks = [ + 'conditional-resource', + 'docker', + 'docker-with-custom-file', + 'failed', + 'iam-test', + 'lambda', + 'missing-ssm-parameter', + 'order-providing', + 'outputs-test-1', + 'outputs-test-2', + 'param-test-1', + 'param-test-2', + 'param-test-3', + 'termination-protection', + 'test-1', + 'test-2', + 'with-nested-stack', + 'with-nested-stack-using-parameters', + 'order-consuming', + ]; + for (const stack of expectedStacks) { + expect(listing).toContain(fixture.fullStackName(stack)); + } +})); +test_helpers_1.integTest('deploy stack without resource', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Deploy the stack without resources + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + // This should have succeeded but not deployed the stack. + await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) + .rejects.toThrow('conditional-resource does not exist'); + // Deploy the stack with resources + await fixture.cdkDeploy('conditional-resource'); + // Then again WITHOUT resources (this should destroy the stack) + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) + .rejects.toThrow('conditional-resource does not exist'); +})); +test_helpers_1.integTest('IAM diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const output = await fixture.cdk(['diff', fixture.fullStackName('iam-test')]); + // Roughly check for a table like this: + // + // ┌───┬─────────────────┬────────┬────────────────┬────────────────────────────-──┬───────────┐ + // │ │ Resource │ Effect │ Action │ Principal │ Condition │ + // ├───┼─────────────────┼────────┼────────────────┼───────────────────────────────┼───────────┤ + // │ + │ ${SomeRole.Arn} │ Allow │ sts:AssumeRole │ Service:ec2.amazonaws.com │ │ + // └───┴─────────────────┴────────┴────────────────┴───────────────────────────────┴───────────┘ + expect(output).toContain('${SomeRole.Arn}'); + expect(output).toContain('sts:AssumeRole'); + expect(output).toContain('ec2.amazonaws.com'); +})); +test_helpers_1.integTest('fast deploy', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // we are using a stack with a nested stack because CFN will always attempt to + // update a nested stack, which will allow us to verify that updates are actually + // skipped unless --force is specified. + const stackArn = await fixture.cdkDeploy('with-nested-stack', { captureStderr: false }); + const changeSet1 = await getLatestChangeSet(); + // Deploy the same stack again, there should be no new change set created + await fixture.cdkDeploy('with-nested-stack'); + const changeSet2 = await getLatestChangeSet(); + expect(changeSet2.ChangeSetId).toEqual(changeSet1.ChangeSetId); + // Deploy the stack again with --force, now we should create a changeset + await fixture.cdkDeploy('with-nested-stack', { options: ['--force'] }); + const changeSet3 = await getLatestChangeSet(); + expect(changeSet3.ChangeSetId).not.toEqual(changeSet2.ChangeSetId); + // Deploy the stack again with tags, expected to create a new changeset + // even though the resources didn't change. + await fixture.cdkDeploy('with-nested-stack', { options: ['--tags', 'key=value'] }); + const changeSet4 = await getLatestChangeSet(); + expect(changeSet4.ChangeSetId).not.toEqual(changeSet3.ChangeSetId); + async function getLatestChangeSet() { + var _a, _b, _c; + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: stackArn }); + if (!((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0])) { + throw new Error('Did not get a ChangeSet at all'); + } + fixture.log(`Found Change Set ${(_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].ChangeSetId}`); + return (_c = response.Stacks) === null || _c === void 0 ? void 0 : _c[0]; + } +})); +test_helpers_1.integTest('failed deploy does not hang', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // this will hang if we introduce https://github.com/aws/aws-cdk/issues/6403 again. + await expect(fixture.cdkDeploy('failed')).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('can still load old assemblies', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); + const testAssembliesDirectory = path.join(__dirname, 'cloud-assemblies'); + for (const asmdir of await listChildDirs(testAssembliesDirectory)) { + fixture.log(`ASSEMBLY ${asmdir}`); + await cdk_helpers_1.cloneDirectory(asmdir, cxAsmDir); + // Some files in the asm directory that have a .js extension are + // actually treated as templates. Evaluate them using NodeJS. + const templates = await listChildren(cxAsmDir, fullPath => Promise.resolve(fullPath.endsWith('.js'))); + for (const template of templates) { + const targetName = template.replace(/.js$/, ''); + await cdk_helpers_1.shell([process.execPath, template, '>', targetName], { + cwd: cxAsmDir, + output: fixture.output, + modEnv: { + TEST_ACCOUNT: await fixture.aws.account(), + TEST_REGION: fixture.aws.region, + }, + }); + } + // Use this directory as a Cloud Assembly + const output = await fixture.cdk([ + '--app', cxAsmDir, + '-v', + 'synth', + ]); + // Assert that there was no providerError in CDK's stderr + // Because we rely on the app/framework to actually error in case the + // provider fails, we inspect the logs here. + expect(output).not.toContain('$providerError'); + } +})); +test_helpers_1.integTest('generating and loading assembly', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const asmOutputDir = `${fixture.integTestDir}-cdk-integ-asm`; + await fixture.shell(['rm', '-rf', asmOutputDir]); + // Synthesize a Cloud Assembly tothe default directory (cdk.out) and a specific directory. + await fixture.cdk(['synth']); + await fixture.cdk(['synth', '--output', asmOutputDir]); + // cdk.out in the current directory and the indicated --output should be the same + await fixture.shell(['diff', 'cdk.out', asmOutputDir]); + // Check that we can 'ls' the synthesized asm. + // Change to some random directory to make sure we're not accidentally loading cdk.json + const list = await fixture.cdk(['--app', asmOutputDir, 'ls'], { cwd: os.tmpdir() }); + // Same stacks we know are in the app + expect(list).toContain(`${fixture.stackNamePrefix}-lambda`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-1`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-2`); + // Check that we can use '.' and just synth ,the generated asm + const stackTemplate = await fixture.cdk(['--app', '.', 'synth', fixture.fullStackName('test-2')], { + cwd: asmOutputDir, + }); + expect(stackTemplate).toContain('topic152D84A37'); + // Deploy a Lambda from the copied asm + await fixture.cdkDeploy('lambda', { options: ['-a', '.'], cwd: asmOutputDir }); + // Remove (rename) the original custom docker file that was used during synth. + // this verifies that the assemly has a copy of it and that the manifest uses + // relative paths to reference to it. + const customDockerFile = path.join(fixture.integTestDir, 'docker', 'Dockerfile.Custom'); + await fs_1.promises.rename(customDockerFile, `${customDockerFile}~`); + try { + // deploy a docker image with custom file without synth (uses assets) + await fixture.cdkDeploy('docker-with-custom-file', { options: ['-a', '.'], cwd: asmOutputDir }); + } + finally { + // Rename back to restore fixture to original state + await fs_1.promises.rename(`${customDockerFile}~`, customDockerFile); + } +})); +test_helpers_1.integTest('templates on disk contain metadata resource, also in nested assemblies', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Synth first, and switch on version reporting because cdk.json is disabling it + await fixture.cdk(['synth', '--version-reporting=true']); + // Load template from disk from root assembly + const templateContents = await fixture.shell(['cat', 'cdk.out/*-lambda.template.json']); + expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); + // Load template from nested assembly + const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*-stage-StackInStage.template.json']); + expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); +})); +async function listChildren(parent, pred) { + const ret = new Array(); + for (const child of await fs_1.promises.readdir(parent, { encoding: 'utf-8' })) { + const fullPath = path.join(parent, child.toString()); + if (await pred(fullPath)) { + ret.push(fullPath); + } + } + return ret; +} +async function listChildDirs(parent) { + return listChildren(parent, async (fullPath) => (await fs_1.promises.stat(fullPath)).isDirectory()); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmludGVndGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNsaS5pbnRlZ3Rlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSwyQkFBb0M7QUFDcEMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QiwrQ0FBNkM7QUFDN0MsK0NBQTBFO0FBQzFFLGlEQUEyQztBQUUzQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUU1Qix3QkFBUyxDQUFDLFlBQVksRUFBRSxnQ0FBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7SUFDM0QsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sT0FBTyxDQUFDLFVBQVUsQ0FBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFckYsT0FBTyxDQUFDLEdBQUcsQ0FBQyw0Q0FBNEMsQ0FBQyxDQUFDO0lBQzFELE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDcEYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0lBRS9CLE9BQU8sQ0FBQyxHQUFHLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUNwRCxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLEVBQUUsa0JBQWtCLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3RGLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLGdDQUFnQyxFQUFFLGdDQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUMvRSxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFH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LENBQUMsQ0FBQyxDQUFDO0lBRTNILE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxDQUFDLFVBQVUsRUFBRSxDQUFDO0FBQ2hGLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSixLQUFLLFVBQVUsWUFBWSxDQUFDLE1BQWMsRUFBRSxJQUFxQztJQUMvRSxNQUFNLEdBQUcsR0FBRyxJQUFJLEtBQUssRUFBVSxDQUFDO0lBQ2hDLEtBQUssTUFBTSxLQUFLLElBQUksTUFBTSxhQUFFLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFO1FBQ25FLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ3JELElBQUksTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUU7WUFDeEIsR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztTQUNwQjtLQUNGO0lBQ0QsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDO0FBRUQsS0FBSyxVQUFVLGFBQWEsQ0FBQyxNQUFjO0lBQ3pDLE9BQU8sWUFBWSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsUUFBZ0IsRUFBRSxFQUFFLENBQUMsQ0FBQyxNQUFNLGFBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO0FBQ25HLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBwcm9taXNlcyBhcyBmcyB9IGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyByZXRyeSwgc2xlZXAgfSBmcm9tICcuL2F3cy1oZWxwZXJzJztcbmltcG9ydCB7IGNsb25lRGlyZWN0b3J5LCBzaGVsbCwgd2l0aERlZmF1bHRGaXh0dXJlIH0gZnJvbSAnLi9jZGstaGVscGVycyc7XG5pbXBvcnQgeyBpbnRlZ1Rlc3QgfSBmcm9tICcuL3Rlc3QtaGVscGVycyc7XG5cbmplc3Quc2V0VGltZW91dCg2MDAgKiAxMDAwKTtcblxuaW50ZWdUZXN0KCdWUEMgTG9va3VwJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGZpeHR1cmUubG9nKCdNYWtpbmcgc3VyZSB3ZSBhcmUgY2xlYW4gYmVmb3JlIHN0YXJ0aW5nLicpO1xuICBhd2FpdCBmaXh0dXJlLmNka0Rlc3Ryb3koJ2RlZmluZS12cGMnLCB7IG1vZEVudjogeyBFTkFCTEVfVlBDX1RFU1RJTkc6ICdERUZJTkUnIH0gfSk7XG5cbiAgZml4dHVyZS5sb2coJ1NldHRpbmcgdXA6IGNyZWF0aW5nIGEgVlBDIHdpdGgga25vd24gdGFncycpO1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnZGVmaW5lLXZwYycsIHsgbW9kRW52OiB7IEVOQUJMRV9WUENfVEVTVElORzogJ0RFRklORScgfSB9KTtcbiAgZml4dHVyZS5sb2coJ1NldHVwIGNvbXBsZXRlIScpO1xuXG4gIGZpeHR1cmUubG9nKCdWZXJpZnlpbmcgd2UgY2FuIG5vdyBpbXBvcnQgdGhhdCBWUEMnKTtcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ2ltcG9ydC12cGMnLCB7IG1vZEVudjogeyBFTkFCTEVfVlBDX1RFU1RJTkc6ICdJTVBPUlQnIH0gfSk7XG59KSk7XG5cbmludGVnVGVzdCgnVHdvIHdheXMgb2Ygc2hvaW5nIHRoZSB2ZXJzaW9uJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IHZlcnNpb24xID0gYXdhaXQgZml4dHVyZS5jZGsoWyd2ZXJzaW9uJ10sIHsgdmVyYm9zZTogZmFsc2UgfSk7XG4gIGNvbnN0IHZlcnNpb24yID0gYXdhaXQgZml4dHVyZS5jZGsoWyctLXZlcnNpb24nXSwgeyB2ZXJib3NlOiBmYWxzZSB9KTtcblxuICBleHBlY3QodmVyc2lvbjEpLnRvRXF1YWwodmVyc2lvbjIpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ1Rlcm1pbmF0aW9uIHByb3RlY3Rpb24nLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgc3RhY2tOYW1lID0gJ3Rlcm1pbmF0aW9uLXByb3RlY3Rpb24nO1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveShzdGFja05hbWUpO1xuXG4gIC8vIFRyeSBhIGRlc3Ryb3kgdGhhdCBzaG91bGQgZmFpbFxuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGtEZXN0cm95KHN0YWNrTmFtZSkpLnJlamVjdHMudG9UaHJvdygnZXhpdGVkIHdpdGggZXJyb3InKTtcblxuICAvLyBDYW4gdXBkYXRlIHRlcm1pbmF0aW9uIHByb3RlY3Rpb24gZXZlbiB0aG91Z2ggdGhlIGNoYW5nZSBzZXQgZG9lc24ndCBjb250YWluIGNoYW5nZXNcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koc3RhY2tOYW1lLCB7IG1vZEVudjogeyBURVJNSU5BVElPTl9QUk9URUNUSU9OOiAnRkFMU0UnIH0gfSk7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVzdHJveShzdGFja05hbWUpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBzeW50aCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGsoWydzeW50aCcsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldLCB7IHZlcmJvc2U6IGZhbHNlIH0pKS5yZXNvbHZlcy50b0VxdWFsKFxuICAgIGBSZXNvdXJjZXM6XG4gIHRvcGljNjk4MzE0OTE6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMS90b3BpYy9SZXNvdXJjZWApO1xuXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ3N5bnRoJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0sIHsgdmVyYm9zZTogZmFsc2UgfSkpLnJlc29sdmVzLnRvRXF1YWwoXG4gICAgYFJlc291cmNlczpcbiAgdG9waWMxNTJEODRBMzc6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMi90b3BpYzEvUmVzb3VyY2VcbiAgdG9waWMyQTRGQjU0N0Y6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMi90b3BpYzIvUmVzb3VyY2VgKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdzc20gcGFyYW1ldGVyIHByb3ZpZGVyIGVycm9yJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ3N5bnRoJyxcbiAgICBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ21pc3Npbmctc3NtLXBhcmFtZXRlcicpLFxuICAgICctYycsICd0ZXN0OnNzbS1wYXJhbWV0ZXItbmFtZT0vZG9lcy9ub3QvZXhpc3QnXSwge1xuICAgIGFsbG93RXJyRXhpdDogdHJ1ZSxcbiAgfSkpLnJlc29sdmVzLnRvQ29udGFpbignU1NNIHBhcmFtZXRlciBub3QgYXZhaWxhYmxlIGluIGFjY291bnQnKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdhdXRvbWF0aWMgb3JkZXJpbmcnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gRGVwbG95IHRoZSBjb25zdW1pbmcgc3RhY2sgd2hpY2ggd2lsbCBpbmNsdWRlIHRoZSBwcm9kdWNpbmcgc3RhY2tcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ29yZGVyLWNvbnN1bWluZycpO1xuXG4gIC8vIERlc3Ryb3kgdGhlIHByb3ZpZGluZyBzdGFjayB3aGljaCB3aWxsIGluY2x1ZGUgdGhlIGNvbnN1bWluZyBzdGFja1xuICBhd2FpdCBmaXh0dXJlLmNka0Rlc3Ryb3koJ29yZGVyLXByb3ZpZGluZycpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NvbnRleHQgc2V0dGluZycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBhd2FpdCBmcy53cml0ZUZpbGUocGF0aC5qb2luKGZpeHR1cmUuaW50ZWdUZXN0RGlyLCAnY2RrLmNvbnRleHQuanNvbicpLCBKU09OLnN0cmluZ2lmeSh7XG4gICAgY29udGV4dGtleTogJ3RoaXMgaXMgdGhlIGNvbnRleHQgdmFsdWUnLFxuICB9KSk7XG4gIHRyeSB7XG4gICAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuY2RrKFsnY29udGV4dCddKSkucmVzb2x2ZXMudG9Db250YWluKCd0aGlzIGlzIHRoZSBjb250ZXh0IHZhbHVlJyk7XG5cbiAgICAvLyBUZXN0IHRoYXQgZGVsZXRpbmcgdGhlIGNvbnRleHRrZXkgd29ya3NcbiAgICBhd2FpdCBmaXh0dXJlLmNkayhbJ2NvbnRleHQnLCAnLS1yZXNldCcsICdjb250ZXh0a2V5J10pO1xuICAgIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2NvbnRleHQnXSkpLnJlc29sdmVzLm5vdC50b0NvbnRhaW4oJ3RoaXMgaXMgdGhlIGNvbnRleHQgdmFsdWUnKTtcblxuICAgIC8vIFRlc3QgdGhhdCBmb3JjZWQgZGVsZXRlIG9mIHRoZSBjb250ZXh0IGtleSBkb2VzIG5vdCB0aHJvd1xuICAgIGF3YWl0IGZpeHR1cmUuY2RrKFsnY29udGV4dCcsICctZicsICctLXJlc2V0JywgJ2NvbnRleHRrZXknXSk7XG5cbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBmcy51bmxpbmsocGF0aC5qb2luKGZpeHR1cmUuaW50ZWdUZXN0RGlyLCAnY2RrLmNvbnRleHQuanNvbicpKTtcbiAgfVxufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd0ZXN0LTInLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuXG4gIC8vIHZlcmlmeSB0aGUgbnVtYmVyIG9mIHJlc291cmNlcyBpbiB0aGUgc3RhY2tcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja1Jlc291cmNlcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrUmVzb3VyY2VzPy5sZW5ndGgpLnRvRXF1YWwoMik7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IGFsbCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBhcm5zID0gYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtKicsIHsgY2FwdHVyZVN0ZGVycjogZmFsc2UgfSk7XG5cbiAgLy8gdmVyaWZ5IHRoYXQgd2Ugb25seSBkZXBsb3llZCBhIHNpbmdsZSBzdGFjayAodGhlcmUncyBhIHNpbmdsZSBBUk4gaW4gdGhlIG91dHB1dClcbiAgZXhwZWN0KGFybnMuc3BsaXQoJ1xcbicpLmxlbmd0aCkudG9FcXVhbCgyKTtcbn0pKTtcblxuaW50ZWdUZXN0KCduZXN0ZWQgc3RhY2sgd2l0aCBwYXJhbWV0ZXJzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIC8vIFNUQUNLX05BTUVfUFJFRklYIGlzIHVzZWQgaW4gTXlUb3BpY1BhcmFtIHRvIGFsbG93IG11bHRpcGxlIGluc3RhbmNlc1xuICAvLyBvZiB0aGlzIHRlc3QgdG8gcnVuIGluIHBhcmFsbGVsLCBvdGhld2lzZSB0aGV5IHdpbGwgYXR0ZW1wdCB0byBjcmVhdGUgdGhlIHNhbWUgU05TIHRvcGljLlxuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd3aXRoLW5lc3RlZC1zdGFjay11c2luZy1wYXJhbWV0ZXJzJywge1xuICAgIG9wdGlvbnM6IFsnLS1wYXJhbWV0ZXJzJywgYE15VG9waWNQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fVRoZXJlSXNOb1Nwb29uYF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIC8vIHZlcmlmeSB0aGF0IHdlIG9ubHkgZGVwbG95ZWQgYSBzaW5nbGUgc3RhY2sgKHRoZXJlJ3MgYSBzaW5nbGUgQVJOIGluIHRoZSBvdXRwdXQpXG4gIGV4cGVjdChzdGFja0Fybi5zcGxpdCgnXFxuJykubGVuZ3RoKS50b0VxdWFsKDEpO1xuXG4gIC8vIHZlcmlmeSB0aGUgbnVtYmVyIG9mIHJlc291cmNlcyBpbiB0aGUgc3RhY2tcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja1Jlc291cmNlcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrUmVzb3VyY2VzPy5sZW5ndGgpLnRvRXF1YWwoMSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGhvdXQgZXhlY3V0ZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd0ZXN0LTInLCB7XG4gICAgb3B0aW9uczogWyctLW5vLWV4ZWN1dGUnXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG4gIC8vIHZlcmlmeSB0aGF0IHdlIG9ubHkgZGVwbG95ZWQgYSBzaW5nbGUgc3RhY2sgKHRoZXJlJ3MgYSBzaW5nbGUgQVJOIGluIHRoZSBvdXRwdXQpXG4gIGV4cGVjdChzdGFja0Fybi5zcGxpdCgnXFxuJykubGVuZ3RoKS50b0VxdWFsKDEpO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogc3RhY2tBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5TdGFja1N0YXR1cykudG9FcXVhbCgnUkVWSUVXX0lOX1BST0dSRVNTJyk7XG59KSk7XG5cbmludGVnVGVzdCgnc2VjdXJpdHkgcmVsYXRlZCBjaGFuZ2VzIHdpdGhvdXQgYSBDTEkgYXJlIGV4cGVjdGVkIHRvIGZhaWwnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gcmVkaXJlY3QgL2Rldi9udWxsIHRvIHN0ZGluLCB3aGljaCBtZWFucyB0aGVyZSB3aWxsIG5vdCBiZSB0dHkgYXR0YWNoZWRcbiAgLy8gc2luY2UgdGhpcyBzdGFjayBpbmNsdWRlcyBzZWN1cml0eS1yZWxhdGVkIGNoYW5nZXMsIHRoZSBkZXBsb3ltZW50IHNob3VsZFxuICAvLyBpbW1lZGlhdGVseSBmYWlsIGJlY2F1c2Ugd2UgY2FuJ3QgY29uZmlybSB0aGUgY2hhbmdlc1xuICBjb25zdCBzdGFja05hbWUgPSAnaWFtLXRlc3QnO1xuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGtEZXBsb3koc3RhY2tOYW1lLCB7XG4gICAgb3B0aW9uczogWyc8JywgJy9kZXYvbnVsbCddLCAvLyBINHgsIHRoaXMgb25seSB3b3JrcyBiZWNhdXNlIEkgaGFwcGVuIHRvIGtub3cgd2UgcGFzcyBzaGVsbDogdHJ1ZS5cbiAgICBuZXZlclJlcXVpcmVBcHByb3ZhbDogZmFsc2UsXG4gIH0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG5cbiAgLy8gRW5zdXJlIHN0YWNrIHdhcyBub3QgZGVwbG95ZWRcbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IGZpeHR1cmUuZnVsbFN0YWNrTmFtZShzdGFja05hbWUpLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdkb2VzIG5vdCBleGlzdCcpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aWxkY2FyZCB3aXRoIG91dHB1dHMnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgb3V0cHV0c0ZpbGUgPSBwYXRoLmpvaW4oZml4dHVyZS5pbnRlZ1Rlc3REaXIsICdvdXRwdXRzJywgJ291dHB1dHMuanNvbicpO1xuICBhd2FpdCBmcy5ta2RpcihwYXRoLmRpcm5hbWUob3V0cHV0c0ZpbGUpLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveShbJ291dHB1dHMtdGVzdC0qJ10sIHtcbiAgICBvcHRpb25zOiBbJy0tb3V0cHV0cy1maWxlJywgb3V0cHV0c0ZpbGVdLFxuICB9KTtcblxuICBjb25zdCBvdXRwdXRzID0gSlNPTi5wYXJzZSgoYXdhaXQgZnMucmVhZEZpbGUob3V0cHV0c0ZpbGUsIHsgZW5jb2Rpbmc6ICd1dGYtOCcgfSkpLnRvU3RyaW5nKCkpO1xuICBleHBlY3Qob3V0cHV0cykudG9FcXVhbCh7XG4gICAgW2Ake2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMWBdOiB7XG4gICAgICBUb3BpY05hbWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMU15VG9waWNgLFxuICAgIH0sXG4gICAgW2Ake2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMmBdOiB7XG4gICAgICBUb3BpY05hbWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMk15T3RoZXJUb3BpY2AsXG4gICAgfSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcGFyYW1ldGVycycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LTEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGBUb3BpY05hbWVQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWJhemluZ2FgLFxuICAgIF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogc3RhY2tBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5QYXJhbWV0ZXJzKS50b0VxdWFsKFtcbiAgICB7XG4gICAgICBQYXJhbWV0ZXJLZXk6ICdUb3BpY05hbWVQYXJhbScsXG4gICAgICBQYXJhbWV0ZXJWYWx1ZTogYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9YmF6aW5nYWAsXG4gICAgfSxcbiAgXSk7XG59KSk7XG5cbmludGVnVGVzdCgndXBkYXRlIHRvIHN0YWNrIGluIFJPTExCQUNLX0NPTVBMRVRFIHN0YXRlIHdpbGwgZGVsZXRlIHN0YWNrIGFuZCBjcmVhdGUgYSBuZXcgb25lJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIC8vIEdJVkVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1AYXd3YCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogZml4dHVyZS5mdWxsU3RhY2tOYW1lKCdwYXJhbS10ZXN0LTEnKSxcbiAgfSk7XG5cbiAgY29uc3Qgc3RhY2tBcm4gPSByZXNwb25zZS5TdGFja3M/LlswXS5TdGFja0lkO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1JPTExCQUNLX0NPTVBMRVRFJyk7XG5cbiAgLy8gV0hFTlxuICBjb25zdCBuZXdTdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LTEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGBUb3BpY05hbWVQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWFsbGdvb2RgLFxuICAgIF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIGNvbnN0IG5ld1N0YWNrUmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBuZXdTdGFja0FybixcbiAgfSk7XG5cbiAgLy8gVEhFTlxuICBleHBlY3QgKHN0YWNrQXJuKS5ub3QudG9FcXVhbChuZXdTdGFja0Fybik7IC8vIG5ldyBzdGFjayB3YXMgY3JlYXRlZFxuICBleHBlY3QobmV3U3RhY2tSZXNwb25zZS5TdGFja3M/LlswXS5TdGFja1N0YXR1cykudG9FcXVhbCgnQ1JFQVRFX0NPTVBMRVRFJyk7XG4gIGV4cGVjdChuZXdTdGFja1Jlc3BvbnNlLlN0YWNrcz8uWzBdLlBhcmFtZXRlcnMpLnRvRXF1YWwoW1xuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ1RvcGljTmFtZVBhcmFtJyxcbiAgICAgIFBhcmFtZXRlclZhbHVlOiBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1hbGxnb29kYCxcbiAgICB9LFxuICBdKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdzdGFjayBpbiBVUERBVEVfUk9MTEJBQ0tfQ09NUExFVEUgc3RhdGUgY2FuIGJlIHVwZGF0ZWQnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1uaWNlYCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KTtcblxuICBsZXQgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrcz8uWzBdLlN0YWNrU3RhdHVzKS50b0VxdWFsKCdDUkVBVEVfQ09NUExFVEUnKTtcblxuICAvLyBiYWQgcGFyYW1ldGVyIG5hbWUgd2l0aCBAIHdpbGwgcHV0IHN0YWNrIGludG8gVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1AYXd3YCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpOztcblxuICByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcblxuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1VQREFURV9ST0xMQkFDS19DT01QTEVURScpO1xuXG4gIC8vIFdIRU5cbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3BhcmFtLXRlc3QtMScsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYFRvcGljTmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9YWxsZ29vZGAsXG4gICAgXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG5cbiAgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgLy8gVEhFTlxuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1VQREFURV9DT01QTEVURScpO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uUGFyYW1ldGVycykudG9FcXVhbChbXG4gICAge1xuICAgICAgUGFyYW1ldGVyS2V5OiAnVG9waWNOYW1lUGFyYW0nLFxuICAgICAgUGFyYW1ldGVyVmFsdWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWFsbGdvb2RgLFxuICAgIH0sXG4gIF0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aXRoIHdpbGRjYXJkIGFuZCBwYXJhbWV0ZXJzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LSonLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1wYXJhbS10ZXN0LTE6VG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1iYXppbmdhYCxcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH0tcGFyYW0tdGVzdC0yOk90aGVyVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1UaGF0c015U3BvdGAsXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXBhcmFtLXRlc3QtMzpEaXNwbGF5TmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9SGV5VGhlcmVgLFxuICAgICAgJy0tcGFyYW1ldGVycycsIGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1wYXJhbS10ZXN0LTM6T3RoZXJEaXNwbGF5TmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9QW5vdGhlck9uZWAsXG4gICAgXSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcGFyYW1ldGVycyBtdWx0aScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBwYXJhbVZhbDEgPSBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1iYXppbmdhYDtcbiAgY29uc3QgcGFyYW1WYWwyID0gYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9PWphZ3NoZW1hc2hgO1xuXG4gIGNvbnN0IHN0YWNrQXJuID0gYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3BhcmFtLXRlc3QtMycsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYERpc3BsYXlOYW1lUGFyYW09JHtwYXJhbVZhbDF9YCxcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgT3RoZXJEaXNwbGF5TmFtZVBhcmFtPSR7cGFyYW1WYWwyfWAsXG4gICAgXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG5cbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrcz8uWzBdLlBhcmFtZXRlcnMpLnRvRXF1YWwoW1xuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ0Rpc3BsYXlOYW1lUGFyYW0nLFxuICAgICAgUGFyYW1ldGVyVmFsdWU6IHBhcmFtVmFsMSxcbiAgICB9LFxuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ090aGVyRGlzcGxheU5hbWVQYXJhbScsXG4gICAgICBQYXJhbWV0ZXJWYWx1ZTogcGFyYW1WYWwyLFxuICAgIH0sXG4gIF0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aXRoIG5vdGlmaWNhdGlvbiBBUk4nLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgdG9waWNOYW1lID0gYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtdG9waWNgO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3Muc25zKCdjcmVhdGVUb3BpYycsIHsgTmFtZTogdG9waWNOYW1lIH0pO1xuICBjb25zdCB0b3BpY0FybiA9IHJlc3BvbnNlLlRvcGljQXJuITtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgndGVzdC0yJywge1xuICAgICAgb3B0aW9uczogWyctLW5vdGlmaWNhdGlvbi1hcm5zJywgdG9waWNBcm5dLFxuICAgIH0pO1xuXG4gICAgLy8gdmVyaWZ5IHRoYXQgdGhlIHN0YWNrIHdlIGRlcGxveWVkIGhhcyBvdXIgbm90aWZpY2F0aW9uIEFSTlxuICAgIGNvbnN0IGRlc2NyaWJlUmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgICBTdGFja05hbWU6IGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0yJyksXG4gICAgfSk7XG4gICAgZXhwZWN0KGRlc2NyaWJlUmVzcG9uc2UuU3RhY2tzPy5bMF0uTm90aWZpY2F0aW9uQVJOcykudG9FcXVhbChbdG9waWNBcm5dKTtcbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmF3cy5zbnMoJ2RlbGV0ZVRvcGljJywge1xuICAgICAgVG9waWNBcm46IHRvcGljQXJuLFxuICAgIH0pO1xuICB9XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcm9sZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCByb2xlTmFtZSA9IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS10ZXN0LXJvbGVgO1xuXG4gIGF3YWl0IGRlbGV0ZVJvbGUoKTtcblxuICBjb25zdCBjcmVhdGVSZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmlhbSgnY3JlYXRlUm9sZScsIHtcbiAgICBSb2xlTmFtZTogcm9sZU5hbWUsXG4gICAgQXNzdW1lUm9sZVBvbGljeURvY3VtZW50OiBKU09OLnN0cmluZ2lmeSh7XG4gICAgICBWZXJzaW9uOiAnMjAxMi0xMC0xNycsXG4gICAgICBTdGF0ZW1lbnQ6IFt7XG4gICAgICAgIEFjdGlvbjogJ3N0czpBc3N1bWVSb2xlJyxcbiAgICAgICAgUHJpbmNpcGFsOiB7IFNlcnZpY2U6ICdjbG91ZGZvcm1hdGlvbi5hbWF6b25hd3MuY29tJyB9LFxuICAgICAgICBFZmZlY3Q6ICdBbGxvdycsXG4gICAgICB9LCB7XG4gICAgICAgIEFjdGlvbjogJ3N0czpBc3N1bWVSb2xlJyxcbiAgICAgICAgUHJpbmNpcGFsOiB7IEFXUzogKGF3YWl0IGZpeHR1cmUuYXdzLnN0cygnZ2V0Q2FsbGVySWRlbnRpdHknLCB7fSkpLkFybiB9LFxuICAgICAgICBFZmZlY3Q6ICdBbGxvdycsXG4gICAgICB9XSxcbiAgICB9KSxcbiAgfSk7XG4gIGNvbnN0IHJvbGVBcm4gPSBjcmVhdGVSZXNwb25zZS5Sb2xlLkFybjtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmF3cy5pYW0oJ3B1dFJvbGVQb2xpY3knLCB7XG4gICAgICBSb2xlTmFtZTogcm9sZU5hbWUsXG4gICAgICBQb2xpY3lOYW1lOiAnRGVmYXVsdFBvbGljeScsXG4gICAgICBQb2xpY3lEb2N1bWVudDogSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICBWZXJzaW9uOiAnMjAxMi0xMC0xNycsXG4gICAgICAgIFN0YXRlbWVudDogW3tcbiAgICAgICAgICBBY3Rpb246ICcqJyxcbiAgICAgICAgICBSZXNvdXJjZTogJyonLFxuICAgICAgICAgIEVmZmVjdDogJ0FsbG93JyxcbiAgICAgICAgfV0sXG4gICAgICB9KSxcbiAgICB9KTtcblxuICAgIGF3YWl0IHJldHJ5KGZpeHR1cmUub3V0cHV0LCAnVHJ5aW5nIHRvIGFzc3VtZSBmcmVzaCByb2xlJywgcmV0cnkuZm9yU2Vjb25kcygzMDApLCBhc3luYyAoKSA9PiB7XG4gICAgICBhd2FpdCBmaXh0dXJlLmF3cy5zdHMoJ2Fzc3VtZVJvbGUnLCB7XG4gICAgICAgIFJvbGVBcm46IHJvbGVBcm4sXG4gICAgICAgIFJvbGVTZXNzaW9uTmFtZTogJ3Rlc3RpbmcnLFxuICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICAvLyBJbiBwcmluY2lwbGUsIHRoZSByb2xlIGhhcyByZXBsaWNhdGVkIGZyb20gJ3VzLWVhc3QtMScgdG8gd2hlcmV2ZXIgd2UncmUgdGVzdGluZy5cbiAgICAvLyBHaXZlIGl0IGEgbGl0dGxlIG1vcmUgc2xlZXAgdG8gbWFrZSBzdXJlIENsb3VkRm9ybWF0aW9uIGlzIG5vdCBoaXR0aW5nIGEgYm94XG4gICAgLy8gdGhhdCBkb2Vzbid0IGhhdmUgaXQgeWV0LlxuICAgIGF3YWl0IHNsZWVwKDUwMDApO1xuXG4gICAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtMicsIHtcbiAgICAgIG9wdGlvbnM6IFsnLS1yb2xlLWFybicsIHJvbGVBcm5dLFxuICAgIH0pO1xuXG4gICAgLy8gSW1tZWRpYXRlbHkgZGVsZXRlIHRoZSBzdGFjayBhZ2FpbiBiZWZvcmUgd2UgZGVsZXRlIHRoZSByb2xlLlxuICAgIC8vXG4gICAgLy8gU2luY2Ugcm9sZXMgYXJlIHN0aWNreSwgaWYgd2UgZGVsZXRlIHRoZSByb2xlIGJlZm9yZSB0aGUgc3RhY2ssIHN1YnNlcXVlbnQgRGVsZXRlU3RhY2tcbiAgICAvLyBvcGVyYXRpb25zIHdpbGwgZmFpbCB3aGVuIENsb3VkRm9ybWF0aW9uIHRyaWVzIHRvIGFzc3VtZSB0aGUgcm9sZSB0aGF0J3MgYWxyZWFkeSBnb25lLlxuICAgIGF3YWl0IGZpeHR1cmUuY2RrRGVzdHJveSgndGVzdC0yJyk7XG5cbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBkZWxldGVSb2xlKCk7XG4gIH1cblxuICBhc3luYyBmdW5jdGlvbiBkZWxldGVSb2xlKCkge1xuICAgIHRyeSB7XG4gICAgICBmb3IgKGNvbnN0IHBvbGljeU5hbWUgb2YgKGF3YWl0IGZpeHR1cmUuYXdzLmlhbSgnbGlzdFJvbGVQb2xpY2llcycsIHsgUm9sZU5hbWU6IHJvbGVOYW1lIH0pKS5Qb2xpY3lOYW1lcykge1xuICAgICAgICBhd2FpdCBmaXh0dXJlLmF3cy5pYW0oJ2RlbGV0ZVJvbGVQb2xpY3knLCB7XG4gICAgICAgICAgUm9sZU5hbWU6IHJvbGVOYW1lLFxuICAgICAgICAgIFBvbGljeU5hbWU6IHBvbGljeU5hbWUsXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgYXdhaXQgZml4dHVyZS5hd3MuaWFtKCdkZWxldGVSb2xlJywgeyBSb2xlTmFtZTogcm9sZU5hbWUgfSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgaWYgKGUubWVzc2FnZS5pbmRleE9mKCdjYW5ub3QgYmUgZm91bmQnKSA+IC0xKSB7IHJldHVybjsgfVxuICAgICAgdGhyb3cgZTtcbiAgICB9XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdjZGsgZGlmZicsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBkaWZmMSA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKTtcbiAgZXhwZWN0KGRpZmYxKS50b0NvbnRhaW4oJ0FXUzo6U05TOjpUb3BpYycpO1xuXG4gIGNvbnN0IGRpZmYyID0gYXdhaXQgZml4dHVyZS5jZGsoWydkaWZmJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pO1xuICBleHBlY3QoZGlmZjIpLnRvQ29udGFpbignQVdTOjpTTlM6OlRvcGljJyk7XG5cbiAgLy8gV2UgY2FuIG1ha2UgaXQgZmFpbCBieSBwYXNzaW5nIC0tZmFpbFxuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGsoWydkaWZmJywgJy0tZmFpbCcsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKSlcbiAgICAucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBkaWZmIC0tZmFpbCBvbiBtdWx0aXBsZSBzdGFja3MgZXhpdHMgd2l0aCBlcnJvciBpZiBhbnkgb2YgdGhlIHN0YWNrcyBjb250YWlucyBhIGRpZmYnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgY29uc3QgZGlmZjEgPSBhd2FpdCBmaXh0dXJlLmNkayhbJ2RpZmYnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ3Rlc3QtMScpXSk7XG4gIGV4cGVjdChkaWZmMSkudG9Db250YWluKCdBV1M6OlNOUzo6VG9waWMnKTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgndGVzdC0yJyk7XG4gIGNvbnN0IGRpZmYyID0gYXdhaXQgZml4dHVyZS5jZGsoWydkaWZmJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pO1xuICBleHBlY3QoZGlmZjIpLnRvQ29udGFpbignVGhlcmUgd2VyZSBubyBkaWZmZXJlbmNlcycpO1xuXG4gIC8vIFdIRU4gLyBUSEVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2RpZmYnLCAnLS1mYWlsJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTEnKSwgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG59KSk7XG5cbmludGVnVGVzdCgnY2RrIGRpZmYgLS1mYWlsIHdpdGggbXVsdGlwbGUgc3RhY2sgZXhpdHMgd2l0aCBpZiBhbnkgb2YgdGhlIHN0YWNrcyBjb250YWlucyBhIGRpZmYnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtMScpO1xuICBjb25zdCBkaWZmMSA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKTtcbiAgZXhwZWN0KGRpZmYxKS50b0NvbnRhaW4oJ1RoZXJlIHdlcmUgbm8gZGlmZmVyZW5jZXMnKTtcblxuICBjb25zdCBkaWZmMiA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0yJyldKTtcbiAgZXhwZWN0KGRpZmYyKS50b0NvbnRhaW4oJ0FXUzo6U05TOjpUb3BpYycpO1xuXG4gIC8vIFdIRU4gLyBUSEVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2RpZmYnLCAnLS1mYWlsJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTEnKSwgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHN0YWNrIHdpdGggZG9ja2VyIGFzc2V0Jywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdkb2NrZXInKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdkZXBsb3kgYW5kIHRlc3Qgc3RhY2sgd2l0aCBsYW1iZGEgYXNzZXQnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywgeyBjYXB0dXJlU3RkZXJyOiBmYWxzZSB9KTtcblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgY29uc3QgbGFtYmRhQXJuID0gcmVzcG9uc2UuU3RhY2tzPy5bMF0uT3V0cHV0cz8uWzBdLk91dHB1dFZhbHVlO1xuICBpZiAobGFtYmRhQXJuID09PSB1bmRlZmluZWQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1N0YWNrIGRpZCBub3QgaGF2ZSBleHBlY3RlZCBMYW1iZGEgQVJOIG91dHB1dCcpO1xuICB9XG5cbiAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgZml4dHVyZS5hd3MubGFtYmRhKCdpbnZva2UnLCB7XG4gICAgRnVuY3Rpb25OYW1lOiBsYW1iZGFBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChKU09OLnN0cmluZ2lmeShvdXRwdXQuUGF5bG9hZCkpLnRvQ29udGFpbignZGVhciBhc3NldCcpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBscycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBsaXN0aW5nID0gYXdhaXQgZml4dHVyZS5jZGsoWydscyddLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuXG4gIGNvbnN0IGV4cGVjdGVkU3RhY2tzID0gW1xuICAgICdjb25kaXRpb25hbC1yZXNvdXJjZScsXG4gICAgJ2RvY2tlcicsXG4gICAgJ2RvY2tlci13aXRoLWN1c3RvbS1maWxlJyxcbiAgICAnZmFpbGVkJyxcbiAgICAnaWFtLXRlc3QnLFxuICAgICdsYW1iZGEnLFxuICAgICdtaXNzaW5nLXNzbS1wYXJhbWV0ZXInLFxuICAgICdvcmRlci1wcm92aWRpbmcnLFxuICAgICdvdXRwdXRzLXRlc3QtMScsXG4gICAgJ291dHB1dHMtdGVzdC0yJyxcbiAgICAncGFyYW0tdGVzdC0xJyxcbiAgICAncGFyYW0tdGVzdC0yJyxcbiAgICAncGFyYW0tdGVzdC0zJyxcbiAgICAndGVybWluYXRpb24tcHJvdGVjdGlvbicsXG4gICAgJ3Rlc3QtMScsXG4gICAgJ3Rlc3QtMicsXG4gICAgJ3dpdGgtbmVzdGVkLXN0YWNrJyxcbiAgICAnd2l0aC1uZXN0ZWQtc3RhY2stdXNpbmctcGFyYW1ldGVycycsXG4gICAgJ29yZGVyLWNvbnN1bWluZycsXG4gIF07XG5cbiAgZm9yIChjb25zdCBzdGFjayBvZiBleHBlY3RlZFN0YWNrcykge1xuICAgIGV4cGVjdChsaXN0aW5nKS50b0NvbnRhaW4oZml4dHVyZS5mdWxsU3RhY2tOYW1lKHN0YWNrKSk7XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdkZXBsb3kgc3RhY2sgd2l0aG91dCByZXNvdXJjZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICAvLyBEZXBsb3kgdGhlIHN0YWNrIHdpdGhvdXQgcmVzb3VyY2VzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdjb25kaXRpb25hbC1yZXNvdXJjZScsIHsgbW9kRW52OiB7IE5PX1JFU09VUkNFOiAnVFJVRScgfSB9KTtcblxuICAvLyBUaGlzIHNob3VsZCBoYXZlIHN1Y2NlZWRlZCBidXQgbm90IGRlcGxveWVkIHRoZSBzdGFjay5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2NvbmRpdGlvbmFsLXJlc291cmNlJykgfSkpXG4gICAgLnJlamVjdHMudG9UaHJvdygnY29uZGl0aW9uYWwtcmVzb3VyY2UgZG9lcyBub3QgZXhpc3QnKTtcblxuICAvLyBEZXBsb3kgdGhlIHN0YWNrIHdpdGggcmVzb3VyY2VzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdjb25kaXRpb25hbC1yZXNvdXJjZScpO1xuXG4gIC8vIFRoZW4gYWdhaW4gV0lUSE9VVCByZXNvdXJjZXMgKHRoaXMgc2hvdWxkIGRlc3Ryb3kgdGhlIHN0YWNrKVxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnY29uZGl0aW9uYWwtcmVzb3VyY2UnLCB7IG1vZEVudjogeyBOT19SRVNPVVJDRTogJ1RSVUUnIH0gfSk7XG5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2NvbmRpdGlvbmFsLXJlc291cmNlJykgfSkpXG4gICAgLnJlamVjdHMudG9UaHJvdygnY29uZGl0aW9uYWwtcmVzb3VyY2UgZG9lcyBub3QgZXhpc3QnKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdJQU0gZGlmZicsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBvdXRwdXQgPSBhd2FpdCBmaXh0dXJlLmNkayhbJ2RpZmYnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2lhbS10ZXN0JyldKTtcblxuICAvLyBSb3VnaGx5IGNoZWNrIGZvciBhIHRhYmxlIGxpa2UgdGhpczpcbiAgLy9cbiAgLy8g4pSM4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSALeKUgOKUgOKUrOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUkFxuICAvLyDilIIgICDilIIgUmVzb3VyY2UgICAgICAgIOKUgiBFZmZlY3Qg4pSCIEFjdGlvbiAgICAgICAgIOKUgiBQcmluY2lwYWwgICAgICAgICAgICAgICAgICAgICDilIIgQ29uZGl0aW9uIOKUglxuICAvLyDilJzilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilKRcbiAgLy8g4pSCICsg4pSCICR7U29tZVJvbGUuQXJufSDilIIgQWxsb3cgIOKUgiBzdHM6QXNzdW1lUm9sZSDilIIgU2VydmljZTplYzIuYW1hem9uYXdzLmNvbSAgICAg4pSCICAgICAgICAgICDilIJcbiAgLy8g4pSU4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYXG5cbiAgZXhwZWN0KG91dHB1dCkudG9Db250YWluKCcke1NvbWVSb2xlLkFybn0nKTtcbiAgZXhwZWN0KG91dHB1dCkudG9Db250YWluKCdzdHM6QXNzdW1lUm9sZScpO1xuICBleHBlY3Qob3V0cHV0KS50b0NvbnRhaW4oJ2VjMi5hbWF6b25hd3MuY29tJyk7XG59KSk7XG5cbmludGVnVGVzdCgnZmFzdCBkZXBsb3knLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gd2UgYXJlIHVzaW5nIGEgc3RhY2sgd2l0aCBhIG5lc3RlZCBzdGFjayBiZWNhdXNlIENGTiB3aWxsIGFsd2F5cyBhdHRlbXB0IHRvXG4gIC8vIHVwZGF0ZSBhIG5lc3RlZCBzdGFjaywgd2hpY2ggd2lsbCBhbGxvdyB1cyB0byB2ZXJpZnkgdGhhdCB1cGRhdGVzIGFyZSBhY3R1YWxseVxuICAvLyBza2lwcGVkIHVubGVzcyAtLWZvcmNlIGlzIHNwZWNpZmllZC5cbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuICBjb25zdCBjaGFuZ2VTZXQxID0gYXdhaXQgZ2V0TGF0ZXN0Q2hhbmdlU2V0KCk7XG5cbiAgLy8gRGVwbG95IHRoZSBzYW1lIHN0YWNrIGFnYWluLCB0aGVyZSBzaG91bGQgYmUgbm8gbmV3IGNoYW5nZSBzZXQgY3JlYXRlZFxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snKTtcbiAgY29uc3QgY2hhbmdlU2V0MiA9IGF3YWl0IGdldExhdGVzdENoYW5nZVNldCgpO1xuICBleHBlY3QoY2hhbmdlU2V0Mi5DaGFuZ2VTZXRJZCkudG9FcXVhbChjaGFuZ2VTZXQxLkNoYW5nZVNldElkKTtcblxuICAvLyBEZXBsb3kgdGhlIHN0YWNrIGFnYWluIHdpdGggLS1mb3JjZSwgbm93IHdlIHNob3VsZCBjcmVhdGUgYSBjaGFuZ2VzZXRcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3dpdGgtbmVzdGVkLXN0YWNrJywgeyBvcHRpb25zOiBbJy0tZm9yY2UnXSB9KTtcbiAgY29uc3QgY2hhbmdlU2V0MyA9IGF3YWl0IGdldExhdGVzdENoYW5nZVNldCgpO1xuICBleHBlY3QoY2hhbmdlU2V0My5DaGFuZ2VTZXRJZCkubm90LnRvRXF1YWwoY2hhbmdlU2V0Mi5DaGFuZ2VTZXRJZCk7XG5cbiAgLy8gRGVwbG95IHRoZSBzdGFjayBhZ2FpbiB3aXRoIHRhZ3MsIGV4cGVjdGVkIHRvIGNyZWF0ZSBhIG5ldyBjaGFuZ2VzZXRcbiAgLy8gZXZlbiB0aG91Z2ggdGhlIHJlc291cmNlcyBkaWRuJ3QgY2hhbmdlLlxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snLCB7IG9wdGlvbnM6IFsnLS10YWdzJywgJ2tleT12YWx1ZSddIH0pO1xuICBjb25zdCBjaGFuZ2VTZXQ0ID0gYXdhaXQgZ2V0TGF0ZXN0Q2hhbmdlU2V0KCk7XG4gIGV4cGVjdChjaGFuZ2VTZXQ0LkNoYW5nZVNldElkKS5ub3QudG9FcXVhbChjaGFuZ2VTZXQzLkNoYW5nZVNldElkKTtcblxuICBhc3luYyBmdW5jdGlvbiBnZXRMYXRlc3RDaGFuZ2VTZXQoKSB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7IFN0YWNrTmFtZTogc3RhY2tBcm4gfSk7XG4gICAgaWYgKCFyZXNwb25zZS5TdGFja3M/LlswXSkgeyB0aHJvdyBuZXcgRXJyb3IoJ0RpZCBub3QgZ2V0IGEgQ2hhbmdlU2V0IGF0IGFsbCcpOyB9XG4gICAgZml4dHVyZS5sb2coYEZvdW5kIENoYW5nZSBTZXQgJHtyZXNwb25zZS5TdGFja3M/LlswXS5DaGFuZ2VTZXRJZH1gKTtcbiAgICByZXR1cm4gcmVzcG9uc2UuU3RhY2tzPy5bMF07XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdmYWlsZWQgZGVwbG95IGRvZXMgbm90IGhhbmcnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gdGhpcyB3aWxsIGhhbmcgaWYgd2UgaW50cm9kdWNlIGh0dHBzOi8vZ2l0aHViLmNvbS9hd3MvYXdzLWNkay9pc3N1ZXMvNjQwMyBhZ2Fpbi5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuY2RrRGVwbG95KCdmYWlsZWQnKSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NhbiBzdGlsbCBsb2FkIG9sZCBhc3NlbWJsaWVzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGN4QXNtRGlyID0gcGF0aC5qb2luKG9zLnRtcGRpcigpLCAnY2RrLWludGVnLWN4Jyk7XG5cbiAgY29uc3QgdGVzdEFzc2VtYmxpZXNEaXJlY3RvcnkgPSBwYXRoLmpvaW4oX19kaXJuYW1lLCAnY2xvdWQtYXNzZW1ibGllcycpO1xuICBmb3IgKGNvbnN0IGFzbWRpciBvZiBhd2FpdCBsaXN0Q2hpbGREaXJzKHRlc3RBc3NlbWJsaWVzRGlyZWN0b3J5KSkge1xuICAgIGZpeHR1cmUubG9nKGBBU1NFTUJMWSAke2FzbWRpcn1gKTtcbiAgICBhd2FpdCBjbG9uZURpcmVjdG9yeShhc21kaXIsIGN4QXNtRGlyKTtcblxuICAgIC8vIFNvbWUgZmlsZXMgaW4gdGhlIGFzbSBkaXJlY3RvcnkgdGhhdCBoYXZlIGEgLmpzIGV4dGVuc2lvbiBhcmVcbiAgICAvLyBhY3R1YWxseSB0cmVhdGVkIGFzIHRlbXBsYXRlcy4gRXZhbHVhdGUgdGhlbSB1c2luZyBOb2RlSlMuXG4gICAgY29uc3QgdGVtcGxhdGVzID0gYXdhaXQgbGlzdENoaWxkcmVuKGN4QXNtRGlyLCBmdWxsUGF0aCA9PiBQcm9taXNlLnJlc29sdmUoZnVsbFBhdGguZW5kc1dpdGgoJy5qcycpKSk7XG4gICAgZm9yIChjb25zdCB0ZW1wbGF0ZSBvZiB0ZW1wbGF0ZXMpIHtcbiAgICAgIGNvbnN0IHRhcmdldE5hbWUgPSB0ZW1wbGF0ZS5yZXBsYWNlKC8uanMkLywgJycpO1xuICAgICAgYXdhaXQgc2hlbGwoW3Byb2Nlc3MuZXhlY1BhdGgsIHRlbXBsYXRlLCAnPicsIHRhcmdldE5hbWVdLCB7XG4gICAgICAgIGN3ZDogY3hBc21EaXIsXG4gICAgICAgIG91dHB1dDogZml4dHVyZS5vdXRwdXQsXG4gICAgICAgIG1vZEVudjoge1xuICAgICAgICAgIFRFU1RfQUNDT1VOVDogYXdhaXQgZml4dHVyZS5hd3MuYWNjb3VudCgpLFxuICAgICAgICAgIFRFU1RfUkVHSU9OOiBmaXh0dXJlLmF3cy5yZWdpb24sXG4gICAgICAgIH0sXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICAvLyBVc2UgdGhpcyBkaXJlY3RvcnkgYXMgYSBDbG91ZCBBc3NlbWJseVxuICAgIGNvbnN0IG91dHB1dCA9IGF3YWl0IGZpeHR1cmUuY2RrKFtcbiAgICAgICctLWFwcCcsIGN4QXNtRGlyLFxuICAgICAgJy12JyxcbiAgICAgICdzeW50aCcsXG4gICAgXSk7XG5cbiAgICAvLyBBc3NlcnQgdGhhdCB0aGVyZSB3YXMgbm8gcHJvdmlkZXJFcnJvciBpbiBDREsncyBzdGRlcnJcbiAgICAvLyBCZWNhdXNlIHdlIHJlbHkgb24gdGhlIGFwcC9mcmFtZXdvcmsgdG8gYWN0dWFsbHkgZXJyb3IgaW4gY2FzZSB0aGVcbiAgICAvLyBwcm92aWRlciBmYWlscywgd2UgaW5zcGVjdCB0aGUgbG9ncyBoZXJlLlxuICAgIGV4cGVjdChvdXRwdXQpLm5vdC50b0NvbnRhaW4oJyRwcm92aWRlckVycm9yJyk7XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdnZW5lcmF0aW5nIGFuZCBsb2FkaW5nIGFzc2VtYmx5Jywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGFzbU91dHB1dERpciA9IGAke2ZpeHR1cmUuaW50ZWdUZXN0RGlyfS1jZGstaW50ZWctYXNtYDtcbiAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ3JtJywgJy1yZicsIGFzbU91dHB1dERpcl0pO1xuXG4gIC8vIFN5bnRoZXNpemUgYSBDbG91ZCBBc3NlbWJseSB0b3RoZSBkZWZhdWx0IGRpcmVjdG9yeSAoY2RrLm91dCkgYW5kIGEgc3BlY2lmaWMgZGlyZWN0b3J5LlxuICBhd2FpdCBmaXh0dXJlLmNkayhbJ3N5bnRoJ10pO1xuICBhd2FpdCBmaXh0dXJlLmNkayhbJ3N5bnRoJywgJy0tb3V0cHV0JywgYXNtT3V0cHV0RGlyXSk7XG5cbiAgLy8gY2RrLm91dCBpbiB0aGUgY3VycmVudCBkaXJlY3RvcnkgYW5kIHRoZSBpbmRpY2F0ZWQgLS1vdXRwdXQgc2hvdWxkIGJlIHRoZSBzYW1lXG4gIGF3YWl0IGZpeHR1cmUuc2hlbGwoWydkaWZmJywgJ2Nkay5vdXQnLCBhc21PdXRwdXREaXJdKTtcblxuICAvLyBDaGVjayB0aGF0IHdlIGNhbiAnbHMnIHRoZSBzeW50aGVzaXplZCBhc20uXG4gIC8vIENoYW5nZSB0byBzb21lIHJhbmRvbSBkaXJlY3RvcnkgdG8gbWFrZSBzdXJlIHdlJ3JlIG5vdCBhY2NpZGVudGFsbHkgbG9hZGluZyBjZGsuanNvblxuICBjb25zdCBsaXN0ID0gYXdhaXQgZml4dHVyZS5jZGsoWyctLWFwcCcsIGFzbU91dHB1dERpciwgJ2xzJ10sIHsgY3dkOiBvcy50bXBkaXIoKSB9KTtcbiAgLy8gU2FtZSBzdGFja3Mgd2Uga25vdyBhcmUgaW4gdGhlIGFwcFxuICBleHBlY3QobGlzdCkudG9Db250YWluKGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1sYW1iZGFgKTtcbiAgZXhwZWN0KGxpc3QpLnRvQ29udGFpbihgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH0tdGVzdC0xYCk7XG4gIGV4cGVjdChsaXN0KS50b0NvbnRhaW4oYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMmApO1xuXG4gIC8vIENoZWNrIHRoYXQgd2UgY2FuIHVzZSAnLicgYW5kIGp1c3Qgc3ludGggLHRoZSBnZW5lcmF0ZWQgYXNtXG4gIGNvbnN0IHN0YWNrVGVtcGxhdGUgPSBhd2FpdCBmaXh0dXJlLmNkayhbJy0tYXBwJywgJy4nLCAnc3ludGgnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ3Rlc3QtMicpXSwge1xuICAgIGN3ZDogYXNtT3V0cHV0RGlyLFxuICB9KTtcbiAgZXhwZWN0KHN0YWNrVGVtcGxhdGUpLnRvQ29udGFpbigndG9waWMxNTJEODRBMzcnKTtcblxuICAvLyBEZXBsb3kgYSBMYW1iZGEgZnJvbSB0aGUgY29waWVkIGFzbVxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywgeyBvcHRpb25zOiBbJy1hJywgJy4nXSwgY3dkOiBhc21PdXRwdXREaXIgfSk7XG5cbiAgLy8gUmVtb3ZlIChyZW5hbWUpIHRoZSBvcmlnaW5hbCBjdXN0b20gZG9ja2VyIGZpbGUgdGhhdCB3YXMgdXNlZCBkdXJpbmcgc3ludGguXG4gIC8vIHRoaXMgdmVyaWZpZXMgdGhhdCB0aGUgYXNzZW1seSBoYXMgYSBjb3B5IG9mIGl0IGFuZCB0aGF0IHRoZSBtYW5pZmVzdCB1c2VzXG4gIC8vIHJlbGF0aXZlIHBhdGhzIHRvIHJlZmVyZW5jZSB0byBpdC5cbiAgY29uc3QgY3VzdG9tRG9ja2VyRmlsZSA9IHBhdGguam9pbihmaXh0dXJlLmludGVnVGVzdERpciwgJ2RvY2tlcicsICdEb2NrZXJmaWxlLkN1c3RvbScpO1xuICBhd2FpdCBmcy5yZW5hbWUoY3VzdG9tRG9ja2VyRmlsZSwgYCR7Y3VzdG9tRG9ja2VyRmlsZX1+YCk7XG4gIHRyeSB7XG5cbiAgICAvLyBkZXBsb3kgYSBkb2NrZXIgaW1hZ2Ugd2l0aCBjdXN0b20gZmlsZSB3aXRob3V0IHN5bnRoICh1c2VzIGFzc2V0cylcbiAgICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnZG9ja2VyLXdpdGgtY3VzdG9tLWZpbGUnLCB7IG9wdGlvbnM6IFsnLWEnLCAnLiddLCBjd2Q6IGFzbU91dHB1dERpciB9KTtcblxuICB9IGZpbmFsbHkge1xuICAgIC8vIFJlbmFtZSBiYWNrIHRvIHJlc3RvcmUgZml4dHVyZSB0byBvcmlnaW5hbCBzdGF0ZVxuICAgIGF3YWl0IGZzLnJlbmFtZShgJHtjdXN0b21Eb2NrZXJGaWxlfX5gLCBjdXN0b21Eb2NrZXJGaWxlKTtcbiAgfVxufSkpO1xuXG5pbnRlZ1Rlc3QoJ3RlbXBsYXRlcyBvbiBkaXNrIGNvbnRhaW4gbWV0YWRhdGEgcmVzb3VyY2UsIGFsc28gaW4gbmVzdGVkIGFzc2VtYmxpZXMnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gU3ludGggZmlyc3QsIGFuZCBzd2l0Y2ggb24gdmVyc2lvbiByZXBvcnRpbmcgYmVjYXVzZSBjZGsuanNvbiBpcyBkaXNhYmxpbmcgaXRcbiAgYXdhaXQgZml4dHVyZS5jZGsoWydzeW50aCcsICctLXZlcnNpb24tcmVwb3J0aW5nPXRydWUnXSk7XG5cbiAgLy8gTG9hZCB0ZW1wbGF0ZSBmcm9tIGRpc2sgZnJvbSByb290IGFzc2VtYmx5XG4gIGNvbnN0IHRlbXBsYXRlQ29udGVudHMgPSBhd2FpdCBmaXh0dXJlLnNoZWxsKFsnY2F0JywgJ2Nkay5vdXQvKi1sYW1iZGEudGVtcGxhdGUuanNvbiddKTtcblxuICBleHBlY3QoSlNPTi5wYXJzZSh0ZW1wbGF0ZUNvbnRlbnRzKS5SZXNvdXJjZXMuQ0RLTWV0YWRhdGEpLnRvQmVUcnV0aHkoKTtcblxuICAvLyBMb2FkIHRlbXBsYXRlIGZyb20gbmVzdGVkIGFzc2VtYmx5XG4gIGNvbnN0IG5lc3RlZFRlbXBsYXRlQ29udGVudHMgPSBhd2FpdCBmaXh0dXJlLnNoZWxsKFsnY2F0JywgJ2Nkay5vdXQvYXNzZW1ibHktKi1zdGFnZS8qLXN0YWdlLVN0YWNrSW5TdGFnZS50ZW1wbGF0ZS5qc29uJ10pO1xuXG4gIGV4cGVjdChKU09OLnBhcnNlKG5lc3RlZFRlbXBsYXRlQ29udGVudHMpLlJlc291cmNlcy5DREtNZXRhZGF0YSkudG9CZVRydXRoeSgpO1xufSkpO1xuXG5hc3luYyBmdW5jdGlvbiBsaXN0Q2hpbGRyZW4ocGFyZW50OiBzdHJpbmcsIHByZWQ6ICh4OiBzdHJpbmcpID0+IFByb21pc2U8Ym9vbGVhbj4pIHtcbiAgY29uc3QgcmV0ID0gbmV3IEFycmF5PHN0cmluZz4oKTtcbiAgZm9yIChjb25zdCBjaGlsZCBvZiBhd2FpdCBmcy5yZWFkZGlyKHBhcmVudCwgeyBlbmNvZGluZzogJ3V0Zi04JyB9KSkge1xuICAgIGNvbnN0IGZ1bGxQYXRoID0gcGF0aC5qb2luKHBhcmVudCwgY2hpbGQudG9TdHJpbmcoKSk7XG4gICAgaWYgKGF3YWl0IHByZWQoZnVsbFBhdGgpKSB7XG4gICAgICByZXQucHVzaChmdWxsUGF0aCk7XG4gICAgfVxuICB9XG4gIHJldHVybiByZXQ7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGxpc3RDaGlsZERpcnMocGFyZW50OiBzdHJpbmcpIHtcbiAgcmV0dXJuIGxpc3RDaGlsZHJlbihwYXJlbnQsIGFzeW5jIChmdWxsUGF0aDogc3RyaW5nKSA9PiAoYXdhaXQgZnMuc3RhdChmdWxsUGF0aCkpLmlzRGlyZWN0b3J5KCkpO1xufVxuIl19 \ No newline at end of file From ff5838fc9d6421bac4e0b54d73f0fc60f8e33fb2 Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Fri, 25 Sep 2020 16:02:11 -0700 Subject: [PATCH 16/46] chore: patch regression tests v1.64.1 (#10548) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cli-regression-patches/v1.64.1/NOTES.md | 3 + .../v1.64.1/cdk-helpers.js | 324 ++++++++++ .../v1.64.1/cli.integtest.js | 599 ++++++++++++++++++ 3 files changed, 926 insertions(+) create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md new file mode 100644 index 0000000000000..1cb31072ab5de --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/NOTES.md @@ -0,0 +1,3 @@ +Added a `-v` switch to the cdk executions that also needs to be +applied to the regression tests so we have a better chance +of catching sporadically failing tests in the act. \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js new file mode 100644 index 0000000000000..ef82e3d3edace --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cdk-helpers.js @@ -0,0 +1,324 @@ +"use strict"; +var _a, _b; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.randomString = exports.rimraf = exports.shell = exports.TestFixture = exports.cloneDirectory = exports.withDefaultFixture = exports.withCdkApp = exports.withAws = void 0; +const child_process = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const resource_pool_1 = require("./resource-pool"); +const REGIONS = process.env.AWS_REGIONS + ? process.env.AWS_REGIONS.split(',') + : [(_b = (_a = process.env.AWS_REGION) !== null && _a !== void 0 ? _a : process.env.AWS_DEFAULT_REGION) !== null && _b !== void 0 ? _b : 'us-east-1']; +process.stdout.write(`Using regions: ${REGIONS}\n`); +const REGION_POOL = new resource_pool_1.ResourcePool(REGIONS); +/** + * Higher order function to execute a block with an AWS client setup + * + * Allocate the next region from the REGION pool and dispose it afterwards. + */ +function withAws(block) { + return (context) => REGION_POOL.using(async (region) => { + const aws = await aws_helpers_1.AwsClients.forRegion(region, context.output); + await sanityCheck(aws); + return block({ ...context, aws }); + }); +} +exports.withAws = withAws; +/** + * Higher order function to execute a block with a CDK app fixture + * + * Requires an AWS client to be passed in. + * + * For backwards compatibility with existing tests (so we don't have to change + * too much) the inner block is expected to take a `TestFixture` object. + */ +function withCdkApp(block) { + return async (context) => { + const randy = randomString(); + const stackNamePrefix = `cdktest-${randy}`; + const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); + context.output.write(` Stack prefix: ${stackNamePrefix}\n`); + context.output.write(` Test directory: ${integTestDir}\n`); + context.output.write(` Region: ${context.aws.region}\n`); + await cloneDirectory(path.join(__dirname, 'app'), integTestDir, context.output); + const fixture = new TestFixture(integTestDir, stackNamePrefix, context.output, context.aws); + let success = true; + try { + await fixture.shell(['npm', 'install', + '@aws-cdk/core', + '@aws-cdk/aws-sns', + '@aws-cdk/aws-iam', + '@aws-cdk/aws-lambda', + '@aws-cdk/aws-ssm', + '@aws-cdk/aws-ecr-assets', + '@aws-cdk/aws-cloudformation', + '@aws-cdk/aws-ec2']); + await ensureBootstrapped(fixture); + await block(fixture); + } + catch (e) { + success = false; + throw e; + } + finally { + await fixture.dispose(success); + } + }; +} +exports.withCdkApp = withCdkApp; +/** + * Default test fixture for most (all?) integ tests + * + * It's a composition of withAws/withCdkApp, expecting the test block to take a `TestFixture` + * object. + * + * We could have put `withAws(withCdkApp(fixture => { /... actual test here.../ }))` in every + * test declaration but centralizing it is going to make it convenient to modify in the future. + */ +function withDefaultFixture(block) { + return withAws(withCdkApp(block)); + // ^~~~~~ this is disappointing TypeScript! Feels like you should have been able to derive this. +} +exports.withDefaultFixture = withDefaultFixture; +/** + * Prepare a target dir byreplicating a source directory + */ +async function cloneDirectory(source, target, output) { + await shell(['rm', '-rf', target], { output }); + await shell(['mkdir', '-p', target], { output }); + await shell(['cp', '-R', source + '/*', target], { output }); +} +exports.cloneDirectory = cloneDirectory; +class TestFixture { + constructor(integTestDir, stackNamePrefix, output, aws) { + this.integTestDir = integTestDir; + this.stackNamePrefix = stackNamePrefix; + this.output = output; + this.aws = aws; + this.qualifier = randomString().substr(0, 10); + this.bucketsToDelete = new Array(); + } + log(s) { + this.output.write(`${s}\n`); + } + async shell(command, options = {}) { + return shell(command, { + output: this.output, + cwd: this.integTestDir, + ...options, + }); + } + async cdkDeploy(stackNames, options = {}) { + var _a, _b; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + const neverRequireApproval = (_a = options.neverRequireApproval) !== null && _a !== void 0 ? _a : true; + return this.cdk(['deploy', + ...(neverRequireApproval ? ['--require-approval=never'] : []), // Default to no approval in an unattended test + ...((_b = options.options) !== null && _b !== void 0 ? _b : []), ...this.fullStackName(stackNames)], options); + } + async cdkDestroy(stackNames, options = {}) { + var _a; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + return this.cdk(['destroy', + '-f', // We never want a prompt in an unattended test + ...((_a = options.options) !== null && _a !== void 0 ? _a : []), ...this.fullStackName(stackNames)], options); + } + async cdk(args, options = {}) { + var _a; + const verbose = (_a = options.verbose) !== null && _a !== void 0 ? _a : true; + return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], { + ...options, + modEnv: { + AWS_REGION: this.aws.region, + AWS_DEFAULT_REGION: this.aws.region, + STACK_NAME_PREFIX: this.stackNamePrefix, + ...options.modEnv, + }, + }); + } + fullStackName(stackNames) { + if (typeof stackNames === 'string') { + return `${this.stackNamePrefix}-${stackNames}`; + } + else { + return stackNames.map(s => `${this.stackNamePrefix}-${s}`); + } + } + /** + * Append this to the list of buckets to potentially delete + * + * At the end of a test, we clean up buckets that may not have gotten destroyed + * (for whatever reason). + */ + rememberToDeleteBucket(bucketName) { + this.bucketsToDelete.push(bucketName); + } + /** + * Cleanup leftover stacks and buckets + */ + async dispose(success) { + const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix); + // Bootstrap stacks have buckets that need to be cleaned + const bucketNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('BucketName', stack)).filter(defined); + await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b))); + // Bootstrap stacks have ECR repositories with images which should be deleted + const imageRepositoryNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('ImageRepositoryName', stack)).filter(defined); + await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r))); + await this.aws.deleteStacks(...stacksToDelete.map(s => s.StackName)); + // We might have leaked some buckets by upgrading the bootstrap stack. Be + // sure to clean everything. + for (const bucket of this.bucketsToDelete) { + await this.aws.deleteBucket(bucket); + } + // If the tests completed successfully, happily delete the fixture + // (otherwise leave it for humans to inspect) + if (success) { + rimraf(this.integTestDir); + } + } + /** + * Return the stacks starting with our testing prefix that should be deleted + */ + async deleteableStacks(prefix) { + var _a; + const statusFilter = [ + 'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE', + 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE', + 'DELETE_FAILED', + 'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS', + 'UPDATE_ROLLBACK_FAILED', + 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_ROLLBACK_COMPLETE', 'REVIEW_IN_PROGRESS', + 'IMPORT_IN_PROGRESS', 'IMPORT_COMPLETE', + 'IMPORT_ROLLBACK_IN_PROGRESS', 'IMPORT_ROLLBACK_FAILED', + 'IMPORT_ROLLBACK_COMPLETE', + ]; + const response = await this.aws.cloudFormation('describeStacks', {}); + return ((_a = response.Stacks) !== null && _a !== void 0 ? _a : []) + .filter(s => s.StackName.startsWith(prefix)) + .filter(s => statusFilter.includes(s.StackStatus)) + .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process + } +} +exports.TestFixture = TestFixture; +/** + * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * + * If we don't do this, calls are going to fail and they'll be retried and everything will take + * forever before the user notices a simple misconfiguration. + * + * We can't check for the presence of environment variables since credentials could come from + * anywhere, so do simple account retrieval. + * + * Only do it once per process. + */ +async function sanityCheck(aws) { + if (sanityChecked === undefined) { + try { + await aws.account(); + sanityChecked = true; + } + catch (e) { + sanityChecked = false; + throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); + } + } + if (!sanityChecked) { + throw new Error('AWS credentials probably not configured, see previous error'); + } +} +let sanityChecked; +/** + * Make sure that the given environment is bootstrapped + * + * Since we go striping across regions, it's going to suck doing this + * by hand so let's just mass-automate it. + */ +async function ensureBootstrapped(fixture) { + // Old-style bootstrap stack with default name + if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { + await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); + } +} +/** + * A shell command that does what you want + * + * Is platform-aware, handles errors nicely. + */ +async function shell(command, options = {}) { + var _a, _b; + if (options.modEnv && options.env) { + throw new Error('Use either env or modEnv but not both'); + } + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(`💻 ${command.join(' ')}\n`); + const env = (_b = options.env) !== null && _b !== void 0 ? _b : (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); + const child = child_process.spawn(command[0], command.slice(1), { + ...options, + env, + // Need this for Windows where we want .cmd and .bat to be found as well. + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + return new Promise((resolve, reject) => { + const stdout = new Array(); + const stderr = new Array(); + child.stdout.on('data', chunk => { + var _a; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + stdout.push(chunk); + }); + child.stderr.on('data', chunk => { + var _a, _b; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + if ((_b = options.captureStderr) !== null && _b !== void 0 ? _b : true) { + stderr.push(chunk); + } + }); + child.once('error', reject); + child.once('close', code => { + if (code === 0 || options.allowErrExit) { + resolve((Buffer.concat(stdout).toString('utf-8') + Buffer.concat(stderr).toString('utf-8')).trim()); + } + else { + reject(new Error(`'${command.join(' ')}' exited with error code ${code}`)); + } + }); + }); +} +exports.shell = shell; +function defined(x) { + return x !== undefined; +} +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +function rimraf(fsPath) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } + else { + fs.unlinkSync(fsPath); + } + } + catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { + throw e; + } + } +} +exports.rimraf = rimraf; +function randomString() { + // Crazy + return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} +exports.randomString = randomString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsK0NBQStDO0FBQy9DLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtDQUE0RDtBQUM1RCxtREFBK0M7QUFHL0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXO0lBQ3JDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0lBQ3BDLENBQUMsQ0FBQyxhQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxtQ0FBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixtQ0FBSSxXQUFXLENBQUMsQ0FBQztBQUU5RSxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsT0FBTyxJQUFJLENBQUMsQ0FBQztBQUVwRCxNQUFNLFdBQVcsR0FBRyxJQUFJLDRCQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7QUFLOUM7Ozs7R0FJRztBQUNILFNBQWdCLE9BQU8sQ0FBd0IsS0FBaUQ7SUFDOUYsT0FBTyxDQUFDLE9BQVUsRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDeEQsTUFBTSxHQUFHLEdBQUcsTUFBTSx3QkFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQy9ELE1BQU0sV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXZCLE9BQU8sS0FBSyxDQUFDLEVBQUUsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFQRCwwQkFPQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxTQUFnQixVQUFVLENBQXFDLEtBQThD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LEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7U0FDdEI7YUFBTTtZQUNMLEVBQUUsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7U0FDdkI7S0FDRjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YseUJBQXlCO1FBQ3pCLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUU7WUFBRSxNQUFNLENBQUMsQ0FBQztTQUFFO0tBQ3RDO0FBQ0gsQ0FBQztBQWhCRCx3QkFnQkM7QUFFRCxTQUFnQixZQUFZO0lBQzFCLFFBQVE7SUFDUixPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUMvRCxDQUFDO0FBSEQsb0NBR0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjaGlsZF9wcm9jZXNzIGZyb20gJ2NoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMnO1xuaW1wb3J0ICogYXMgb3MgZnJvbSAnb3MnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IG91dHB1dEZyb21TdGFjaywgQXdzQ2xpZW50cyB9IGZyb20gJy4vYXdzLWhlbHBlcnMnO1xuaW1wb3J0IHsgUmVzb3VyY2VQb29sIH0gZnJvbSAnLi9yZXNvdXJjZS1wb29sJztcbmltcG9ydCB7IFRlc3RDb250ZXh0IH0gZnJvbSAnLi90ZXN0LWhlbHBlcnMnO1xuXG5jb25zdCBSRUdJT05TID0gcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTlNcbiAgPyBwcm9jZXNzLmVudi5BV1NfUkVHSU9OUy5zcGxpdCgnLCcpXG4gIDogW3Byb2Nlc3MuZW52LkFXU19SRUdJT04gPz8gcHJvY2Vzcy5lbnYuQVdTX0RFRkFVTFRfUkVHSU9OID8/ICd1cy1lYXN0LTEnXTtcblxucHJvY2Vzcy5zdGRvdXQud3JpdGUoYFVzaW5nIHJlZ2lvbnM6ICR7UkVHSU9OU31cXG5gKTtcblxuY29uc3QgUkVHSU9OX1BPT0wgPSBuZXcgUmVzb3VyY2VQb29sKFJFR0lPTlMpO1xuXG5cbmV4cG9ydCB0eXBlIEF3c0NvbnRleHQgPSB7IHJlYWRvbmx5IGF3czogQXdzQ2xpZW50cyB9O1xuXG4vKipcbiAqIEhpZ2hlciBvcmRlciBmdW5jdGlvbiB0byBleGVjdXRlIGEgYmxvY2sgd2l0aCBhbiBBV1MgY2xpZW50IHNldHVwXG4gKlxuICogQWxsb2NhdGUgdGhlIG5leHQgcmVnaW9uIGZyb20gdGhlIFJFR0lPTiBwb29sIGFuZCBkaXNwb3NlIGl0IGFmdGVyd2FyZHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3aXRoQXdzPEEgZXh0ZW5kcyBUZXN0Q29udGV4dD4oYmxvY2s6IChjb250ZXh0OiBBICYgQXdzQ29udGV4dCkgPT4gUHJvbWlzZTx2b2lkPikge1xuICByZXR1cm4gKGNvbnRleHQ6IEEpID0+IFJFR0lPTl9QT09MLnVzaW5nKGFzeW5jIChyZWdpb24pID0+IHtcbiAgICBjb25zdCBhd3MgPSBhd2FpdCBBd3NDbGllbnRzLmZvclJlZ2lvbihyZWdpb24sIGNvbnRleHQub3V0cHV0KTtcbiAgICBhd2FpdCBzYW5pdHlDaGVjayhhd3MpO1xuXG4gICAgcmV0dXJuIGJsb2NrKHsgLi4uY29udGV4dCwgYXdzIH0pO1xuICB9KTtcbn1cblxuLyoqXG4gKiBIaWdoZXIgb3JkZXIgZnVuY3Rpb24gdG8gZXhlY3V0ZSBhIGJsb2NrIHdpdGggYSBDREsgYXBwIGZpeHR1cmVcbiAqXG4gKiBSZXF1aXJlcyBhbiBBV1MgY2xpZW50IHRvIGJlIHBhc3NlZCBpbi5cbiAqXG4gKiBGb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHkgd2l0aCBleGlzdGluZyB0ZXN0cyAoc28gd2UgZG9uJ3QgaGF2ZSB0byBjaGFuZ2VcbiAqIHRvbyBtdWNoKSB0aGUgaW5uZXIgYmxvY2sgaXMgZXhwZWN0ZWQgdG8gdGFrZSBhIGBUZXN0Rml4dHVyZWAgb2JqZWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aENka0FwcDxBIGV4dGVuZHMgVGVzdENvbnRleHQgJiBBd3NDb250ZXh0PihibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiBhc3luYyAoY29udGV4dDogQSkgPT4ge1xuICAgIGNvbnN0IHJhbmR5ID0gcmFuZG9tU3RyaW5nKCk7XG4gICAgY29uc3Qgc3RhY2tOYW1lUHJlZml4ID0gYGNka3Rlc3QtJHtyYW5keX1gO1xuICAgIGNvbnN0IGludGVnVGVzdERpciA9IHBhdGguam9pbihvcy50bXBkaXIoKSwgYGNkay1pbnRlZy0ke3JhbmR5fWApO1xuXG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBTdGFjayBwcmVmaXg6ICAgJHtzdGFja05hbWVQcmVmaXh9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBUZXN0IGRpcmVjdG9yeTogJHtpbnRlZ1Rlc3REaXJ9XFxuYCk7XG4gICAgY29udGV4dC5vdXRwdXQud3JpdGUoYCBSZWdpb246ICAgICAgICAgJHtjb250ZXh0LmF3cy5yZWdpb259XFxuYCk7XG5cbiAgICBhd2FpdCBjbG9uZURpcmVjdG9yeShwYXRoLmpvaW4oX19kaXJuYW1lLCAnYXBwJyksIGludGVnVGVzdERpciwgY29udGV4dC5vdXRwdXQpO1xuICAgIGNvbnN0IGZpeHR1cmUgPSBuZXcgVGVzdEZpeHR1cmUoXG4gICAgICBpbnRlZ1Rlc3REaXIsXG4gICAgICBzdGFja05hbWVQcmVmaXgsXG4gICAgICBjb250ZXh0Lm91dHB1dCxcbiAgICAgIGNvbnRleHQuYXdzKTtcblxuICAgIGxldCBzdWNjZXNzID0gdHJ1ZTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ25wbScsICdpbnN0YWxsJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2NvcmUnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNucycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtaWFtJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNzbScsXG4gICAgICAgICdAYXdzLWNkay9hd3MtZWNyLWFzc2V0cycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtY2xvdWRmb3JtYXRpb24nLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWVjMiddKTtcblxuICAgICAgYXdhaXQgZW5zdXJlQm9vdHN0cmFwcGVkKGZpeHR1cmUpO1xuXG4gICAgICBhd2FpdCBibG9jayhmaXh0dXJlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBzdWNjZXNzID0gZmFsc2U7XG4gICAgICB0aHJvdyBlO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBhd2FpdCBmaXh0dXJlLmRpc3Bvc2Uoc3VjY2Vzcyk7XG4gICAgfVxuICB9O1xufVxuXG4vKipcbiAqIERlZmF1bHQgdGVzdCBmaXh0dXJlIGZvciBtb3N0IChhbGw/KSBpbnRlZyB0ZXN0c1xuICpcbiAqIEl0J3MgYSBjb21wb3NpdGlvbiBvZiB3aXRoQXdzL3dpdGhDZGtBcHAsIGV4cGVjdGluZyB0aGUgdGVzdCBibG9jayB0byB0YWtlIGEgYFRlc3RGaXh0dXJlYFxuICogb2JqZWN0LlxuICpcbiAqIFdlIGNvdWxkIGhhdmUgcHV0IGB3aXRoQXdzKHdpdGhDZGtBcHAoZml4dHVyZSA9PiB7IC8uLi4gYWN0dWFsIHRlc3QgaGVyZS4uLi8gfSkpYCBpbiBldmVyeVxuICogdGVzdCBkZWNsYXJhdGlvbiBidXQgY2VudHJhbGl6aW5nIGl0IGlzIGdvaW5nIHRvIG1ha2UgaXQgY29udmVuaWVudCB0byBtb2RpZnkgaW4gdGhlIGZ1dHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdpdGhEZWZhdWx0Rml4dHVyZShibG9jazogKGNvbnRleHQ6IFRlc3RGaXh0dXJlKSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiB3aXRoQXdzPFRlc3RDb250ZXh0Pih3aXRoQ2RrQXBwKGJsb2NrKSk7XG4gIC8vICAgICAgICAgICAgICBefn5+fn4gdGhpcyBpcyBkaXNhcHBvaW50aW5nIFR5cGVTY3JpcHQhIEZlZWxzIGxpa2UgeW91IHNob3VsZCBoYXZlIGJlZW4gYWJsZSB0byBkZXJpdmUgdGhpcy5cbn1cblxuZXhwb3J0IGludGVyZmFjZSBTaGVsbE9wdGlvbnMgZXh0ZW5kcyBjaGlsZF9wcm9jZXNzLlNwYXduT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBQcm9wZXJ0aWVzIHRvIGFkZCB0byAnZW52J1xuICAgKi9cbiAgbW9kRW52PzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcblxuICAvKipcbiAgICogRG9uJ3QgZmFpbCB3aGVuIGV4aXRpbmcgd2l0aCBhbiBlcnJvclxuICAgKlxuICAgKiBAZGVmYXVsdCBmYWxzZVxuICAgKi9cbiAgYWxsb3dFcnJFeGl0PzogYm9vbGVhbjtcblxuICAvKipcbiAgICogV2hldGhlciB0byBjYXB0dXJlIHN0ZGVyclxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICBjYXB0dXJlU3RkZXJyPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogUGFzcyBvdXRwdXQgaGVyZVxuICAgKi9cbiAgb3V0cHV0PzogTm9kZUpTLldyaXRhYmxlU3RyZWFtO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENka0NsaU9wdGlvbnMgZXh0ZW5kcyBTaGVsbE9wdGlvbnMge1xuICBvcHRpb25zPzogc3RyaW5nW107XG4gIG5ldmVyUmVxdWlyZUFwcHJvdmFsPzogYm9vbGVhbjtcbiAgdmVyYm9zZT86IGJvb2xlYW47XG59XG5cbi8qKlxuICogUHJlcGFyZSBhIHRhcmdldCBkaXIgYnlyZXBsaWNhdGluZyBhIHNvdXJjZSBkaXJlY3RvcnlcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsb25lRGlyZWN0b3J5KHNvdXJjZTogc3RyaW5nLCB0YXJnZXQ6IHN0cmluZywgb3V0cHV0PzogTm9kZUpTLldyaXRhYmxlU3RyZWFtKSB7XG4gIGF3YWl0IHNoZWxsKFsncm0nLCAnLXJmJywgdGFyZ2V0XSwgeyBvdXRwdXQgfSk7XG4gIGF3YWl0IHNoZWxsKFsnbWtkaXInLCAnLXAnLCB0YXJnZXRdLCB7IG91dHB1dCB9KTtcbiAgYXdhaXQgc2hlbGwoWydjcCcsICctUicsIHNvdXJjZSArICcvKicsIHRhcmdldF0sIHsgb3V0cHV0IH0pO1xufVxuXG5leHBvcnQgY2xhc3MgVGVzdEZpeHR1cmUge1xuICBwdWJsaWMgcmVhZG9ubHkgcXVhbGlmaWVyID0gcmFuZG9tU3RyaW5nKCkuc3Vic3RyKDAsIDEwKTtcbiAgcHJpdmF0ZSByZWFkb25seSBidWNrZXRzVG9EZWxldGUgPSBuZXcgQXJyYXk8c3RyaW5nPigpO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHB1YmxpYyByZWFkb25seSBpbnRlZ1Rlc3REaXI6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgc3RhY2tOYW1lUHJlZml4OiBzdHJpbmcsXG4gICAgcHVibGljIHJlYWRvbmx5IG91dHB1dDogTm9kZUpTLldyaXRhYmxlU3RyZWFtLFxuICAgIHB1YmxpYyByZWFkb25seSBhd3M6IEF3c0NsaWVudHMpIHtcbiAgfVxuXG4gIHB1YmxpYyBsb2coczogc3RyaW5nKSB7XG4gICAgdGhpcy5vdXRwdXQud3JpdGUoYCR7c31cXG5gKTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBzaGVsbChjb21tYW5kOiBzdHJpbmdbXSwgb3B0aW9uczogT21pdDxTaGVsbE9wdGlvbnMsICdjd2QnfCdvdXRwdXQnPiA9IHt9KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICByZXR1cm4gc2hlbGwoY29tbWFuZCwge1xuICAgICAgb3V0cHV0OiB0aGlzLm91dHB1dCxcbiAgICAgIGN3ZDogdGhpcy5pbnRlZ1Rlc3REaXIsXG4gICAgICAuLi5vcHRpb25zLFxuICAgIH0pO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIGNka0RlcGxveShzdGFja05hbWVzOiBzdHJpbmcgfCBzdHJpbmdbXSwgb3B0aW9uczogQ2RrQ2xpT3B0aW9ucyA9IHt9KSB7XG4gICAgc3RhY2tOYW1lcyA9IHR5cGVvZiBzdGFja05hbWVzID09PSAnc3RyaW5nJyA/IFtzdGFja05hbWVzXSA6IHN0YWNrTmFtZXM7XG5cbiAgICBjb25zdCBuZXZlclJlcXVpcmVBcHByb3ZhbCA9IG9wdGlvbnMubmV2ZXJSZXF1aXJlQXBwcm92YWwgPz8gdHJ1ZTtcblxuICAgIHJldHVybiB0aGlzLmNkayhbJ2RlcGxveScsXG4gICAgICAuLi4obmV2ZXJSZXF1aXJlQXBwcm92YWwgPyBbJy0tcmVxdWlyZS1hcHByb3ZhbD1uZXZlciddIDogW10pLCAvLyBEZWZhdWx0IHRvIG5vIGFwcHJvdmFsIGluIGFuIHVuYXR0ZW5kZWQgdGVzdFxuICAgICAgLi4uKG9wdGlvbnMub3B0aW9ucyA/PyBbXSksXG4gICAgICAuLi50aGlzLmZ1bGxTdGFja05hbWUoc3RhY2tOYW1lcyldLCBvcHRpb25zKTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBjZGtEZXN0cm95KHN0YWNrTmFtZXM6IHN0cmluZyB8IHN0cmluZ1tdLCBvcHRpb25zOiBDZGtDbGlPcHRpb25zID0ge30pIHtcbiAgICBzdGFja05hbWVzID0gdHlwZW9mIHN0YWNrTmFtZXMgPT09ICdzdHJpbmcnID8gW3N0YWNrTmFtZXNdIDogc3RhY2tOYW1lcztcblxuICAgIHJldHVybiB0aGlzLmNkayhbJ2Rlc3Ryb3knLFxuICAgICAgJy1mJywgLy8gV2UgbmV2ZXIgd2FudCBhIHByb21wdCBpbiBhbiB1bmF0dGVuZGVkIHRlc3RcbiAgICAgIC4uLihvcHRpb25zLm9wdGlvbnMgPz8gW10pLFxuICAgICAgLi4udGhpcy5mdWxsU3RhY2tOYW1lKHN0YWNrTmFtZXMpXSwgb3B0aW9ucyk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgY2RrKGFyZ3M6IHN0cmluZ1tdLCBvcHRpb25zOiBDZGtDbGlPcHRpb25zID0ge30pIHtcbiAgICBjb25zdCB2ZXJib3NlID0gb3B0aW9ucy52ZXJib3NlID8/IHRydWU7XG5cbiAgICByZXR1cm4gdGhpcy5zaGVsbChbJ2NkaycsIC4uLih2ZXJib3NlID8gWyctdiddIDogW10pLCAuLi5hcmdzXSwge1xuICAgICAgLi4ub3B0aW9ucyxcbiAgICAgIG1vZEVudjoge1xuICAgICAgICBBV1NfUkVHSU9OOiB0aGlzLmF3cy5yZWdpb24sXG4gICAgICAgIEFXU19ERUZBVUxUX1JFR0lPTjogdGhpcy5hd3MucmVnaW9uLFxuICAgICAgICBTVEFDS19OQU1FX1BSRUZJWDogdGhpcy5zdGFja05hbWVQcmVmaXgsXG4gICAgICAgIC4uLm9wdGlvbnMubW9kRW52LFxuICAgICAgfSxcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBmdWxsU3RhY2tOYW1lKHN0YWNrTmFtZTogc3RyaW5nKTogc3RyaW5nO1xuICBwdWJsaWMgZnVsbFN0YWNrTmFtZShzdGFja05hbWVzOiBzdHJpbmdbXSk6IHN0cmluZ1tdO1xuICBwdWJsaWMgZnVsbFN0YWNrTmFtZShzdGFja05hbWVzOiBzdHJpbmcgfCBzdHJpbmdbXSk6IHN0cmluZyB8IHN0cmluZ1tdIHtcbiAgICBpZiAodHlwZW9mIHN0YWNrTmFtZXMgPT09ICdzdHJpbmcnKSB7XG4gICAgICByZXR1cm4gYCR7dGhpcy5zdGFja05hbWVQcmVmaXh9LSR7c3RhY2tOYW1lc31gO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gc3RhY2tOYW1lcy5tYXAocyA9PiBgJHt0aGlzLnN0YWNrTmFtZVByZWZpeH0tJHtzfWApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBBcHBlbmQgdGhpcyB0byB0aGUgbGlzdCBvZiBidWNrZXRzIHRvIHBvdGVudGlhbGx5IGRlbGV0ZVxuICAgKlxuICAgKiBBdCB0aGUgZW5kIG9mIGEgdGVzdCwgd2UgY2xlYW4gdXAgYnVja2V0cyB0aGF0IG1heSBub3QgaGF2ZSBnb3R0ZW4gZGVzdHJveWVkXG4gICAqIChmb3Igd2hhdGV2ZXIgcmVhc29uKS5cbiAgICovXG4gIHB1YmxpYyByZW1lbWJlclRvRGVsZXRlQnVja2V0KGJ1Y2tldE5hbWU6IHN0cmluZykge1xuICAgIHRoaXMuYnVja2V0c1RvRGVsZXRlLnB1c2goYnVja2V0TmFtZSk7XG4gIH1cblxuICAvKipcbiAgICogQ2xlYW51cCBsZWZ0b3ZlciBzdGFja3MgYW5kIGJ1Y2tldHNcbiAgICovXG4gIHB1YmxpYyBhc3luYyBkaXNwb3NlKHN1Y2Nlc3M6IGJvb2xlYW4pIHtcbiAgICBjb25zdCBzdGFja3NUb0RlbGV0ZSA9IGF3YWl0IHRoaXMuZGVsZXRlYWJsZVN0YWNrcyh0aGlzLnN0YWNrTmFtZVByZWZpeCk7XG5cbiAgICAvLyBCb290c3RyYXAgc3RhY2tzIGhhdmUgYnVja2V0cyB0aGF0IG5lZWQgdG8gYmUgY2xlYW5lZFxuICAgIGNvbnN0IGJ1Y2tldE5hbWVzID0gc3RhY2tzVG9EZWxldGUubWFwKHN0YWNrID0+IG91dHB1dEZyb21TdGFjaygnQnVja2V0TmFtZScsIHN0YWNrKSkuZmlsdGVyKGRlZmluZWQpO1xuICAgIGF3YWl0IFByb21pc2UuYWxsKGJ1Y2tldE5hbWVzLm1hcChiID0+IHRoaXMuYXdzLmVtcHR5QnVja2V0KGIpKSk7XG5cbiAgICAvLyBCb290c3RyYXAgc3RhY2tzIGhhdmUgRUNSIHJlcG9zaXRvcmllcyB3aXRoIGltYWdlcyB3aGljaCBzaG91bGQgYmUgZGVsZXRlZFxuICAgIGNvbnN0IGltYWdlUmVwb3NpdG9yeU5hbWVzID0gc3RhY2tzVG9EZWxldGUubWFwKHN0YWNrID0+IG91dHB1dEZyb21TdGFjaygnSW1hZ2VSZXBvc2l0b3J5TmFtZScsIHN0YWNrKSkuZmlsdGVyKGRlZmluZWQpO1xuICAgIGF3YWl0IFByb21pc2UuYWxsKGltYWdlUmVwb3NpdG9yeU5hbWVzLm1hcChyID0+IHRoaXMuYXdzLmRlbGV0ZUltYWdlUmVwb3NpdG9yeShyKSkpO1xuXG4gICAgYXdhaXQgdGhpcy5hd3MuZGVsZXRlU3RhY2tzKC4uLnN0YWNrc1RvRGVsZXRlLm1hcChzID0+IHMuU3RhY2tOYW1lKSk7XG5cbiAgICAvLyBXZSBtaWdodCBoYXZlIGxlYWtlZCBzb21lIGJ1Y2tldHMgYnkgdXBncmFkaW5nIHRoZSBib290c3RyYXAgc3RhY2suIEJlXG4gICAgLy8gc3VyZSB0byBjbGVhbiBldmVyeXRoaW5nLlxuICAgIGZvciAoY29uc3QgYnVja2V0IG9mIHRoaXMuYnVja2V0c1RvRGVsZXRlKSB7XG4gICAgICBhd2FpdCB0aGlzLmF3cy5kZWxldGVCdWNrZXQoYnVja2V0KTtcbiAgICB9XG5cbiAgICAvLyBJZiB0aGUgdGVzdHMgY29tcGxldGVkIHN1Y2Nlc3NmdWxseSwgaGFwcGlseSBkZWxldGUgdGhlIGZpeHR1cmVcbiAgICAvLyAob3RoZXJ3aXNlIGxlYXZlIGl0IGZvciBodW1hbnMgdG8gaW5zcGVjdClcbiAgICBpZiAoc3VjY2Vzcykge1xuICAgICAgcmltcmFmKHRoaXMuaW50ZWdUZXN0RGlyKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJuIHRoZSBzdGFja3Mgc3RhcnRpbmcgd2l0aCBvdXIgdGVzdGluZyBwcmVmaXggdGhhdCBzaG91bGQgYmUgZGVsZXRlZFxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBkZWxldGVhYmxlU3RhY2tzKHByZWZpeDogc3RyaW5nKTogUHJvbWlzZTxBV1MuQ2xvdWRGb3JtYXRpb24uU3RhY2tbXT4ge1xuICAgIGNvbnN0IHN0YXR1c0ZpbHRlciA9IFtcbiAgICAgICdDUkVBVEVfSU5fUFJPR1JFU1MnLCAnQ1JFQVRFX0ZBSUxFRCcsICdDUkVBVEVfQ09NUExFVEUnLFxuICAgICAgJ1JPTExCQUNLX0lOX1BST0dSRVNTJywgJ1JPTExCQUNLX0ZBSUxFRCcsICdST0xMQkFDS19DT01QTEVURScsXG4gICAgICAnREVMRVRFX0ZBSUxFRCcsXG4gICAgICAnVVBEQVRFX0lOX1BST0dSRVNTJywgJ1VQREFURV9DT01QTEVURV9DTEVBTlVQX0lOX1BST0dSRVNTJyxcbiAgICAgICdVUERBVEVfQ09NUExFVEUnLCAnVVBEQVRFX1JPTExCQUNLX0lOX1BST0dSRVNTJyxcbiAgICAgICdVUERBVEVfUk9MTEJBQ0tfRkFJTEVEJyxcbiAgICAgICdVUERBVEVfUk9MTEJBQ0tfQ09NUExFVEVfQ0xFQU5VUF9JTl9QUk9HUkVTUycsXG4gICAgICAnVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFJywgJ1JFVklFV19JTl9QUk9HUkVTUycsXG4gICAgICAnSU1QT1JUX0lOX1BST0dSRVNTJywgJ0lNUE9SVF9DT01QTEVURScsXG4gICAgICAnSU1QT1JUX1JPTExCQUNLX0lOX1BST0dSRVNTJywgJ0lNUE9SVF9ST0xMQkFDS19GQUlMRUQnLFxuICAgICAgJ0lNUE9SVF9ST0xMQkFDS19DT01QTEVURScsXG4gICAgXTtcblxuICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge30pO1xuXG4gICAgcmV0dXJuIChyZXNwb25zZS5TdGFja3MgPz8gW10pXG4gICAgICAuZmlsdGVyKHMgPT4gcy5TdGFja05hbWUuc3RhcnRzV2l0aChwcmVmaXgpKVxuICAgICAgLmZpbHRlcihzID0+IHN0YXR1c0ZpbHRlci5pbmNsdWRlcyhzLlN0YWNrU3RhdHVzKSlcbiAgICAgIC5maWx0ZXIocyA9PiBzLlJvb3RJZCA9PT0gdW5kZWZpbmVkKTsgLy8gT25seSBkZWxldGUgcGFyZW50IHN0YWNrcy4gTmVzdGVkIHN0YWNrcyBhcmUgZGVsZXRlZCBpbiB0aGUgcHJvY2Vzc1xuICB9XG59XG5cbi8qKlxuICogUGVyZm9ybSBhIG9uZS10aW1lIHF1aWNrIHNhbml0eSBjaGVjayB0aGF0IHRoZSBBV1MgY2xpZW50cyBoYXMgcHJvcGVybHkgY29uZmlndXJlZCBjcmVkZW50aWFsc1xuICpcbiAqIElmIHdlIGRvbid0IGRvIHRoaXMsIGNhbGxzIGFyZSBnb2luZyB0byBmYWlsIGFuZCB0aGV5J2xsIGJlIHJldHJpZWQgYW5kIGV2ZXJ5dGhpbmcgd2lsbCB0YWtlXG4gKiBmb3JldmVyIGJlZm9yZSB0aGUgdXNlciBub3RpY2VzIGEgc2ltcGxlIG1pc2NvbmZpZ3VyYXRpb24uXG4gKlxuICogV2UgY2FuJ3QgY2hlY2sgZm9yIHRoZSBwcmVzZW5jZSBvZiBlbnZpcm9ubWVudCB2YXJpYWJsZXMgc2luY2UgY3JlZGVudGlhbHMgY291bGQgY29tZSBmcm9tXG4gKiBhbnl3aGVyZSwgc28gZG8gc2ltcGxlIGFjY291bnQgcmV0cmlldmFsLlxuICpcbiAqIE9ubHkgZG8gaXQgb25jZSBwZXIgcHJvY2Vzcy5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gc2FuaXR5Q2hlY2soYXdzOiBBd3NDbGllbnRzKSB7XG4gIGlmIChzYW5pdHlDaGVja2VkID09PSB1bmRlZmluZWQpIHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgYXdzLmFjY291bnQoKTtcbiAgICAgIHNhbml0eUNoZWNrZWQgPSB0cnVlO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIHNhbml0eUNoZWNrZWQgPSBmYWxzZTtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQVdTIGNyZWRlbnRpYWxzIHByb2JhYmx5IG5vdCBjb25maWd1cmVkLCBnb3QgZXJyb3I6ICR7ZS5tZXNzYWdlfWApO1xuICAgIH1cbiAgfVxuICBpZiAoIXNhbml0eUNoZWNrZWQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0FXUyBjcmVkZW50aWFscyBwcm9iYWJseSBub3QgY29uZmlndXJlZCwgc2VlIHByZXZpb3VzIGVycm9yJyk7XG4gIH1cbn1cbmxldCBzYW5pdHlDaGVja2VkOiBib29sZWFuIHwgdW5kZWZpbmVkO1xuXG4vKipcbiAqIE1ha2Ugc3VyZSB0aGF0IHRoZSBnaXZlbiBlbnZpcm9ubWVudCBpcyBib290c3RyYXBwZWRcbiAqXG4gKiBTaW5jZSB3ZSBnbyBzdHJpcGluZyBhY3Jvc3MgcmVnaW9ucywgaXQncyBnb2luZyB0byBzdWNrIGRvaW5nIHRoaXNcbiAqIGJ5IGhhbmQgc28gbGV0J3MganVzdCBtYXNzLWF1dG9tYXRlIGl0LlxuICovXG5hc3luYyBmdW5jdGlvbiBlbnN1cmVCb290c3RyYXBwZWQoZml4dHVyZTogVGVzdEZpeHR1cmUpIHtcbiAgLy8gT2xkLXN0eWxlIGJvb3RzdHJhcCBzdGFjayB3aXRoIGRlZmF1bHQgbmFtZVxuICBpZiAoYXdhaXQgZml4dHVyZS5hd3Muc3RhY2tTdGF0dXMoJ0NES1Rvb2xraXQnKSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgYXdhaXQgZml4dHVyZS5jZGsoWydib290c3RyYXAnLCBgYXdzOi8vJHthd2FpdCBmaXh0dXJlLmF3cy5hY2NvdW50KCl9LyR7Zml4dHVyZS5hd3MucmVnaW9ufWBdKTtcbiAgfVxufVxuXG4vKipcbiAqIEEgc2hlbGwgY29tbWFuZCB0aGF0IGRvZXMgd2hhdCB5b3Ugd2FudFxuICpcbiAqIElzIHBsYXRmb3JtLWF3YXJlLCBoYW5kbGVzIGVycm9ycyBuaWNlbHkuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzaGVsbChjb21tYW5kOiBzdHJpbmdbXSwgb3B0aW9uczogU2hlbGxPcHRpb25zID0ge30pOiBQcm9taXNlPHN0cmluZz4ge1xuICBpZiAob3B0aW9ucy5tb2RFbnYgJiYgb3B0aW9ucy5lbnYpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1VzZSBlaXRoZXIgZW52IG9yIG1vZEVudiBidXQgbm90IGJvdGgnKTtcbiAgfVxuXG4gIG9wdGlvbnMub3V0cHV0Py53cml0ZShg8J+SuyAke2NvbW1hbmQuam9pbignICcpfVxcbmApO1xuXG4gIGNvbnN0IGVudiA9IG9wdGlvbnMuZW52ID8/IChvcHRpb25zLm1vZEVudiA/IHsgLi4ucHJvY2Vzcy5lbnYsIC4uLm9wdGlvbnMubW9kRW52IH0gOiB1bmRlZmluZWQpO1xuXG4gIGNvbnN0IGNoaWxkID0gY2hpbGRfcHJvY2Vzcy5zcGF3bihjb21tYW5kWzBdLCBjb21tYW5kLnNsaWNlKDEpLCB7XG4gICAgLi4ub3B0aW9ucyxcbiAgICBlbnYsXG4gICAgLy8gTmVlZCB0aGlzIGZvciBXaW5kb3dzIHdoZXJlIHdlIHdhbnQgLmNtZCBhbmQgLmJhdCB0byBiZSBmb3VuZCBhcyB3ZWxsLlxuICAgIHNoZWxsOiB0cnVlLFxuICAgIHN0ZGlvOiBbJ2lnbm9yZScsICdwaXBlJywgJ3BpcGUnXSxcbiAgfSk7XG5cbiAgcmV0dXJuIG5ldyBQcm9taXNlPHN0cmluZz4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgIGNvbnN0IHN0ZG91dCA9IG5ldyBBcnJheTxCdWZmZXI+KCk7XG4gICAgY29uc3Qgc3RkZXJyID0gbmV3IEFycmF5PEJ1ZmZlcj4oKTtcblxuICAgIGNoaWxkLnN0ZG91dCEub24oJ2RhdGEnLCBjaHVuayA9PiB7XG4gICAgICBvcHRpb25zLm91dHB1dD8ud3JpdGUoY2h1bmspO1xuICAgICAgc3Rkb3V0LnB1c2goY2h1bmspO1xuICAgIH0pO1xuXG4gICAgY2hpbGQuc3RkZXJyIS5vbignZGF0YScsIGNodW5rID0+IHtcbiAgICAgIG9wdGlvbnMub3V0cHV0Py53cml0ZShjaHVuayk7XG4gICAgICBpZiAob3B0aW9ucy5jYXB0dXJlU3RkZXJyID8/IHRydWUpIHtcbiAgICAgICAgc3RkZXJyLnB1c2goY2h1bmspO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgY2hpbGQub25jZSgnZXJyb3InLCByZWplY3QpO1xuXG4gICAgY2hpbGQub25jZSgnY2xvc2UnLCBjb2RlID0+IHtcbiAgICAgIGlmIChjb2RlID09PSAwIHx8IG9wdGlvbnMuYWxsb3dFcnJFeGl0KSB7XG4gICAgICAgIHJlc29sdmUoKEJ1ZmZlci5jb25jYXQoc3Rkb3V0KS50b1N0cmluZygndXRmLTgnKSArIEJ1ZmZlci5jb25jYXQoc3RkZXJyKS50b1N0cmluZygndXRmLTgnKSkudHJpbSgpKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoYCcke2NvbW1hbmQuam9pbignICcpfScgZXhpdGVkIHdpdGggZXJyb3IgY29kZSAke2NvZGV9YCkpO1xuICAgICAgfVxuICAgIH0pO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmaW5lZDxBPih4OiBBKTogeCBpcyBOb25OdWxsYWJsZTxBPiB7XG4gIHJldHVybiB4ICE9PSB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogcm0gLXJmIHJlaW1wbGVtZW50YXRpb24sIGRvbid0IHdhbnQgdG8gZGVwZW5kIG9uIGFuIE5QTSBwYWNrYWdlIGZvciB0aGlzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByaW1yYWYoZnNQYXRoOiBzdHJpbmcpIHtcbiAgdHJ5IHtcbiAgICBjb25zdCBpc0RpciA9IGZzLmxzdGF0U3luYyhmc1BhdGgpLmlzRGlyZWN0b3J5KCk7XG5cbiAgICBpZiAoaXNEaXIpIHtcbiAgICAgIGZvciAoY29uc3QgZmlsZSBvZiBmcy5yZWFkZGlyU3luYyhmc1BhdGgpKSB7XG4gICAgICAgIHJpbXJhZihwYXRoLmpvaW4oZnNQYXRoLCBmaWxlKSk7XG4gICAgICB9XG4gICAgICBmcy5ybWRpclN5bmMoZnNQYXRoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZnMudW5saW5rU3luYyhmc1BhdGgpO1xuICAgIH1cbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIFdlIHdpbGwgc3Vydml2ZSBFTk9FTlRcbiAgICBpZiAoZS5jb2RlICE9PSAnRU5PRU5UJykgeyB0aHJvdyBlOyB9XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJhbmRvbVN0cmluZygpIHtcbiAgLy8gQ3JhenlcbiAgcmV0dXJuIE1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnJlcGxhY2UoL1teYS16MC05XSsvZywgJycpO1xufSJdfQ== \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js new file mode 100644 index 0000000000000..a63578ecfaee1 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.64.1/cli.integtest.js @@ -0,0 +1,599 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs_1 = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const cdk_helpers_1 = require("./cdk-helpers"); +const test_helpers_1 = require("./test-helpers"); +jest.setTimeout(600 * 1000); +test_helpers_1.integTest('VPC Lookup', cdk_helpers_1.withDefaultFixture(async (fixture) => { + fixture.log('Making sure we are clean before starting.'); + await fixture.cdkDestroy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); + fixture.log('Setting up: creating a VPC with known tags'); + await fixture.cdkDeploy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); + fixture.log('Setup complete!'); + fixture.log('Verifying we can now import that VPC'); + await fixture.cdkDeploy('import-vpc', { modEnv: { ENABLE_VPC_TESTING: 'IMPORT' } }); +})); +test_helpers_1.integTest('Two ways of shoing the version', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const version1 = await fixture.cdk(['version'], { verbose: false }); + const version2 = await fixture.cdk(['--version'], { verbose: false }); + expect(version1).toEqual(version2); +})); +test_helpers_1.integTest('Termination protection', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const stackName = 'termination-protection'; + await fixture.cdkDeploy(stackName); + // Try a destroy that should fail + await expect(fixture.cdkDestroy(stackName)).rejects.toThrow('exited with error'); + // Can update termination protection even though the change set doesn't contain changes + await fixture.cdkDeploy(stackName, { modEnv: { TERMINATION_PROTECTION: 'FALSE' } }); + await fixture.cdkDestroy(stackName); +})); +test_helpers_1.integTest('cdk synth', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await expect(fixture.cdk(['synth', fixture.fullStackName('test-1')], { verbose: false })).resolves.toEqual(`Resources: + topic69831491: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-1/topic/Resource`); + await expect(fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false })).resolves.toEqual(`Resources: + topic152D84A37: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-2/topic1/Resource + topic2A4FB547F: + Type: AWS::SNS::Topic + Metadata: + aws:cdk:path: ${fixture.stackNamePrefix}-test-2/topic2/Resource`); +})); +test_helpers_1.integTest('ssm parameter provider error', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await expect(fixture.cdk(['synth', + fixture.fullStackName('missing-ssm-parameter'), + '-c', 'test:ssm-parameter-name=/does/not/exist'], { + allowErrExit: true, + })).resolves.toContain('SSM parameter not available in account'); +})); +test_helpers_1.integTest('automatic ordering', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Deploy the consuming stack which will include the producing stack + await fixture.cdkDeploy('order-consuming'); + // Destroy the providing stack which will include the consuming stack + await fixture.cdkDestroy('order-providing'); +})); +test_helpers_1.integTest('context setting', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fs_1.promises.writeFile(path.join(fixture.integTestDir, 'cdk.context.json'), JSON.stringify({ + contextkey: 'this is the context value', + })); + try { + await expect(fixture.cdk(['context'])).resolves.toContain('this is the context value'); + // Test that deleting the contextkey works + await fixture.cdk(['context', '--reset', 'contextkey']); + await expect(fixture.cdk(['context'])).resolves.not.toContain('this is the context value'); + // Test that forced delete of the context key does not throw + await fixture.cdk(['context', '-f', '--reset', 'contextkey']); + } + finally { + await fs_1.promises.unlink(path.join(fixture.integTestDir, 'cdk.context.json')); + } +})); +test_helpers_1.integTest('deploy', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('test-2', { captureStderr: false }); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect((_a = response.StackResources) === null || _a === void 0 ? void 0 : _a.length).toEqual(2); +})); +test_helpers_1.integTest('deploy all', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const arns = await fixture.cdkDeploy('test-*', { captureStderr: false }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(arns.split('\n').length).toEqual(2); +})); +test_helpers_1.integTest('nested stack with parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + // STACK_NAME_PREFIX is used in MyTopicParam to allow multiple instances + // of this test to run in parallel, othewise they will attempt to create the same SNS topic. + const stackArn = await fixture.cdkDeploy('with-nested-stack-using-parameters', { + options: ['--parameters', `MyTopicParam=${fixture.stackNamePrefix}ThereIsNoSpoon`], + captureStderr: false, + }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect((_a = response.StackResources) === null || _a === void 0 ? void 0 : _a.length).toEqual(1); +})); +test_helpers_1.integTest('deploy without execute', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('test-2', { + options: ['--no-execute'], + captureStderr: false, + }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); +})); +test_helpers_1.integTest('security related changes without a CLI are expected to fail', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // redirect /dev/null to stdin, which means there will not be tty attached + // since this stack includes security-related changes, the deployment should + // immediately fail because we can't confirm the changes + const stackName = 'iam-test'; + await expect(fixture.cdkDeploy(stackName, { + options: ['<', '/dev/null'], + neverRequireApproval: false, + })).rejects.toThrow('exited with error'); + // Ensure stack was not deployed + await expect(fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName(stackName), + })).rejects.toThrow('does not exist'); +})); +test_helpers_1.integTest('deploy wildcard with outputs', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs_1.promises.mkdir(path.dirname(outputsFile), { recursive: true }); + await fixture.cdkDeploy(['outputs-test-*'], { + options: ['--outputs-file', outputsFile], + }); + const outputs = JSON.parse((await fs_1.promises.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + expect(outputs).toEqual({ + [`${fixture.stackNamePrefix}-outputs-test-1`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-1MyTopic`, + }, + [`${fixture.stackNamePrefix}-outputs-test-2`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-2MyOtherTopic`, + }, + }); +})); +test_helpers_1.integTest('deploy with parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}bazinga`, + ], + captureStderr: false, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}bazinga`, + }, + ]); +})); +test_helpers_1.integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b, _c, _d; + // GIVEN + await expect(fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error'); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('param-test-1'), + }); + const stackArn = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackId; + expect((_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].StackStatus).toEqual('ROLLBACK_COMPLETE'); + // WHEN + const newStackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, + ], + captureStderr: false, + }); + const newStackResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: newStackArn, + }); + // THEN + expect(stackArn).not.toEqual(newStackArn); // new stack was created + expect((_c = newStackResponse.Stacks) === null || _c === void 0 ? void 0 : _c[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect((_d = newStackResponse.Stacks) === null || _d === void 0 ? void 0 : _d[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}allgood`, + }, + ]); +})); +test_helpers_1.integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b, _c, _d; + // GIVEN + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}nice`, + ], + captureStderr: false, + }); + let response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('CREATE_COMPLETE'); + // bad parameter name with @ will put stack into UPDATE_ROLLBACK_COMPLETE + await expect(fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, + ], + captureStderr: false, + })).rejects.toThrow('exited with error'); + ; + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].StackStatus).toEqual('UPDATE_ROLLBACK_COMPLETE'); + // WHEN + await fixture.cdkDeploy('param-test-1', { + options: [ + '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, + ], + captureStderr: false, + }); + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + // THEN + expect((_c = response.Stacks) === null || _c === void 0 ? void 0 : _c[0].StackStatus).toEqual('UPDATE_COMPLETE'); + expect((_d = response.Stacks) === null || _d === void 0 ? void 0 : _d[0].Parameters).toEqual([ + { + ParameterKey: 'TopicNameParam', + ParameterValue: `${fixture.stackNamePrefix}allgood`, + }, + ]); +})); +test_helpers_1.integTest('deploy with wildcard and parameters', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('param-test-*', { + options: [ + '--parameters', `${fixture.stackNamePrefix}-param-test-1:TopicNameParam=${fixture.stackNamePrefix}bazinga`, + '--parameters', `${fixture.stackNamePrefix}-param-test-2:OtherTopicNameParam=${fixture.stackNamePrefix}ThatsMySpot`, + '--parameters', `${fixture.stackNamePrefix}-param-test-3:DisplayNameParam=${fixture.stackNamePrefix}HeyThere`, + '--parameters', `${fixture.stackNamePrefix}-param-test-3:OtherDisplayNameParam=${fixture.stackNamePrefix}AnotherOne`, + ], + }); +})); +test_helpers_1.integTest('deploy with parameters multi', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const paramVal1 = `${fixture.stackNamePrefix}bazinga`; + const paramVal2 = `${fixture.stackNamePrefix}=jagshemash`; + const stackArn = await fixture.cdkDeploy('param-test-3', { + options: [ + '--parameters', `DisplayNameParam=${paramVal1}`, + '--parameters', `OtherDisplayNameParam=${paramVal2}`, + ], + captureStderr: false, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Parameters).toEqual([ + { + ParameterKey: 'DisplayNameParam', + ParameterValue: paramVal1, + }, + { + ParameterKey: 'OtherDisplayNameParam', + ParameterValue: paramVal2, + }, + ]); +})); +test_helpers_1.integTest('deploy with notification ARN', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a; + const topicName = `${fixture.stackNamePrefix}-test-topic`; + const response = await fixture.aws.sns('createTopic', { Name: topicName }); + const topicArn = response.TopicArn; + try { + await fixture.cdkDeploy('test-2', { + options: ['--notification-arns', topicArn], + }); + // verify that the stack we deployed has our notification ARN + const describeResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('test-2'), + }); + expect((_a = describeResponse.Stacks) === null || _a === void 0 ? void 0 : _a[0].NotificationARNs).toEqual([topicArn]); + } + finally { + await fixture.aws.sns('deleteTopic', { + TopicArn: topicArn, + }); + } +})); +test_helpers_1.integTest('deploy with role', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const roleName = `${fixture.stackNamePrefix}-test-role`; + await deleteRole(); + const createResponse = await fixture.aws.iam('createRole', { + RoleName: roleName, + AssumeRolePolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, + Effect: 'Allow', + }], + }), + }); + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { + RoleName: roleName, + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: '*', + Resource: '*', + Effect: 'Allow', + }], + }), + }); + await aws_helpers_1.retry(fixture.output, 'Trying to assume fresh role', aws_helpers_1.retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', + }); + }); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await aws_helpers_1.sleep(5000); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); + } + finally { + await deleteRole(); + } + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); + } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } + catch (e) { + if (e.message.indexOf('cannot be found') > -1) { + return; + } + throw e; + } + } +})); +test_helpers_1.integTest('cdk diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + // We can make it fail by passing --fail + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1')])) + .rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('cdk diff --fail on multiple stacks exits with error if any of the stacks contains a diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // GIVEN + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + await fixture.cdkDeploy('test-2'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('There were no differences'); + // WHEN / THEN + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('cdk diff --fail with multiple stack exits with if any of the stacks contains a diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('test-1'); + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('There were no differences'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + // WHEN / THEN + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('deploy stack with docker asset', cdk_helpers_1.withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('docker'); +})); +test_helpers_1.integTest('deploy and test stack with lambda asset', cdk_helpers_1.withDefaultFixture(async (fixture) => { + var _a, _b; + const stackArn = await fixture.cdkDeploy('lambda', { captureStderr: false }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const lambdaArn = (_b = (_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Outputs) === null || _b === void 0 ? void 0 : _b[0].OutputValue; + if (lambdaArn === undefined) { + throw new Error('Stack did not have expected Lambda ARN output'); + } + const output = await fixture.aws.lambda('invoke', { + FunctionName: lambdaArn, + }); + expect(JSON.stringify(output.Payload)).toContain('dear asset'); +})); +test_helpers_1.integTest('cdk ls', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const listing = await fixture.cdk(['ls'], { captureStderr: false }); + const expectedStacks = [ + 'conditional-resource', + 'docker', + 'docker-with-custom-file', + 'failed', + 'iam-test', + 'lambda', + 'missing-ssm-parameter', + 'order-providing', + 'outputs-test-1', + 'outputs-test-2', + 'param-test-1', + 'param-test-2', + 'param-test-3', + 'termination-protection', + 'test-1', + 'test-2', + 'with-nested-stack', + 'with-nested-stack-using-parameters', + 'order-consuming', + ]; + for (const stack of expectedStacks) { + expect(listing).toContain(fixture.fullStackName(stack)); + } +})); +test_helpers_1.integTest('deploy stack without resource', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Deploy the stack without resources + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + // This should have succeeded but not deployed the stack. + await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) + .rejects.toThrow('conditional-resource does not exist'); + // Deploy the stack with resources + await fixture.cdkDeploy('conditional-resource'); + // Then again WITHOUT resources (this should destroy the stack) + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) + .rejects.toThrow('conditional-resource does not exist'); +})); +test_helpers_1.integTest('IAM diff', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const output = await fixture.cdk(['diff', fixture.fullStackName('iam-test')]); + // Roughly check for a table like this: + // + // ┌───┬─────────────────┬────────┬────────────────┬────────────────────────────-──┬───────────┐ + // │ │ Resource │ Effect │ Action │ Principal │ Condition │ + // ├───┼─────────────────┼────────┼────────────────┼───────────────────────────────┼───────────┤ + // │ + │ ${SomeRole.Arn} │ Allow │ sts:AssumeRole │ Service:ec2.amazonaws.com │ │ + // └───┴─────────────────┴────────┴────────────────┴───────────────────────────────┴───────────┘ + expect(output).toContain('${SomeRole.Arn}'); + expect(output).toContain('sts:AssumeRole'); + expect(output).toContain('ec2.amazonaws.com'); +})); +test_helpers_1.integTest('fast deploy', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // we are using a stack with a nested stack because CFN will always attempt to + // update a nested stack, which will allow us to verify that updates are actually + // skipped unless --force is specified. + const stackArn = await fixture.cdkDeploy('with-nested-stack', { captureStderr: false }); + const changeSet1 = await getLatestChangeSet(); + // Deploy the same stack again, there should be no new change set created + await fixture.cdkDeploy('with-nested-stack'); + const changeSet2 = await getLatestChangeSet(); + expect(changeSet2.ChangeSetId).toEqual(changeSet1.ChangeSetId); + // Deploy the stack again with --force, now we should create a changeset + await fixture.cdkDeploy('with-nested-stack', { options: ['--force'] }); + const changeSet3 = await getLatestChangeSet(); + expect(changeSet3.ChangeSetId).not.toEqual(changeSet2.ChangeSetId); + // Deploy the stack again with tags, expected to create a new changeset + // even though the resources didn't change. + await fixture.cdkDeploy('with-nested-stack', { options: ['--tags', 'key=value'] }); + const changeSet4 = await getLatestChangeSet(); + expect(changeSet4.ChangeSetId).not.toEqual(changeSet3.ChangeSetId); + async function getLatestChangeSet() { + var _a, _b, _c; + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: stackArn }); + if (!((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0])) { + throw new Error('Did not get a ChangeSet at all'); + } + fixture.log(`Found Change Set ${(_b = response.Stacks) === null || _b === void 0 ? void 0 : _b[0].ChangeSetId}`); + return (_c = response.Stacks) === null || _c === void 0 ? void 0 : _c[0]; + } +})); +test_helpers_1.integTest('failed deploy does not hang', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // this will hang if we introduce https://github.com/aws/aws-cdk/issues/6403 again. + await expect(fixture.cdkDeploy('failed')).rejects.toThrow('exited with error'); +})); +test_helpers_1.integTest('can still load old assemblies', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); + const testAssembliesDirectory = path.join(__dirname, 'cloud-assemblies'); + for (const asmdir of await listChildDirs(testAssembliesDirectory)) { + fixture.log(`ASSEMBLY ${asmdir}`); + await cdk_helpers_1.cloneDirectory(asmdir, cxAsmDir); + // Some files in the asm directory that have a .js extension are + // actually treated as templates. Evaluate them using NodeJS. + const templates = await listChildren(cxAsmDir, fullPath => Promise.resolve(fullPath.endsWith('.js'))); + for (const template of templates) { + const targetName = template.replace(/.js$/, ''); + await cdk_helpers_1.shell([process.execPath, template, '>', targetName], { + cwd: cxAsmDir, + output: fixture.output, + modEnv: { + TEST_ACCOUNT: await fixture.aws.account(), + TEST_REGION: fixture.aws.region, + }, + }); + } + // Use this directory as a Cloud Assembly + const output = await fixture.cdk([ + '--app', cxAsmDir, + '-v', + 'synth', + ]); + // Assert that there was no providerError in CDK's stderr + // Because we rely on the app/framework to actually error in case the + // provider fails, we inspect the logs here. + expect(output).not.toContain('$providerError'); + } +})); +test_helpers_1.integTest('generating and loading assembly', cdk_helpers_1.withDefaultFixture(async (fixture) => { + const asmOutputDir = `${fixture.integTestDir}-cdk-integ-asm`; + await fixture.shell(['rm', '-rf', asmOutputDir]); + // Synthesize a Cloud Assembly tothe default directory (cdk.out) and a specific directory. + await fixture.cdk(['synth']); + await fixture.cdk(['synth', '--output', asmOutputDir]); + // cdk.out in the current directory and the indicated --output should be the same + await fixture.shell(['diff', 'cdk.out', asmOutputDir]); + // Check that we can 'ls' the synthesized asm. + // Change to some random directory to make sure we're not accidentally loading cdk.json + const list = await fixture.cdk(['--app', asmOutputDir, 'ls'], { cwd: os.tmpdir() }); + // Same stacks we know are in the app + expect(list).toContain(`${fixture.stackNamePrefix}-lambda`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-1`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-2`); + // Check that we can use '.' and just synth ,the generated asm + const stackTemplate = await fixture.cdk(['--app', '.', 'synth', fixture.fullStackName('test-2')], { + cwd: asmOutputDir, + }); + expect(stackTemplate).toContain('topic152D84A37'); + // Deploy a Lambda from the copied asm + await fixture.cdkDeploy('lambda', { options: ['-a', '.'], cwd: asmOutputDir }); + // Remove (rename) the original custom docker file that was used during synth. + // this verifies that the assemly has a copy of it and that the manifest uses + // relative paths to reference to it. + const customDockerFile = path.join(fixture.integTestDir, 'docker', 'Dockerfile.Custom'); + await fs_1.promises.rename(customDockerFile, `${customDockerFile}~`); + try { + // deploy a docker image with custom file without synth (uses assets) + await fixture.cdkDeploy('docker-with-custom-file', { options: ['-a', '.'], cwd: asmOutputDir }); + } + finally { + // Rename back to restore fixture to original state + await fs_1.promises.rename(`${customDockerFile}~`, customDockerFile); + } +})); +test_helpers_1.integTest('templates on disk contain metadata resource, also in nested assemblies', cdk_helpers_1.withDefaultFixture(async (fixture) => { + // Synth first, and switch on version reporting because cdk.json is disabling it + await fixture.cdk(['synth', '--version-reporting=true']); + // Load template from disk from root assembly + const templateContents = await fixture.shell(['cat', 'cdk.out/*-lambda.template.json']); + expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); + // Load template from nested assembly + const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*-stage-StackInStage.template.json']); + expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); +})); +async function listChildren(parent, pred) { + const ret = new Array(); + for (const child of await fs_1.promises.readdir(parent, { encoding: 'utf-8' })) { + const fullPath = path.join(parent, child.toString()); + if (await pred(fullPath)) { + ret.push(fullPath); + } + } + return ret; +} +async function listChildDirs(parent) { + return listChildren(parent, async (fullPath) => (await fs_1.promises.stat(fullPath)).isDirectory()); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmludGVndGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNsaS5pbnRlZ3Rlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSwyQkFBb0M7QUFDcEMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QiwrQ0FBNkM7QUFDN0MsK0NBQTBFO0FBQzFFLGlEQUEyQztBQUUzQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUU1Qix3QkFBUyxDQUFDLFlBQVksRUFBRSxnQ0FBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7SUFDM0QsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sT0FBTyxDQUFDLFVBQVUsQ0FBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFckYsT0FBTyxDQUFDLEdBQUcsQ0FBQyw0Q0FBNEMsQ0FBQyxDQUFDO0lBQzFELE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDcEYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0lBRS9CLE9BQU8sQ0FBQyxHQUFHLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUNwRCxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLEVBQUUsa0JBQWtCLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3RGLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLGdDQUFnQyxFQUFFLGdDQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUMvRSxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFH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LENBQUMsQ0FBQyxDQUFDO0lBRTNILE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxDQUFDLFVBQVUsRUFBRSxDQUFDO0FBQ2hGLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSixLQUFLLFVBQVUsWUFBWSxDQUFDLE1BQWMsRUFBRSxJQUFxQztJQUMvRSxNQUFNLEdBQUcsR0FBRyxJQUFJLEtBQUssRUFBVSxDQUFDO0lBQ2hDLEtBQUssTUFBTSxLQUFLLElBQUksTUFBTSxhQUFFLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFO1FBQ25FLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ3JELElBQUksTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUU7WUFDeEIsR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztTQUNwQjtLQUNGO0lBQ0QsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDO0FBRUQsS0FBSyxVQUFVLGFBQWEsQ0FBQyxNQUFjO0lBQ3pDLE9BQU8sWUFBWSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsUUFBZ0IsRUFBRSxFQUFFLENBQUMsQ0FBQyxNQUFNLGFBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO0FBQ25HLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBwcm9taXNlcyBhcyBmcyB9IGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyByZXRyeSwgc2xlZXAgfSBmcm9tICcuL2F3cy1oZWxwZXJzJztcbmltcG9ydCB7IGNsb25lRGlyZWN0b3J5LCBzaGVsbCwgd2l0aERlZmF1bHRGaXh0dXJlIH0gZnJvbSAnLi9jZGstaGVscGVycyc7XG5pbXBvcnQgeyBpbnRlZ1Rlc3QgfSBmcm9tICcuL3Rlc3QtaGVscGVycyc7XG5cbmplc3Quc2V0VGltZW91dCg2MDAgKiAxMDAwKTtcblxuaW50ZWdUZXN0KCdWUEMgTG9va3VwJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGZpeHR1cmUubG9nKCdNYWtpbmcgc3VyZSB3ZSBhcmUgY2xlYW4gYmVmb3JlIHN0YXJ0aW5nLicpO1xuICBhd2FpdCBmaXh0dXJlLmNka0Rlc3Ryb3koJ2RlZmluZS12cGMnLCB7IG1vZEVudjogeyBFTkFCTEVfVlBDX1RFU1RJTkc6ICdERUZJTkUnIH0gfSk7XG5cbiAgZml4dHVyZS5sb2coJ1NldHRpbmcgdXA6IGNyZWF0aW5nIGEgVlBDIHdpdGgga25vd24gdGFncycpO1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnZGVmaW5lLXZwYycsIHsgbW9kRW52OiB7IEVOQUJMRV9WUENfVEVTVElORzogJ0RFRklORScgfSB9KTtcbiAgZml4dHVyZS5sb2coJ1NldHVwIGNvbXBsZXRlIScpO1xuXG4gIGZpeHR1cmUubG9nKCdWZXJpZnlpbmcgd2UgY2FuIG5vdyBpbXBvcnQgdGhhdCBWUEMnKTtcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ2ltcG9ydC12cGMnLCB7IG1vZEVudjogeyBFTkFCTEVfVlBDX1RFU1RJTkc6ICdJTVBPUlQnIH0gfSk7XG59KSk7XG5cbmludGVnVGVzdCgnVHdvIHdheXMgb2Ygc2hvaW5nIHRoZSB2ZXJzaW9uJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IHZlcnNpb24xID0gYXdhaXQgZml4dHVyZS5jZGsoWyd2ZXJzaW9uJ10sIHsgdmVyYm9zZTogZmFsc2UgfSk7XG4gIGNvbnN0IHZlcnNpb24yID0gYXdhaXQgZml4dHVyZS5jZGsoWyctLXZlcnNpb24nXSwgeyB2ZXJib3NlOiBmYWxzZSB9KTtcblxuICBleHBlY3QodmVyc2lvbjEpLnRvRXF1YWwodmVyc2lvbjIpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ1Rlcm1pbmF0aW9uIHByb3RlY3Rpb24nLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgc3RhY2tOYW1lID0gJ3Rlcm1pbmF0aW9uLXByb3RlY3Rpb24nO1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveShzdGFja05hbWUpO1xuXG4gIC8vIFRyeSBhIGRlc3Ryb3kgdGhhdCBzaG91bGQgZmFpbFxuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGtEZXN0cm95KHN0YWNrTmFtZSkpLnJlamVjdHMudG9UaHJvdygnZXhpdGVkIHdpdGggZXJyb3InKTtcblxuICAvLyBDYW4gdXBkYXRlIHRlcm1pbmF0aW9uIHByb3RlY3Rpb24gZXZlbiB0aG91Z2ggdGhlIGNoYW5nZSBzZXQgZG9lc24ndCBjb250YWluIGNoYW5nZXNcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koc3RhY2tOYW1lLCB7IG1vZEVudjogeyBURVJNSU5BVElPTl9QUk9URUNUSU9OOiAnRkFMU0UnIH0gfSk7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVzdHJveShzdGFja05hbWUpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBzeW50aCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGsoWydzeW50aCcsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldLCB7IHZlcmJvc2U6IGZhbHNlIH0pKS5yZXNvbHZlcy50b0VxdWFsKFxuICAgIGBSZXNvdXJjZXM6XG4gIHRvcGljNjk4MzE0OTE6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMS90b3BpYy9SZXNvdXJjZWApO1xuXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ3N5bnRoJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0sIHsgdmVyYm9zZTogZmFsc2UgfSkpLnJlc29sdmVzLnRvRXF1YWwoXG4gICAgYFJlc291cmNlczpcbiAgdG9waWMxNTJEODRBMzc6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMi90b3BpYzEvUmVzb3VyY2VcbiAgdG9waWMyQTRGQjU0N0Y6XG4gICAgVHlwZTogQVdTOjpTTlM6OlRvcGljXG4gICAgTWV0YWRhdGE6XG4gICAgICBhd3M6Y2RrOnBhdGg6ICR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMi90b3BpYzIvUmVzb3VyY2VgKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdzc20gcGFyYW1ldGVyIHByb3ZpZGVyIGVycm9yJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ3N5bnRoJyxcbiAgICBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ21pc3Npbmctc3NtLXBhcmFtZXRlcicpLFxuICAgICctYycsICd0ZXN0OnNzbS1wYXJhbWV0ZXItbmFtZT0vZG9lcy9ub3QvZXhpc3QnXSwge1xuICAgIGFsbG93RXJyRXhpdDogdHJ1ZSxcbiAgfSkpLnJlc29sdmVzLnRvQ29udGFpbignU1NNIHBhcmFtZXRlciBub3QgYXZhaWxhYmxlIGluIGFjY291bnQnKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdhdXRvbWF0aWMgb3JkZXJpbmcnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gRGVwbG95IHRoZSBjb25zdW1pbmcgc3RhY2sgd2hpY2ggd2lsbCBpbmNsdWRlIHRoZSBwcm9kdWNpbmcgc3RhY2tcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ29yZGVyLWNvbnN1bWluZycpO1xuXG4gIC8vIERlc3Ryb3kgdGhlIHByb3ZpZGluZyBzdGFjayB3aGljaCB3aWxsIGluY2x1ZGUgdGhlIGNvbnN1bWluZyBzdGFja1xuICBhd2FpdCBmaXh0dXJlLmNka0Rlc3Ryb3koJ29yZGVyLXByb3ZpZGluZycpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NvbnRleHQgc2V0dGluZycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBhd2FpdCBmcy53cml0ZUZpbGUocGF0aC5qb2luKGZpeHR1cmUuaW50ZWdUZXN0RGlyLCAnY2RrLmNvbnRleHQuanNvbicpLCBKU09OLnN0cmluZ2lmeSh7XG4gICAgY29udGV4dGtleTogJ3RoaXMgaXMgdGhlIGNvbnRleHQgdmFsdWUnLFxuICB9KSk7XG4gIHRyeSB7XG4gICAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuY2RrKFsnY29udGV4dCddKSkucmVzb2x2ZXMudG9Db250YWluKCd0aGlzIGlzIHRoZSBjb250ZXh0IHZhbHVlJyk7XG5cbiAgICAvLyBUZXN0IHRoYXQgZGVsZXRpbmcgdGhlIGNvbnRleHRrZXkgd29ya3NcbiAgICBhd2FpdCBmaXh0dXJlLmNkayhbJ2NvbnRleHQnLCAnLS1yZXNldCcsICdjb250ZXh0a2V5J10pO1xuICAgIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2NvbnRleHQnXSkpLnJlc29sdmVzLm5vdC50b0NvbnRhaW4oJ3RoaXMgaXMgdGhlIGNvbnRleHQgdmFsdWUnKTtcblxuICAgIC8vIFRlc3QgdGhhdCBmb3JjZWQgZGVsZXRlIG9mIHRoZSBjb250ZXh0IGtleSBkb2VzIG5vdCB0aHJvd1xuICAgIGF3YWl0IGZpeHR1cmUuY2RrKFsnY29udGV4dCcsICctZicsICctLXJlc2V0JywgJ2NvbnRleHRrZXknXSk7XG5cbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBmcy51bmxpbmsocGF0aC5qb2luKGZpeHR1cmUuaW50ZWdUZXN0RGlyLCAnY2RrLmNvbnRleHQuanNvbicpKTtcbiAgfVxufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd0ZXN0LTInLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuXG4gIC8vIHZlcmlmeSB0aGUgbnVtYmVyIG9mIHJlc291cmNlcyBpbiB0aGUgc3RhY2tcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja1Jlc291cmNlcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrUmVzb3VyY2VzPy5sZW5ndGgpLnRvRXF1YWwoMik7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IGFsbCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBhcm5zID0gYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtKicsIHsgY2FwdHVyZVN0ZGVycjogZmFsc2UgfSk7XG5cbiAgLy8gdmVyaWZ5IHRoYXQgd2Ugb25seSBkZXBsb3llZCBhIHNpbmdsZSBzdGFjayAodGhlcmUncyBhIHNpbmdsZSBBUk4gaW4gdGhlIG91dHB1dClcbiAgZXhwZWN0KGFybnMuc3BsaXQoJ1xcbicpLmxlbmd0aCkudG9FcXVhbCgyKTtcbn0pKTtcblxuaW50ZWdUZXN0KCduZXN0ZWQgc3RhY2sgd2l0aCBwYXJhbWV0ZXJzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIC8vIFNUQUNLX05BTUVfUFJFRklYIGlzIHVzZWQgaW4gTXlUb3BpY1BhcmFtIHRvIGFsbG93IG11bHRpcGxlIGluc3RhbmNlc1xuICAvLyBvZiB0aGlzIHRlc3QgdG8gcnVuIGluIHBhcmFsbGVsLCBvdGhld2lzZSB0aGV5IHdpbGwgYXR0ZW1wdCB0byBjcmVhdGUgdGhlIHNhbWUgU05TIHRvcGljLlxuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd3aXRoLW5lc3RlZC1zdGFjay11c2luZy1wYXJhbWV0ZXJzJywge1xuICAgIG9wdGlvbnM6IFsnLS1wYXJhbWV0ZXJzJywgYE15VG9waWNQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fVRoZXJlSXNOb1Nwb29uYF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIC8vIHZlcmlmeSB0aGF0IHdlIG9ubHkgZGVwbG95ZWQgYSBzaW5nbGUgc3RhY2sgKHRoZXJlJ3MgYSBzaW5nbGUgQVJOIGluIHRoZSBvdXRwdXQpXG4gIGV4cGVjdChzdGFja0Fybi5zcGxpdCgnXFxuJykubGVuZ3RoKS50b0VxdWFsKDEpO1xuXG4gIC8vIHZlcmlmeSB0aGUgbnVtYmVyIG9mIHJlc291cmNlcyBpbiB0aGUgc3RhY2tcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja1Jlc291cmNlcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrUmVzb3VyY2VzPy5sZW5ndGgpLnRvRXF1YWwoMSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGhvdXQgZXhlY3V0ZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCd0ZXN0LTInLCB7XG4gICAgb3B0aW9uczogWyctLW5vLWV4ZWN1dGUnXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG4gIC8vIHZlcmlmeSB0aGF0IHdlIG9ubHkgZGVwbG95ZWQgYSBzaW5nbGUgc3RhY2sgKHRoZXJlJ3MgYSBzaW5nbGUgQVJOIGluIHRoZSBvdXRwdXQpXG4gIGV4cGVjdChzdGFja0Fybi5zcGxpdCgnXFxuJykubGVuZ3RoKS50b0VxdWFsKDEpO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogc3RhY2tBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5TdGFja1N0YXR1cykudG9FcXVhbCgnUkVWSUVXX0lOX1BST0dSRVNTJyk7XG59KSk7XG5cbmludGVnVGVzdCgnc2VjdXJpdHkgcmVsYXRlZCBjaGFuZ2VzIHdpdGhvdXQgYSBDTEkgYXJlIGV4cGVjdGVkIHRvIGZhaWwnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gcmVkaXJlY3QgL2Rldi9udWxsIHRvIHN0ZGluLCB3aGljaCBtZWFucyB0aGVyZSB3aWxsIG5vdCBiZSB0dHkgYXR0YWNoZWRcbiAgLy8gc2luY2UgdGhpcyBzdGFjayBpbmNsdWRlcyBzZWN1cml0eS1yZWxhdGVkIGNoYW5nZXMsIHRoZSBkZXBsb3ltZW50IHNob3VsZFxuICAvLyBpbW1lZGlhdGVseSBmYWlsIGJlY2F1c2Ugd2UgY2FuJ3QgY29uZmlybSB0aGUgY2hhbmdlc1xuICBjb25zdCBzdGFja05hbWUgPSAnaWFtLXRlc3QnO1xuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGtEZXBsb3koc3RhY2tOYW1lLCB7XG4gICAgb3B0aW9uczogWyc8JywgJy9kZXYvbnVsbCddLCAvLyBINHgsIHRoaXMgb25seSB3b3JrcyBiZWNhdXNlIEkgaGFwcGVuIHRvIGtub3cgd2UgcGFzcyBzaGVsbDogdHJ1ZS5cbiAgICBuZXZlclJlcXVpcmVBcHByb3ZhbDogZmFsc2UsXG4gIH0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG5cbiAgLy8gRW5zdXJlIHN0YWNrIHdhcyBub3QgZGVwbG95ZWRcbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IGZpeHR1cmUuZnVsbFN0YWNrTmFtZShzdGFja05hbWUpLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdkb2VzIG5vdCBleGlzdCcpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aWxkY2FyZCB3aXRoIG91dHB1dHMnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgb3V0cHV0c0ZpbGUgPSBwYXRoLmpvaW4oZml4dHVyZS5pbnRlZ1Rlc3REaXIsICdvdXRwdXRzJywgJ291dHB1dHMuanNvbicpO1xuICBhd2FpdCBmcy5ta2RpcihwYXRoLmRpcm5hbWUob3V0cHV0c0ZpbGUpLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveShbJ291dHB1dHMtdGVzdC0qJ10sIHtcbiAgICBvcHRpb25zOiBbJy0tb3V0cHV0cy1maWxlJywgb3V0cHV0c0ZpbGVdLFxuICB9KTtcblxuICBjb25zdCBvdXRwdXRzID0gSlNPTi5wYXJzZSgoYXdhaXQgZnMucmVhZEZpbGUob3V0cHV0c0ZpbGUsIHsgZW5jb2Rpbmc6ICd1dGYtOCcgfSkpLnRvU3RyaW5nKCkpO1xuICBleHBlY3Qob3V0cHV0cykudG9FcXVhbCh7XG4gICAgW2Ake2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMWBdOiB7XG4gICAgICBUb3BpY05hbWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMU15VG9waWNgLFxuICAgIH0sXG4gICAgW2Ake2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMmBdOiB7XG4gICAgICBUb3BpY05hbWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1vdXRwdXRzLXRlc3QtMk15T3RoZXJUb3BpY2AsXG4gICAgfSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcGFyYW1ldGVycycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBzdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LTEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGBUb3BpY05hbWVQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWJhemluZ2FgLFxuICAgIF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogc3RhY2tBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5QYXJhbWV0ZXJzKS50b0VxdWFsKFtcbiAgICB7XG4gICAgICBQYXJhbWV0ZXJLZXk6ICdUb3BpY05hbWVQYXJhbScsXG4gICAgICBQYXJhbWV0ZXJWYWx1ZTogYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9YmF6aW5nYWAsXG4gICAgfSxcbiAgXSk7XG59KSk7XG5cbmludGVnVGVzdCgndXBkYXRlIHRvIHN0YWNrIGluIFJPTExCQUNLX0NPTVBMRVRFIHN0YXRlIHdpbGwgZGVsZXRlIHN0YWNrIGFuZCBjcmVhdGUgYSBuZXcgb25lJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIC8vIEdJVkVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1AYXd3YCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogZml4dHVyZS5mdWxsU3RhY2tOYW1lKCdwYXJhbS10ZXN0LTEnKSxcbiAgfSk7XG5cbiAgY29uc3Qgc3RhY2tBcm4gPSByZXNwb25zZS5TdGFja3M/LlswXS5TdGFja0lkO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1JPTExCQUNLX0NPTVBMRVRFJyk7XG5cbiAgLy8gV0hFTlxuICBjb25zdCBuZXdTdGFja0FybiA9IGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LTEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGBUb3BpY05hbWVQYXJhbT0ke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWFsbGdvb2RgLFxuICAgIF0sXG4gICAgY2FwdHVyZVN0ZGVycjogZmFsc2UsXG4gIH0pO1xuXG4gIGNvbnN0IG5ld1N0YWNrUmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBuZXdTdGFja0FybixcbiAgfSk7XG5cbiAgLy8gVEhFTlxuICBleHBlY3QgKHN0YWNrQXJuKS5ub3QudG9FcXVhbChuZXdTdGFja0Fybik7IC8vIG5ldyBzdGFjayB3YXMgY3JlYXRlZFxuICBleHBlY3QobmV3U3RhY2tSZXNwb25zZS5TdGFja3M/LlswXS5TdGFja1N0YXR1cykudG9FcXVhbCgnQ1JFQVRFX0NPTVBMRVRFJyk7XG4gIGV4cGVjdChuZXdTdGFja1Jlc3BvbnNlLlN0YWNrcz8uWzBdLlBhcmFtZXRlcnMpLnRvRXF1YWwoW1xuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ1RvcGljTmFtZVBhcmFtJyxcbiAgICAgIFBhcmFtZXRlclZhbHVlOiBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1hbGxnb29kYCxcbiAgICB9LFxuICBdKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdzdGFjayBpbiBVUERBVEVfUk9MTEJBQ0tfQ09NUExFVEUgc3RhdGUgY2FuIGJlIHVwZGF0ZWQnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1uaWNlYCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KTtcblxuICBsZXQgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrcz8uWzBdLlN0YWNrU3RhdHVzKS50b0VxdWFsKCdDUkVBVEVfQ09NUExFVEUnKTtcblxuICAvLyBiYWQgcGFyYW1ldGVyIG5hbWUgd2l0aCBAIHdpbGwgcHV0IHN0YWNrIGludG8gVVBEQVRFX1JPTExCQUNLX0NPTVBMRVRFXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNka0RlcGxveSgncGFyYW0tdGVzdC0xJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1AYXd3YCxcbiAgICBdLFxuICAgIGNhcHR1cmVTdGRlcnI6IGZhbHNlLFxuICB9KSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpOztcblxuICByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcblxuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1VQREFURV9ST0xMQkFDS19DT01QTEVURScpO1xuXG4gIC8vIFdIRU5cbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3BhcmFtLXRlc3QtMScsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYFRvcGljTmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9YWxsZ29vZGAsXG4gICAgXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG5cbiAgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgLy8gVEhFTlxuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uU3RhY2tTdGF0dXMpLnRvRXF1YWwoJ1VQREFURV9DT01QTEVURScpO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uUGFyYW1ldGVycykudG9FcXVhbChbXG4gICAge1xuICAgICAgUGFyYW1ldGVyS2V5OiAnVG9waWNOYW1lUGFyYW0nLFxuICAgICAgUGFyYW1ldGVyVmFsdWU6IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fWFsbGdvb2RgLFxuICAgIH0sXG4gIF0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aXRoIHdpbGRjYXJkIGFuZCBwYXJhbWV0ZXJzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdwYXJhbS10ZXN0LSonLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tcGFyYW1ldGVycycsIGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1wYXJhbS10ZXN0LTE6VG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1iYXppbmdhYCxcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH0tcGFyYW0tdGVzdC0yOk90aGVyVG9waWNOYW1lUGFyYW09JHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1UaGF0c015U3BvdGAsXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXBhcmFtLXRlc3QtMzpEaXNwbGF5TmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9SGV5VGhlcmVgLFxuICAgICAgJy0tcGFyYW1ldGVycycsIGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1wYXJhbS10ZXN0LTM6T3RoZXJEaXNwbGF5TmFtZVBhcmFtPSR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9QW5vdGhlck9uZWAsXG4gICAgXSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcGFyYW1ldGVycyBtdWx0aScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBwYXJhbVZhbDEgPSBgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH1iYXppbmdhYDtcbiAgY29uc3QgcGFyYW1WYWwyID0gYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9PWphZ3NoZW1hc2hgO1xuXG4gIGNvbnN0IHN0YWNrQXJuID0gYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3BhcmFtLXRlc3QtMycsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS1wYXJhbWV0ZXJzJywgYERpc3BsYXlOYW1lUGFyYW09JHtwYXJhbVZhbDF9YCxcbiAgICAgICctLXBhcmFtZXRlcnMnLCBgT3RoZXJEaXNwbGF5TmFtZVBhcmFtPSR7cGFyYW1WYWwyfWAsXG4gICAgXSxcbiAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgfSk7XG5cbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBzdGFja0FybixcbiAgfSk7XG5cbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrcz8uWzBdLlBhcmFtZXRlcnMpLnRvRXF1YWwoW1xuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ0Rpc3BsYXlOYW1lUGFyYW0nLFxuICAgICAgUGFyYW1ldGVyVmFsdWU6IHBhcmFtVmFsMSxcbiAgICB9LFxuICAgIHtcbiAgICAgIFBhcmFtZXRlcktleTogJ090aGVyRGlzcGxheU5hbWVQYXJhbScsXG4gICAgICBQYXJhbWV0ZXJWYWx1ZTogcGFyYW1WYWwyLFxuICAgIH0sXG4gIF0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2RlcGxveSB3aXRoIG5vdGlmaWNhdGlvbiBBUk4nLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgdG9waWNOYW1lID0gYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtdG9waWNgO1xuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZml4dHVyZS5hd3Muc25zKCdjcmVhdGVUb3BpYycsIHsgTmFtZTogdG9waWNOYW1lIH0pO1xuICBjb25zdCB0b3BpY0FybiA9IHJlc3BvbnNlLlRvcGljQXJuITtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgndGVzdC0yJywge1xuICAgICAgb3B0aW9uczogWyctLW5vdGlmaWNhdGlvbi1hcm5zJywgdG9waWNBcm5dLFxuICAgIH0pO1xuXG4gICAgLy8gdmVyaWZ5IHRoYXQgdGhlIHN0YWNrIHdlIGRlcGxveWVkIGhhcyBvdXIgbm90aWZpY2F0aW9uIEFSTlxuICAgIGNvbnN0IGRlc2NyaWJlUmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgICBTdGFja05hbWU6IGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0yJyksXG4gICAgfSk7XG4gICAgZXhwZWN0KGRlc2NyaWJlUmVzcG9uc2UuU3RhY2tzPy5bMF0uTm90aWZpY2F0aW9uQVJOcykudG9FcXVhbChbdG9waWNBcm5dKTtcbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmF3cy5zbnMoJ2RlbGV0ZVRvcGljJywge1xuICAgICAgVG9waWNBcm46IHRvcGljQXJuLFxuICAgIH0pO1xuICB9XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHdpdGggcm9sZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCByb2xlTmFtZSA9IGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS10ZXN0LXJvbGVgO1xuXG4gIGF3YWl0IGRlbGV0ZVJvbGUoKTtcblxuICBjb25zdCBjcmVhdGVSZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmlhbSgnY3JlYXRlUm9sZScsIHtcbiAgICBSb2xlTmFtZTogcm9sZU5hbWUsXG4gICAgQXNzdW1lUm9sZVBvbGljeURvY3VtZW50OiBKU09OLnN0cmluZ2lmeSh7XG4gICAgICBWZXJzaW9uOiAnMjAxMi0xMC0xNycsXG4gICAgICBTdGF0ZW1lbnQ6IFt7XG4gICAgICAgIEFjdGlvbjogJ3N0czpBc3N1bWVSb2xlJyxcbiAgICAgICAgUHJpbmNpcGFsOiB7IFNlcnZpY2U6ICdjbG91ZGZvcm1hdGlvbi5hbWF6b25hd3MuY29tJyB9LFxuICAgICAgICBFZmZlY3Q6ICdBbGxvdycsXG4gICAgICB9LCB7XG4gICAgICAgIEFjdGlvbjogJ3N0czpBc3N1bWVSb2xlJyxcbiAgICAgICAgUHJpbmNpcGFsOiB7IEFXUzogKGF3YWl0IGZpeHR1cmUuYXdzLnN0cygnZ2V0Q2FsbGVySWRlbnRpdHknLCB7fSkpLkFybiB9LFxuICAgICAgICBFZmZlY3Q6ICdBbGxvdycsXG4gICAgICB9XSxcbiAgICB9KSxcbiAgfSk7XG4gIGNvbnN0IHJvbGVBcm4gPSBjcmVhdGVSZXNwb25zZS5Sb2xlLkFybjtcbiAgdHJ5IHtcbiAgICBhd2FpdCBmaXh0dXJlLmF3cy5pYW0oJ3B1dFJvbGVQb2xpY3knLCB7XG4gICAgICBSb2xlTmFtZTogcm9sZU5hbWUsXG4gICAgICBQb2xpY3lOYW1lOiAnRGVmYXVsdFBvbGljeScsXG4gICAgICBQb2xpY3lEb2N1bWVudDogSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICBWZXJzaW9uOiAnMjAxMi0xMC0xNycsXG4gICAgICAgIFN0YXRlbWVudDogW3tcbiAgICAgICAgICBBY3Rpb246ICcqJyxcbiAgICAgICAgICBSZXNvdXJjZTogJyonLFxuICAgICAgICAgIEVmZmVjdDogJ0FsbG93JyxcbiAgICAgICAgfV0sXG4gICAgICB9KSxcbiAgICB9KTtcblxuICAgIGF3YWl0IHJldHJ5KGZpeHR1cmUub3V0cHV0LCAnVHJ5aW5nIHRvIGFzc3VtZSBmcmVzaCByb2xlJywgcmV0cnkuZm9yU2Vjb25kcygzMDApLCBhc3luYyAoKSA9PiB7XG4gICAgICBhd2FpdCBmaXh0dXJlLmF3cy5zdHMoJ2Fzc3VtZVJvbGUnLCB7XG4gICAgICAgIFJvbGVBcm46IHJvbGVBcm4sXG4gICAgICAgIFJvbGVTZXNzaW9uTmFtZTogJ3Rlc3RpbmcnLFxuICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICAvLyBJbiBwcmluY2lwbGUsIHRoZSByb2xlIGhhcyByZXBsaWNhdGVkIGZyb20gJ3VzLWVhc3QtMScgdG8gd2hlcmV2ZXIgd2UncmUgdGVzdGluZy5cbiAgICAvLyBHaXZlIGl0IGEgbGl0dGxlIG1vcmUgc2xlZXAgdG8gbWFrZSBzdXJlIENsb3VkRm9ybWF0aW9uIGlzIG5vdCBoaXR0aW5nIGEgYm94XG4gICAgLy8gdGhhdCBkb2Vzbid0IGhhdmUgaXQgeWV0LlxuICAgIGF3YWl0IHNsZWVwKDUwMDApO1xuXG4gICAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtMicsIHtcbiAgICAgIG9wdGlvbnM6IFsnLS1yb2xlLWFybicsIHJvbGVBcm5dLFxuICAgIH0pO1xuXG4gICAgLy8gSW1tZWRpYXRlbHkgZGVsZXRlIHRoZSBzdGFjayBhZ2FpbiBiZWZvcmUgd2UgZGVsZXRlIHRoZSByb2xlLlxuICAgIC8vXG4gICAgLy8gU2luY2Ugcm9sZXMgYXJlIHN0aWNreSwgaWYgd2UgZGVsZXRlIHRoZSByb2xlIGJlZm9yZSB0aGUgc3RhY2ssIHN1YnNlcXVlbnQgRGVsZXRlU3RhY2tcbiAgICAvLyBvcGVyYXRpb25zIHdpbGwgZmFpbCB3aGVuIENsb3VkRm9ybWF0aW9uIHRyaWVzIHRvIGFzc3VtZSB0aGUgcm9sZSB0aGF0J3MgYWxyZWFkeSBnb25lLlxuICAgIGF3YWl0IGZpeHR1cmUuY2RrRGVzdHJveSgndGVzdC0yJyk7XG5cbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBkZWxldGVSb2xlKCk7XG4gIH1cblxuICBhc3luYyBmdW5jdGlvbiBkZWxldGVSb2xlKCkge1xuICAgIHRyeSB7XG4gICAgICBmb3IgKGNvbnN0IHBvbGljeU5hbWUgb2YgKGF3YWl0IGZpeHR1cmUuYXdzLmlhbSgnbGlzdFJvbGVQb2xpY2llcycsIHsgUm9sZU5hbWU6IHJvbGVOYW1lIH0pKS5Qb2xpY3lOYW1lcykge1xuICAgICAgICBhd2FpdCBmaXh0dXJlLmF3cy5pYW0oJ2RlbGV0ZVJvbGVQb2xpY3knLCB7XG4gICAgICAgICAgUm9sZU5hbWU6IHJvbGVOYW1lLFxuICAgICAgICAgIFBvbGljeU5hbWU6IHBvbGljeU5hbWUsXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgYXdhaXQgZml4dHVyZS5hd3MuaWFtKCdkZWxldGVSb2xlJywgeyBSb2xlTmFtZTogcm9sZU5hbWUgfSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgaWYgKGUubWVzc2FnZS5pbmRleE9mKCdjYW5ub3QgYmUgZm91bmQnKSA+IC0xKSB7IHJldHVybjsgfVxuICAgICAgdGhyb3cgZTtcbiAgICB9XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdjZGsgZGlmZicsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBkaWZmMSA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKTtcbiAgZXhwZWN0KGRpZmYxKS50b0NvbnRhaW4oJ0FXUzo6U05TOjpUb3BpYycpO1xuXG4gIGNvbnN0IGRpZmYyID0gYXdhaXQgZml4dHVyZS5jZGsoWydkaWZmJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pO1xuICBleHBlY3QoZGlmZjIpLnRvQ29udGFpbignQVdTOjpTTlM6OlRvcGljJyk7XG5cbiAgLy8gV2UgY2FuIG1ha2UgaXQgZmFpbCBieSBwYXNzaW5nIC0tZmFpbFxuICBhd2FpdCBleHBlY3QoZml4dHVyZS5jZGsoWydkaWZmJywgJy0tZmFpbCcsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKSlcbiAgICAucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBkaWZmIC0tZmFpbCBvbiBtdWx0aXBsZSBzdGFja3MgZXhpdHMgd2l0aCBlcnJvciBpZiBhbnkgb2YgdGhlIHN0YWNrcyBjb250YWlucyBhIGRpZmYnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgY29uc3QgZGlmZjEgPSBhd2FpdCBmaXh0dXJlLmNkayhbJ2RpZmYnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ3Rlc3QtMScpXSk7XG4gIGV4cGVjdChkaWZmMSkudG9Db250YWluKCdBV1M6OlNOUzo6VG9waWMnKTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgndGVzdC0yJyk7XG4gIGNvbnN0IGRpZmYyID0gYXdhaXQgZml4dHVyZS5jZGsoWydkaWZmJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pO1xuICBleHBlY3QoZGlmZjIpLnRvQ29udGFpbignVGhlcmUgd2VyZSBubyBkaWZmZXJlbmNlcycpO1xuXG4gIC8vIFdIRU4gLyBUSEVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2RpZmYnLCAnLS1mYWlsJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTEnKSwgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG59KSk7XG5cbmludGVnVGVzdCgnY2RrIGRpZmYgLS1mYWlsIHdpdGggbXVsdGlwbGUgc3RhY2sgZXhpdHMgd2l0aCBpZiBhbnkgb2YgdGhlIHN0YWNrcyBjb250YWlucyBhIGRpZmYnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gR0lWRU5cbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3Rlc3QtMScpO1xuICBjb25zdCBkaWZmMSA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0xJyldKTtcbiAgZXhwZWN0KGRpZmYxKS50b0NvbnRhaW4oJ1RoZXJlIHdlcmUgbm8gZGlmZmVyZW5jZXMnKTtcblxuICBjb25zdCBkaWZmMiA9IGF3YWl0IGZpeHR1cmUuY2RrKFsnZGlmZicsIGZpeHR1cmUuZnVsbFN0YWNrTmFtZSgndGVzdC0yJyldKTtcbiAgZXhwZWN0KGRpZmYyKS50b0NvbnRhaW4oJ0FXUzo6U05TOjpUb3BpYycpO1xuXG4gIC8vIFdIRU4gLyBUSEVOXG4gIGF3YWl0IGV4cGVjdChmaXh0dXJlLmNkayhbJ2RpZmYnLCAnLS1mYWlsJywgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTEnKSwgZml4dHVyZS5mdWxsU3RhY2tOYW1lKCd0ZXN0LTInKV0pKS5yZWplY3RzLnRvVGhyb3coJ2V4aXRlZCB3aXRoIGVycm9yJyk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IHN0YWNrIHdpdGggZG9ja2VyIGFzc2V0Jywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdkb2NrZXInKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdkZXBsb3kgYW5kIHRlc3Qgc3RhY2sgd2l0aCBsYW1iZGEgYXNzZXQnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywgeyBjYXB0dXJlU3RkZXJyOiBmYWxzZSB9KTtcblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHtcbiAgICBTdGFja05hbWU6IHN0YWNrQXJuLFxuICB9KTtcbiAgY29uc3QgbGFtYmRhQXJuID0gcmVzcG9uc2UuU3RhY2tzPy5bMF0uT3V0cHV0cz8uWzBdLk91dHB1dFZhbHVlO1xuICBpZiAobGFtYmRhQXJuID09PSB1bmRlZmluZWQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1N0YWNrIGRpZCBub3QgaGF2ZSBleHBlY3RlZCBMYW1iZGEgQVJOIG91dHB1dCcpO1xuICB9XG5cbiAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgZml4dHVyZS5hd3MubGFtYmRhKCdpbnZva2UnLCB7XG4gICAgRnVuY3Rpb25OYW1lOiBsYW1iZGFBcm4sXG4gIH0pO1xuXG4gIGV4cGVjdChKU09OLnN0cmluZ2lmeShvdXRwdXQuUGF5bG9hZCkpLnRvQ29udGFpbignZGVhciBhc3NldCcpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NkayBscycsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBsaXN0aW5nID0gYXdhaXQgZml4dHVyZS5jZGsoWydscyddLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuXG4gIGNvbnN0IGV4cGVjdGVkU3RhY2tzID0gW1xuICAgICdjb25kaXRpb25hbC1yZXNvdXJjZScsXG4gICAgJ2RvY2tlcicsXG4gICAgJ2RvY2tlci13aXRoLWN1c3RvbS1maWxlJyxcbiAgICAnZmFpbGVkJyxcbiAgICAnaWFtLXRlc3QnLFxuICAgICdsYW1iZGEnLFxuICAgICdtaXNzaW5nLXNzbS1wYXJhbWV0ZXInLFxuICAgICdvcmRlci1wcm92aWRpbmcnLFxuICAgICdvdXRwdXRzLXRlc3QtMScsXG4gICAgJ291dHB1dHMtdGVzdC0yJyxcbiAgICAncGFyYW0tdGVzdC0xJyxcbiAgICAncGFyYW0tdGVzdC0yJyxcbiAgICAncGFyYW0tdGVzdC0zJyxcbiAgICAndGVybWluYXRpb24tcHJvdGVjdGlvbicsXG4gICAgJ3Rlc3QtMScsXG4gICAgJ3Rlc3QtMicsXG4gICAgJ3dpdGgtbmVzdGVkLXN0YWNrJyxcbiAgICAnd2l0aC1uZXN0ZWQtc3RhY2stdXNpbmctcGFyYW1ldGVycycsXG4gICAgJ29yZGVyLWNvbnN1bWluZycsXG4gIF07XG5cbiAgZm9yIChjb25zdCBzdGFjayBvZiBleHBlY3RlZFN0YWNrcykge1xuICAgIGV4cGVjdChsaXN0aW5nKS50b0NvbnRhaW4oZml4dHVyZS5mdWxsU3RhY2tOYW1lKHN0YWNrKSk7XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdkZXBsb3kgc3RhY2sgd2l0aG91dCByZXNvdXJjZScsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICAvLyBEZXBsb3kgdGhlIHN0YWNrIHdpdGhvdXQgcmVzb3VyY2VzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdjb25kaXRpb25hbC1yZXNvdXJjZScsIHsgbW9kRW52OiB7IE5PX1JFU09VUkNFOiAnVFJVRScgfSB9KTtcblxuICAvLyBUaGlzIHNob3VsZCBoYXZlIHN1Y2NlZWRlZCBidXQgbm90IGRlcGxveWVkIHRoZSBzdGFjay5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2NvbmRpdGlvbmFsLXJlc291cmNlJykgfSkpXG4gICAgLnJlamVjdHMudG9UaHJvdygnY29uZGl0aW9uYWwtcmVzb3VyY2UgZG9lcyBub3QgZXhpc3QnKTtcblxuICAvLyBEZXBsb3kgdGhlIHN0YWNrIHdpdGggcmVzb3VyY2VzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdjb25kaXRpb25hbC1yZXNvdXJjZScpO1xuXG4gIC8vIFRoZW4gYWdhaW4gV0lUSE9VVCByZXNvdXJjZXMgKHRoaXMgc2hvdWxkIGRlc3Ryb3kgdGhlIHN0YWNrKVxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnY29uZGl0aW9uYWwtcmVzb3VyY2UnLCB7IG1vZEVudjogeyBOT19SRVNPVVJDRTogJ1RSVUUnIH0gfSk7XG5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2NvbmRpdGlvbmFsLXJlc291cmNlJykgfSkpXG4gICAgLnJlamVjdHMudG9UaHJvdygnY29uZGl0aW9uYWwtcmVzb3VyY2UgZG9lcyBub3QgZXhpc3QnKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdJQU0gZGlmZicsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBvdXRwdXQgPSBhd2FpdCBmaXh0dXJlLmNkayhbJ2RpZmYnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ2lhbS10ZXN0JyldKTtcblxuICAvLyBSb3VnaGx5IGNoZWNrIGZvciBhIHRhYmxlIGxpa2UgdGhpczpcbiAgLy9cbiAgLy8g4pSM4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSs4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSALeKUgOKUgOKUrOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUkFxuICAvLyDilIIgICDilIIgUmVzb3VyY2UgICAgICAgIOKUgiBFZmZlY3Qg4pSCIEFjdGlvbiAgICAgICAgIOKUgiBQcmluY2lwYWwgICAgICAgICAgICAgICAgICAgICDilIIgQ29uZGl0aW9uIOKUglxuICAvLyDilJzilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilLzilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilKRcbiAgLy8g4pSCICsg4pSCICR7U29tZVJvbGUuQXJufSDilIIgQWxsb3cgIOKUgiBzdHM6QXNzdW1lUm9sZSDilIIgU2VydmljZTplYzIuYW1hem9uYXdzLmNvbSAgICAg4pSCICAgICAgICAgICDilIJcbiAgLy8g4pSU4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pS04pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSYXG5cbiAgZXhwZWN0KG91dHB1dCkudG9Db250YWluKCcke1NvbWVSb2xlLkFybn0nKTtcbiAgZXhwZWN0KG91dHB1dCkudG9Db250YWluKCdzdHM6QXNzdW1lUm9sZScpO1xuICBleHBlY3Qob3V0cHV0KS50b0NvbnRhaW4oJ2VjMi5hbWF6b25hd3MuY29tJyk7XG59KSk7XG5cbmludGVnVGVzdCgnZmFzdCBkZXBsb3knLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gd2UgYXJlIHVzaW5nIGEgc3RhY2sgd2l0aCBhIG5lc3RlZCBzdGFjayBiZWNhdXNlIENGTiB3aWxsIGFsd2F5cyBhdHRlbXB0IHRvXG4gIC8vIHVwZGF0ZSBhIG5lc3RlZCBzdGFjaywgd2hpY2ggd2lsbCBhbGxvdyB1cyB0byB2ZXJpZnkgdGhhdCB1cGRhdGVzIGFyZSBhY3R1YWxseVxuICAvLyBza2lwcGVkIHVubGVzcyAtLWZvcmNlIGlzIHNwZWNpZmllZC5cbiAgY29uc3Qgc3RhY2tBcm4gPSBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snLCB7IGNhcHR1cmVTdGRlcnI6IGZhbHNlIH0pO1xuICBjb25zdCBjaGFuZ2VTZXQxID0gYXdhaXQgZ2V0TGF0ZXN0Q2hhbmdlU2V0KCk7XG5cbiAgLy8gRGVwbG95IHRoZSBzYW1lIHN0YWNrIGFnYWluLCB0aGVyZSBzaG91bGQgYmUgbm8gbmV3IGNoYW5nZSBzZXQgY3JlYXRlZFxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snKTtcbiAgY29uc3QgY2hhbmdlU2V0MiA9IGF3YWl0IGdldExhdGVzdENoYW5nZVNldCgpO1xuICBleHBlY3QoY2hhbmdlU2V0Mi5DaGFuZ2VTZXRJZCkudG9FcXVhbChjaGFuZ2VTZXQxLkNoYW5nZVNldElkKTtcblxuICAvLyBEZXBsb3kgdGhlIHN0YWNrIGFnYWluIHdpdGggLS1mb3JjZSwgbm93IHdlIHNob3VsZCBjcmVhdGUgYSBjaGFuZ2VzZXRcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ3dpdGgtbmVzdGVkLXN0YWNrJywgeyBvcHRpb25zOiBbJy0tZm9yY2UnXSB9KTtcbiAgY29uc3QgY2hhbmdlU2V0MyA9IGF3YWl0IGdldExhdGVzdENoYW5nZVNldCgpO1xuICBleHBlY3QoY2hhbmdlU2V0My5DaGFuZ2VTZXRJZCkubm90LnRvRXF1YWwoY2hhbmdlU2V0Mi5DaGFuZ2VTZXRJZCk7XG5cbiAgLy8gRGVwbG95IHRoZSBzdGFjayBhZ2FpbiB3aXRoIHRhZ3MsIGV4cGVjdGVkIHRvIGNyZWF0ZSBhIG5ldyBjaGFuZ2VzZXRcbiAgLy8gZXZlbiB0aG91Z2ggdGhlIHJlc291cmNlcyBkaWRuJ3QgY2hhbmdlLlxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnd2l0aC1uZXN0ZWQtc3RhY2snLCB7IG9wdGlvbnM6IFsnLS10YWdzJywgJ2tleT12YWx1ZSddIH0pO1xuICBjb25zdCBjaGFuZ2VTZXQ0ID0gYXdhaXQgZ2V0TGF0ZXN0Q2hhbmdlU2V0KCk7XG4gIGV4cGVjdChjaGFuZ2VTZXQ0LkNoYW5nZVNldElkKS5ub3QudG9FcXVhbChjaGFuZ2VTZXQzLkNoYW5nZVNldElkKTtcblxuICBhc3luYyBmdW5jdGlvbiBnZXRMYXRlc3RDaGFuZ2VTZXQoKSB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7IFN0YWNrTmFtZTogc3RhY2tBcm4gfSk7XG4gICAgaWYgKCFyZXNwb25zZS5TdGFja3M/LlswXSkgeyB0aHJvdyBuZXcgRXJyb3IoJ0RpZCBub3QgZ2V0IGEgQ2hhbmdlU2V0IGF0IGFsbCcpOyB9XG4gICAgZml4dHVyZS5sb2coYEZvdW5kIENoYW5nZSBTZXQgJHtyZXNwb25zZS5TdGFja3M/LlswXS5DaGFuZ2VTZXRJZH1gKTtcbiAgICByZXR1cm4gcmVzcG9uc2UuU3RhY2tzPy5bMF07XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdmYWlsZWQgZGVwbG95IGRvZXMgbm90IGhhbmcnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gdGhpcyB3aWxsIGhhbmcgaWYgd2UgaW50cm9kdWNlIGh0dHBzOi8vZ2l0aHViLmNvbS9hd3MvYXdzLWNkay9pc3N1ZXMvNjQwMyBhZ2Fpbi5cbiAgYXdhaXQgZXhwZWN0KGZpeHR1cmUuY2RrRGVwbG95KCdmYWlsZWQnKSkucmVqZWN0cy50b1Rocm93KCdleGl0ZWQgd2l0aCBlcnJvcicpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NhbiBzdGlsbCBsb2FkIG9sZCBhc3NlbWJsaWVzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGN4QXNtRGlyID0gcGF0aC5qb2luKG9zLnRtcGRpcigpLCAnY2RrLWludGVnLWN4Jyk7XG5cbiAgY29uc3QgdGVzdEFzc2VtYmxpZXNEaXJlY3RvcnkgPSBwYXRoLmpvaW4oX19kaXJuYW1lLCAnY2xvdWQtYXNzZW1ibGllcycpO1xuICBmb3IgKGNvbnN0IGFzbWRpciBvZiBhd2FpdCBsaXN0Q2hpbGREaXJzKHRlc3RBc3NlbWJsaWVzRGlyZWN0b3J5KSkge1xuICAgIGZpeHR1cmUubG9nKGBBU1NFTUJMWSAke2FzbWRpcn1gKTtcbiAgICBhd2FpdCBjbG9uZURpcmVjdG9yeShhc21kaXIsIGN4QXNtRGlyKTtcblxuICAgIC8vIFNvbWUgZmlsZXMgaW4gdGhlIGFzbSBkaXJlY3RvcnkgdGhhdCBoYXZlIGEgLmpzIGV4dGVuc2lvbiBhcmVcbiAgICAvLyBhY3R1YWxseSB0cmVhdGVkIGFzIHRlbXBsYXRlcy4gRXZhbHVhdGUgdGhlbSB1c2luZyBOb2RlSlMuXG4gICAgY29uc3QgdGVtcGxhdGVzID0gYXdhaXQgbGlzdENoaWxkcmVuKGN4QXNtRGlyLCBmdWxsUGF0aCA9PiBQcm9taXNlLnJlc29sdmUoZnVsbFBhdGguZW5kc1dpdGgoJy5qcycpKSk7XG4gICAgZm9yIChjb25zdCB0ZW1wbGF0ZSBvZiB0ZW1wbGF0ZXMpIHtcbiAgICAgIGNvbnN0IHRhcmdldE5hbWUgPSB0ZW1wbGF0ZS5yZXBsYWNlKC8uanMkLywgJycpO1xuICAgICAgYXdhaXQgc2hlbGwoW3Byb2Nlc3MuZXhlY1BhdGgsIHRlbXBsYXRlLCAnPicsIHRhcmdldE5hbWVdLCB7XG4gICAgICAgIGN3ZDogY3hBc21EaXIsXG4gICAgICAgIG91dHB1dDogZml4dHVyZS5vdXRwdXQsXG4gICAgICAgIG1vZEVudjoge1xuICAgICAgICAgIFRFU1RfQUNDT1VOVDogYXdhaXQgZml4dHVyZS5hd3MuYWNjb3VudCgpLFxuICAgICAgICAgIFRFU1RfUkVHSU9OOiBmaXh0dXJlLmF3cy5yZWdpb24sXG4gICAgICAgIH0sXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICAvLyBVc2UgdGhpcyBkaXJlY3RvcnkgYXMgYSBDbG91ZCBBc3NlbWJseVxuICAgIGNvbnN0IG91dHB1dCA9IGF3YWl0IGZpeHR1cmUuY2RrKFtcbiAgICAgICctLWFwcCcsIGN4QXNtRGlyLFxuICAgICAgJy12JyxcbiAgICAgICdzeW50aCcsXG4gICAgXSk7XG5cbiAgICAvLyBBc3NlcnQgdGhhdCB0aGVyZSB3YXMgbm8gcHJvdmlkZXJFcnJvciBpbiBDREsncyBzdGRlcnJcbiAgICAvLyBCZWNhdXNlIHdlIHJlbHkgb24gdGhlIGFwcC9mcmFtZXdvcmsgdG8gYWN0dWFsbHkgZXJyb3IgaW4gY2FzZSB0aGVcbiAgICAvLyBwcm92aWRlciBmYWlscywgd2UgaW5zcGVjdCB0aGUgbG9ncyBoZXJlLlxuICAgIGV4cGVjdChvdXRwdXQpLm5vdC50b0NvbnRhaW4oJyRwcm92aWRlckVycm9yJyk7XG4gIH1cbn0pKTtcblxuaW50ZWdUZXN0KCdnZW5lcmF0aW5nIGFuZCBsb2FkaW5nIGFzc2VtYmx5Jywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGFzbU91dHB1dERpciA9IGAke2ZpeHR1cmUuaW50ZWdUZXN0RGlyfS1jZGstaW50ZWctYXNtYDtcbiAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ3JtJywgJy1yZicsIGFzbU91dHB1dERpcl0pO1xuXG4gIC8vIFN5bnRoZXNpemUgYSBDbG91ZCBBc3NlbWJseSB0b3RoZSBkZWZhdWx0IGRpcmVjdG9yeSAoY2RrLm91dCkgYW5kIGEgc3BlY2lmaWMgZGlyZWN0b3J5LlxuICBhd2FpdCBmaXh0dXJlLmNkayhbJ3N5bnRoJ10pO1xuICBhd2FpdCBmaXh0dXJlLmNkayhbJ3N5bnRoJywgJy0tb3V0cHV0JywgYXNtT3V0cHV0RGlyXSk7XG5cbiAgLy8gY2RrLm91dCBpbiB0aGUgY3VycmVudCBkaXJlY3RvcnkgYW5kIHRoZSBpbmRpY2F0ZWQgLS1vdXRwdXQgc2hvdWxkIGJlIHRoZSBzYW1lXG4gIGF3YWl0IGZpeHR1cmUuc2hlbGwoWydkaWZmJywgJ2Nkay5vdXQnLCBhc21PdXRwdXREaXJdKTtcblxuICAvLyBDaGVjayB0aGF0IHdlIGNhbiAnbHMnIHRoZSBzeW50aGVzaXplZCBhc20uXG4gIC8vIENoYW5nZSB0byBzb21lIHJhbmRvbSBkaXJlY3RvcnkgdG8gbWFrZSBzdXJlIHdlJ3JlIG5vdCBhY2NpZGVudGFsbHkgbG9hZGluZyBjZGsuanNvblxuICBjb25zdCBsaXN0ID0gYXdhaXQgZml4dHVyZS5jZGsoWyctLWFwcCcsIGFzbU91dHB1dERpciwgJ2xzJ10sIHsgY3dkOiBvcy50bXBkaXIoKSB9KTtcbiAgLy8gU2FtZSBzdGFja3Mgd2Uga25vdyBhcmUgaW4gdGhlIGFwcFxuICBleHBlY3QobGlzdCkudG9Db250YWluKGAke2ZpeHR1cmUuc3RhY2tOYW1lUHJlZml4fS1sYW1iZGFgKTtcbiAgZXhwZWN0KGxpc3QpLnRvQ29udGFpbihgJHtmaXh0dXJlLnN0YWNrTmFtZVByZWZpeH0tdGVzdC0xYCk7XG4gIGV4cGVjdChsaXN0KS50b0NvbnRhaW4oYCR7Zml4dHVyZS5zdGFja05hbWVQcmVmaXh9LXRlc3QtMmApO1xuXG4gIC8vIENoZWNrIHRoYXQgd2UgY2FuIHVzZSAnLicgYW5kIGp1c3Qgc3ludGggLHRoZSBnZW5lcmF0ZWQgYXNtXG4gIGNvbnN0IHN0YWNrVGVtcGxhdGUgPSBhd2FpdCBmaXh0dXJlLmNkayhbJy0tYXBwJywgJy4nLCAnc3ludGgnLCBmaXh0dXJlLmZ1bGxTdGFja05hbWUoJ3Rlc3QtMicpXSwge1xuICAgIGN3ZDogYXNtT3V0cHV0RGlyLFxuICB9KTtcbiAgZXhwZWN0KHN0YWNrVGVtcGxhdGUpLnRvQ29udGFpbigndG9waWMxNTJEODRBMzcnKTtcblxuICAvLyBEZXBsb3kgYSBMYW1iZGEgZnJvbSB0aGUgY29waWVkIGFzbVxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywgeyBvcHRpb25zOiBbJy1hJywgJy4nXSwgY3dkOiBhc21PdXRwdXREaXIgfSk7XG5cbiAgLy8gUmVtb3ZlIChyZW5hbWUpIHRoZSBvcmlnaW5hbCBjdXN0b20gZG9ja2VyIGZpbGUgdGhhdCB3YXMgdXNlZCBkdXJpbmcgc3ludGguXG4gIC8vIHRoaXMgdmVyaWZpZXMgdGhhdCB0aGUgYXNzZW1seSBoYXMgYSBjb3B5IG9mIGl0IGFuZCB0aGF0IHRoZSBtYW5pZmVzdCB1c2VzXG4gIC8vIHJlbGF0aXZlIHBhdGhzIHRvIHJlZmVyZW5jZSB0byBpdC5cbiAgY29uc3QgY3VzdG9tRG9ja2VyRmlsZSA9IHBhdGguam9pbihmaXh0dXJlLmludGVnVGVzdERpciwgJ2RvY2tlcicsICdEb2NrZXJmaWxlLkN1c3RvbScpO1xuICBhd2FpdCBmcy5yZW5hbWUoY3VzdG9tRG9ja2VyRmlsZSwgYCR7Y3VzdG9tRG9ja2VyRmlsZX1+YCk7XG4gIHRyeSB7XG5cbiAgICAvLyBkZXBsb3kgYSBkb2NrZXIgaW1hZ2Ugd2l0aCBjdXN0b20gZmlsZSB3aXRob3V0IHN5bnRoICh1c2VzIGFzc2V0cylcbiAgICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnZG9ja2VyLXdpdGgtY3VzdG9tLWZpbGUnLCB7IG9wdGlvbnM6IFsnLWEnLCAnLiddLCBjd2Q6IGFzbU91dHB1dERpciB9KTtcblxuICB9IGZpbmFsbHkge1xuICAgIC8vIFJlbmFtZSBiYWNrIHRvIHJlc3RvcmUgZml4dHVyZSB0byBvcmlnaW5hbCBzdGF0ZVxuICAgIGF3YWl0IGZzLnJlbmFtZShgJHtjdXN0b21Eb2NrZXJGaWxlfX5gLCBjdXN0b21Eb2NrZXJGaWxlKTtcbiAgfVxufSkpO1xuXG5pbnRlZ1Rlc3QoJ3RlbXBsYXRlcyBvbiBkaXNrIGNvbnRhaW4gbWV0YWRhdGEgcmVzb3VyY2UsIGFsc28gaW4gbmVzdGVkIGFzc2VtYmxpZXMnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgLy8gU3ludGggZmlyc3QsIGFuZCBzd2l0Y2ggb24gdmVyc2lvbiByZXBvcnRpbmcgYmVjYXVzZSBjZGsuanNvbiBpcyBkaXNhYmxpbmcgaXRcbiAgYXdhaXQgZml4dHVyZS5jZGsoWydzeW50aCcsICctLXZlcnNpb24tcmVwb3J0aW5nPXRydWUnXSk7XG5cbiAgLy8gTG9hZCB0ZW1wbGF0ZSBmcm9tIGRpc2sgZnJvbSByb290IGFzc2VtYmx5XG4gIGNvbnN0IHRlbXBsYXRlQ29udGVudHMgPSBhd2FpdCBmaXh0dXJlLnNoZWxsKFsnY2F0JywgJ2Nkay5vdXQvKi1sYW1iZGEudGVtcGxhdGUuanNvbiddKTtcblxuICBleHBlY3QoSlNPTi5wYXJzZSh0ZW1wbGF0ZUNvbnRlbnRzKS5SZXNvdXJjZXMuQ0RLTWV0YWRhdGEpLnRvQmVUcnV0aHkoKTtcblxuICAvLyBMb2FkIHRlbXBsYXRlIGZyb20gbmVzdGVkIGFzc2VtYmx5XG4gIGNvbnN0IG5lc3RlZFRlbXBsYXRlQ29udGVudHMgPSBhd2FpdCBmaXh0dXJlLnNoZWxsKFsnY2F0JywgJ2Nkay5vdXQvYXNzZW1ibHktKi1zdGFnZS8qLXN0YWdlLVN0YWNrSW5TdGFnZS50ZW1wbGF0ZS5qc29uJ10pO1xuXG4gIGV4cGVjdChKU09OLnBhcnNlKG5lc3RlZFRlbXBsYXRlQ29udGVudHMpLlJlc291cmNlcy5DREtNZXRhZGF0YSkudG9CZVRydXRoeSgpO1xufSkpO1xuXG5hc3luYyBmdW5jdGlvbiBsaXN0Q2hpbGRyZW4ocGFyZW50OiBzdHJpbmcsIHByZWQ6ICh4OiBzdHJpbmcpID0+IFByb21pc2U8Ym9vbGVhbj4pIHtcbiAgY29uc3QgcmV0ID0gbmV3IEFycmF5PHN0cmluZz4oKTtcbiAgZm9yIChjb25zdCBjaGlsZCBvZiBhd2FpdCBmcy5yZWFkZGlyKHBhcmVudCwgeyBlbmNvZGluZzogJ3V0Zi04JyB9KSkge1xuICAgIGNvbnN0IGZ1bGxQYXRoID0gcGF0aC5qb2luKHBhcmVudCwgY2hpbGQudG9TdHJpbmcoKSk7XG4gICAgaWYgKGF3YWl0IHByZWQoZnVsbFBhdGgpKSB7XG4gICAgICByZXQucHVzaChmdWxsUGF0aCk7XG4gICAgfVxuICB9XG4gIHJldHVybiByZXQ7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGxpc3RDaGlsZERpcnMocGFyZW50OiBzdHJpbmcpIHtcbiAgcmV0dXJuIGxpc3RDaGlsZHJlbihwYXJlbnQsIGFzeW5jIChmdWxsUGF0aDogc3RyaW5nKSA9PiAoYXdhaXQgZnMuc3RhdChmdWxsUGF0aCkpLmlzRGlyZWN0b3J5KCkpO1xufVxuIl19 \ No newline at end of file From 6a24026f30ee6af3c1778195b2db8b61537e3296 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 25 Sep 2020 17:08:16 -0700 Subject: [PATCH 17/46] fix(cfn-include): Fn::GetAtt with a string argument fails to include (#10546) As it turns out, `Fn::GetAtt` can be passed a string argument not only in YAML, but in JSON CloudFormation templates as well. Handle that case in our template parser for `cfn-include`. This handling allows us to stop special-casing transforming the short-form `!GetAtt` in our YAML parsing. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test-templates/get-att-string-form.json | 15 ++++++ .../test/valid-templates.test.ts | 8 +++ .../test/yaml-templates.test.ts | 4 +- packages/@aws-cdk/core/lib/cfn-parse.ts | 30 +++++++++--- .../core/lib/private/cfn-reference.ts | 49 +++++++++++++++---- packages/@aws-cdk/yaml-cfn/lib/yaml.ts | 38 ++------------ 6 files changed, 93 insertions(+), 51 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/get-att-string-form.json diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/get-att-string-form.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/get-att-string-form.json new file mode 100644 index 0000000000000..c76fc888ba6a6 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/get-att-string-form.json @@ -0,0 +1,15 @@ +{ + "Resources": { + "Bucket1": { + "Type": "AWS::S3::Bucket" + }, + "Bucket2": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "Bucket1Arn": { + "Fn::GetAtt": "Bucket1.Arn" + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 7394ada485266..3072ca6844214 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -232,6 +232,14 @@ describe('CDK Include', () => { ); }); + test('can ingest a JSON template with string-form Fn::GetAtt, and output it unchanged', () => { + includeTestTemplate(stack, 'get-att-string-form.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('get-att-string-form.json'), + ); + }); + test('can ingest a template with Fn::Sub in string form with escaped and unescaped references and output it unchanged', () => { includeTestTemplate(stack, 'fn-sub-string.json'); diff --git a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts index 84f3fb43ab4bc..664b558841832 100644 --- a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts @@ -140,7 +140,7 @@ describe('CDK Include', () => { "Bucket1": { "Type": "AWS::S3::Bucket", "Properties": { - "BucketName": { "Fn::GetAtt": ["Bucket0", "Arn"] }, + "BucketName": { "Fn::GetAtt": "Bucket0.Arn" }, "AccessControl": { "Fn::GetAtt": ["ELB", "SourceSecurityGroup.GroupName"] }, }, }, @@ -148,7 +148,7 @@ describe('CDK Include', () => { "Type": "AWS::S3::Bucket", "Properties": { "BucketName": { "Fn::GetAtt": ["Bucket1", "Arn"] }, - "AccessControl": { "Fn::GetAtt": ["ELB", "SourceSecurityGroup.GroupName"] }, + "AccessControl": { "Fn::GetAtt": "ELB.SourceSecurityGroup.GroupName" }, }, }, }, diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 7a18378556b9d..b226f9e1472cb 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -10,7 +10,7 @@ import { } from './cfn-resource-policy'; import { CfnTag } from './cfn-tag'; import { Lazy } from './lazy'; -import { CfnReference } from './private/cfn-reference'; +import { CfnReference, ReferenceRendering } from './private/cfn-reference'; import { IResolvable } from './resolvable'; import { Mapper, Validator } from './runtime'; import { isResolvableObject, Token } from './token'; @@ -457,13 +457,29 @@ export class CfnParser { } } case 'Fn::GetAtt': { - // Fn::GetAtt takes a 2-element list as its argument const value = object[key]; - const target = this.finder.findResource(value[0]); + let logicalId: string, attributeName: string, stringForm: boolean; + // Fn::GetAtt takes as arguments either a string... + if (typeof value === 'string') { + // ...in which case the logical ID and the attribute name are separated with '.' + const dotIndex = value.indexOf('.'); + if (dotIndex === -1) { + throw new Error(`Short-form Fn::GetAtt must contain a '.' in its string argument, got: '${value}'`); + } + logicalId = value.substr(0, dotIndex); + attributeName = value.substr(dotIndex + 1); // the +1 is to skip the actual '.' + stringForm = true; + } else { + // ...or a 2-element list + logicalId = value[0]; + attributeName = value[1]; + stringForm = false; + } + const target = this.finder.findResource(logicalId); if (!target) { - throw new Error(`Resource used in GetAtt expression with logical ID: '${value[0]}' not found`); + throw new Error(`Resource used in GetAtt expression with logical ID: '${logicalId}' not found`); } - return target.getAtt(value[1]); + return CfnReference.for(target, attributeName, stringForm ? ReferenceRendering.GET_ATT_STRING : undefined); } case 'Fn::Join': { // Fn::Join takes a 2-element list as its argument, @@ -618,7 +634,7 @@ export class CfnParser { if (!refElement) { throw new Error(`Element referenced in Fn::Sub expression with logical ID: '${refTarget}' was not found in the template`); } - return leftHalf + CfnReference.for(refElement, 'Ref', true).toString() + this.parseFnSubString(rightHalf, map); + return leftHalf + CfnReference.for(refElement, 'Ref', ReferenceRendering.FN_SUB).toString() + this.parseFnSubString(rightHalf, map); } else { const targetId = refTarget.substring(0, dotIndex); const refResource = this.finder.findResource(targetId); @@ -626,7 +642,7 @@ export class CfnParser { throw new Error(`Resource referenced in Fn::Sub expression with logical ID: '${targetId}' was not found in the template`); } const attribute = refTarget.substring(dotIndex + 1); - return leftHalf + CfnReference.for(refResource, attribute, true).toString() + this.parseFnSubString(rightHalf, map); + return leftHalf + CfnReference.for(refResource, attribute, ReferenceRendering.FN_SUB).toString() + this.parseFnSubString(rightHalf, map); } } diff --git a/packages/@aws-cdk/core/lib/private/cfn-reference.ts b/packages/@aws-cdk/core/lib/private/cfn-reference.ts index 201b5bebfa7d3..b25597602f6b6 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-reference.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-reference.ts @@ -2,6 +2,24 @@ import { Reference } from '../reference'; const CFN_REFERENCE_SYMBOL = Symbol.for('@aws-cdk/core.CfnReference'); +/** + * An enum that allows controlling how will the created reference + * be rendered in the resulting CloudFormation template. + */ +export enum ReferenceRendering { + /** + * Used for rendering a reference inside Fn::Sub expressions, + * which mean these must resolve to "${Sth}" instead of { Ref: "Sth" }. + */ + FN_SUB, + + /** + * Used for rendering Fn::GetAtt with its arguments in string form + * (as opposed to the more common arguments in array form, which we render by default). + */ + GET_ATT_STRING, +} + /** * A Token that represents a CloudFormation reference to another resource * @@ -34,14 +52,19 @@ export class CfnReference extends Reference { * * Lazy.stringValue({ produce: () => new CfnReference(...) }) * - * If fnSub is true, then this reference will resolve as ${logicalID}. - * This allows cloudformation-include to correctly handle Fn::Sub. */ - public static for(target: CfnElement, attribute: string, fnSub: boolean = false) { - return CfnReference.singletonReference(target, attribute, fnSub, () => { - const cfnIntrinsic = fnSub + public static for(target: CfnElement, attribute: string, refRender?: ReferenceRendering) { + return CfnReference.singletonReference(target, attribute, refRender, () => { + const cfnIntrinsic = refRender === ReferenceRendering.FN_SUB ? ('${' + target.logicalId + (attribute === 'Ref' ? '' : `.${attribute}`) + '}') - : (attribute === 'Ref' ? { Ref: target.logicalId } : { 'Fn::GetAtt': [target.logicalId, attribute] }); + : (attribute === 'Ref' + ? { Ref: target.logicalId } + : { + 'Fn::GetAtt': refRender === ReferenceRendering.GET_ATT_STRING + ? `${target.logicalId}.${attribute}` + : [target.logicalId, attribute], + } + ); return new CfnReference(cfnIntrinsic, attribute, target); }); } @@ -50,7 +73,7 @@ export class CfnReference extends Reference { * Return a CfnReference that references a pseudo referencd */ public static forPseudo(pseudoName: string, scope: Construct) { - return CfnReference.singletonReference(scope, `Pseudo:${pseudoName}`, false, () => { + return CfnReference.singletonReference(scope, `Pseudo:${pseudoName}`, undefined, () => { const cfnIntrinsic = { Ref: pseudoName }; return new CfnReference(cfnIntrinsic, pseudoName, scope); }); @@ -65,13 +88,21 @@ export class CfnReference extends Reference { * Get or create the table. * Passing fnSub = true allows cloudformation-include to correctly handle Fn::Sub. */ - private static singletonReference(target: Construct, attribKey: string, fnSub: boolean, fresh: () => CfnReference) { + private static singletonReference(target: Construct, attribKey: string, refRender: ReferenceRendering | undefined, fresh: () => CfnReference) { let attribs = CfnReference.referenceTable.get(target); if (!attribs) { attribs = new Map(); CfnReference.referenceTable.set(target, attribs); } - const cacheKey = attribKey + (fnSub ? 'Fn::Sub' : ''); + let cacheKey = attribKey; + switch (refRender) { + case ReferenceRendering.FN_SUB: + cacheKey += 'Fn::Sub'; + break; + case ReferenceRendering.GET_ATT_STRING: + cacheKey += 'Fn::GetAtt::String'; + break; + } let ref = attribs.get(cacheKey); if (!ref) { ref = fresh(); diff --git a/packages/@aws-cdk/yaml-cfn/lib/yaml.ts b/packages/@aws-cdk/yaml-cfn/lib/yaml.ts index 0a613f5aa7e14..eca37db0ed048 100644 --- a/packages/@aws-cdk/yaml-cfn/lib/yaml.ts +++ b/packages/@aws-cdk/yaml-cfn/lib/yaml.ts @@ -29,54 +29,26 @@ export function deserialize(str: string): any { return parseYamlStrWithCfnTags(str); } -function makeTagForCfnIntrinsic( - intrinsicName: string, addFnPrefix: boolean = true, - resolveFun?: (_doc: yaml.Document, cstNode: yaml_cst.CST.Node) => any): yaml_types.Schema.CustomTag { - +function makeTagForCfnIntrinsic(intrinsicName: string, addFnPrefix: boolean): yaml_types.Schema.CustomTag { return { identify(value: any) { return typeof value === 'string'; }, tag: `!${intrinsicName}`, - resolve: resolveFun || ((_doc: yaml.Document, cstNode: yaml_cst.CST.Node) => { + resolve: (_doc: yaml.Document, cstNode: yaml_cst.CST.Node) => { const ret: any = {}; ret[addFnPrefix ? `Fn::${intrinsicName}` : intrinsicName] = // the +1 is to account for the ! the short form begins with parseYamlStrWithCfnTags(cstNode.toString().substring(intrinsicName.length + 1)); return ret; - }), + }, }; } const shortForms: yaml_types.Schema.CustomTag[] = [ 'Base64', 'Cidr', 'FindInMap', 'GetAZs', 'ImportValue', 'Join', 'Sub', - 'Select', 'Split', 'Transform', 'And', 'Equals', 'If', 'Not', 'Or', -].map(name => makeTagForCfnIntrinsic(name)).concat( + 'Select', 'Split', 'Transform', 'And', 'Equals', 'If', 'Not', 'Or', 'GetAtt', +].map(name => makeTagForCfnIntrinsic(name, true)).concat( makeTagForCfnIntrinsic('Ref', false), makeTagForCfnIntrinsic('Condition', false), - makeTagForCfnIntrinsic('GetAtt', true, (_doc: yaml.Document, cstNode: yaml_cst.CST.Node): any => { - const parsedArguments = parseYamlStrWithCfnTags(cstNode.toString().substring('!GetAtt'.length)); - - let value: any; - if (typeof parsedArguments === 'string') { - // if the arguments to !GetAtt are a string, - // the part before the first '.' is the logical ID, - // and the rest is the attribute name - // (which can contain '.') - const firstDot = parsedArguments.indexOf('.'); - if (firstDot === -1) { - throw new Error(`Short-form Fn::GetAtt must contain a '.' in its string argument, got: '${parsedArguments}'`); - } - value = [ - parsedArguments.substring(0, firstDot), - parsedArguments.substring(firstDot + 1), // the + 1 is to skip the actual '.' - ]; - } else { - // this is the form where the arguments to Fn::GetAtt are already an array - - // in this case, nothing more to do - value = parsedArguments; - } - - return { 'Fn::GetAtt': value }; - }), ); function parseYamlStrWithCfnTags(text: string): any { From d68ce2f4b42099064342baeb4b494810aa362e27 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 25 Sep 2020 17:41:55 -0700 Subject: [PATCH 18/46] feat: support the 'Description' resource attribute (#10522) One more resource attribute that we missed, and that is needed for cfn-include to be able to handle ingesting all templates. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/cloudformation-include/lib/cfn-include.ts | 4 ++-- .../test-templates/custom-resource-with-attributes.json | 1 + packages/@aws-cdk/core/lib/cfn-parse.ts | 1 + packages/@aws-cdk/core/lib/cfn-resource.ts | 9 +++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index aad5c653c3693..becb5666c3653 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -589,8 +589,8 @@ export class CfnInclude extends core.CfnElement { // fail early for resource attributes we don't support yet const knownAttributes = [ - 'Type', 'Properties', 'Condition', 'DependsOn', 'Metadata', 'Version', - 'CreationPolicy', 'UpdatePolicy', 'DeletionPolicy', 'UpdateReplacePolicy', + 'Condition', 'DependsOn', 'Description', 'Metadata', 'Properties', 'Type', 'Version', + 'CreationPolicy', 'DeletionPolicy', 'UpdatePolicy', 'UpdateReplacePolicy', ]; for (const attribute of Object.keys(resourceAttributes)) { if (!knownAttributes.includes(attribute)) { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/custom-resource-with-attributes.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/custom-resource-with-attributes.json index c490a16515944..b1dcf4d219bf1 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/custom-resource-with-attributes.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/custom-resource-with-attributes.json @@ -28,6 +28,7 @@ }, "CustomResource": { "Type": "AWS::CloudFormation::CustomResource", + "Description": "some random custom resource", "Properties": { "ServiceToken": "CustomValue", "CustomFuncProp": { diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index b226f9e1472cb..886a9228b3d0a 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -285,6 +285,7 @@ export class CfnParser { cfnOptions.deletionPolicy = this.parseDeletionPolicy(resourceAttributes.DeletionPolicy); cfnOptions.updateReplacePolicy = this.parseDeletionPolicy(resourceAttributes.UpdateReplacePolicy); cfnOptions.version = this.parseValue(resourceAttributes.Version); + cfnOptions.description = this.parseValue(resourceAttributes.Description); cfnOptions.metadata = this.parseValue(resourceAttributes.Metadata); // handle Condition diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index fcb1c95eaf5bb..f5049bdcd2326 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -301,6 +301,7 @@ export class CfnResource extends CfnRefElement { UpdateReplacePolicy: capitalizePropertyNames(this, this.cfnOptions.updateReplacePolicy), DeletionPolicy: capitalizePropertyNames(this, this.cfnOptions.deletionPolicy), Version: this.cfnOptions.version, + Description: this.cfnOptions.description, Metadata: ignoreEmpty(this.cfnOptions.metadata), Condition: this.cfnOptions.condition && this.cfnOptions.condition.logicalId, }, props => { @@ -438,6 +439,14 @@ export interface ICfnResourceOptions { */ version?: string; + /** + * The description of this resource. + * Used for informational purposes only, is not processed in any way + * (and stays with the CloudFormation template, is not passed to the underlying resource, + * even if it does have a 'description' property). + */ + description?: string; + /** * Metadata associated with the CloudFormation resource. This is not the same as the construct metadata which can be added * using construct.addMetadata(), but would not appear in the CloudFormation template automatically. From 4aadaa779b48f35838cccd4e25107b2338f05547 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 28 Sep 2020 10:53:09 +0200 Subject: [PATCH 19/46] fix(codebuild): `encryptionKey` cannot be disabled (#10474) Because of a limitation of the CodeBuild Service API and its CloudFormation implementation, it is not possible to disable the encryption key used to encrypt uploaded artifacts after having deployed a Project once that uses a key (the update back to "no key" is ignored). Work around this by explicitly always selecting the default `alias/aws/s3` key. This is the same one CodeBuild would have used if no key was given, except it doesn't suffer from the "property cannot be made empty" problem. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-codebuild/lib/project.ts | 4 +++- ...earning-container-build-image.expected.json | 3 ++- .../test/integ.caching.expected.json | 3 ++- .../test/integ.defaults.lit.expected.json | 5 +++-- .../test/integ.docker-asset.lit.expected.json | 3 ++- .../integ.docker-registry.lit.expected.json | 5 +++-- .../test/integ.ecr.lit.expected.json | 3 ++- .../test/integ.github.expected.json | 3 ++- .../test/integ.project-bucket.expected.json | 3 ++- ...g.project-buildspec-artifacts.expected.json | 14 ++++++++------ ....project-file-system-location.expected.json | 1 + ...t-secondary-sources-artifacts.expected.json | 1 + .../test/integ.project-vpc.expected.json | 1 + .../aws-codebuild/test/test.codebuild.ts | 18 ++++++++++++++++++ ...build-multiple-inputs-outputs.expected.json | 5 +++-- .../integ.pipeline-ecs-deploy.expected.json | 5 +++-- .../integ.project-events.expected.json | 3 ++- .../codebuild/integ.start-build.expected.json | 5 +++-- 18 files changed, 61 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 4c1ec9ca6b752..abf61ea06be86 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -753,7 +753,9 @@ export class Project extends ProjectBase { environment: this.renderEnvironment(props.environment, environmentVariables), fileSystemLocations: Lazy.anyValue({ produce: () => this.renderFileSystemLocations() }), // lazy, because we have a setter for it in setEncryptionKey - encryptionKey: Lazy.stringValue({ produce: () => this._encryptionKey && this._encryptionKey.keyArn }), + // The 'alias/aws/s3' default is necessary because leaving the `encryptionKey` field + // empty will not remove existing encryptionKeys during an update (ref. t/D17810523) + encryptionKey: Lazy.stringValue({ produce: () => this._encryptionKey ? this._encryptionKey.keyArn : 'alias/aws/s3' }), badgeEnabled: props.badge, cache: cache._toCloudFormation(), name: this.physicalName, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json index 2c91da398b0a7..6eb429e6cb569 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json @@ -208,7 +208,8 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json index 738924ce04b93..79f9c50c2e5a7 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json @@ -165,7 +165,8 @@ ] }, "Type": "S3" - } + }, + "EncryptionKey": "alias/aws/s3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json index aae808b132920..c7162f3219c85 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json @@ -144,8 +144,9 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"echo \\\"Hello, CodeBuild!\\\"\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json index 561aeb8f22f9a..64ff7f68d7f8c 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json @@ -163,7 +163,8 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json index efaee26898636..ca3d5bfef0619 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json @@ -154,8 +154,9 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json index c92a8b6c2deba..12ca8fff1853b 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json @@ -184,7 +184,8 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json index 017456796ac69..222a29feeeb8a 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json @@ -112,7 +112,8 @@ "Location": "https://github.com/aws/aws-cdk.git", "ReportBuildStatus": false, "Type": "GITHUB" - } + }, + "EncryptionKey": "alias/aws/s3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json index 3c3262ad23747..0ba33dae84b91 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json @@ -156,7 +156,8 @@ ] }, "Type": "S3" - } + }, + "EncryptionKey": "alias/aws/s3" } } } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json index fff6b92d11f44..13ff8f1e054c0 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json @@ -1,9 +1,9 @@ { "Resources": { "MyBucketF68F3FF0": { - "DeletionPolicy": "Delete", + "Type": "AWS::S3::Bucket", "UpdateReplacePolicy": "Delete", - "Type": "AWS::S3::Bucket" + "DeletionPolicy": "Delete" }, "MyProjectRole9BBE5233": { "Type": "AWS::IAM::Role", @@ -133,7 +133,9 @@ "Properties": { "Artifacts": { "ArtifactIdentifier": "AddArtifact1", - "Location": { "Ref": "MyBucketF68F3FF0" }, + "Location": { + "Ref": "MyBucketF68F3FF0" + }, "NamespaceType": "NONE", "OverrideArtifactName": true, "Packaging": "ZIP", @@ -155,9 +157,9 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\"\n}", "Type": "NO_SOURCE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } -} - +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json index c23bcab0e013a..ef679f0b9fb7a 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json @@ -371,6 +371,7 @@ "BuildSpec": "{\n \"version\": \"0.2\"\n}", "Type": "NO_SOURCE" }, + "EncryptionKey": "alias/aws/s3", "FileSystemLocations": [ { "Identifier": "myidentifier", diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json index 26a3636bfc5b3..f7261ca4b21ae 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json @@ -180,6 +180,7 @@ "BuildSpec": "{\n \"version\": \"0.2\"\n}", "Type": "NO_SOURCE" }, + "EncryptionKey": "alias/aws/s3", "SecondaryArtifacts": [ { "ArtifactIdentifier": "AddArtifact1", diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json index 2e46025573d0b..6d3eb3091b7ed 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json @@ -371,6 +371,7 @@ "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"echo \\\"Nothing to do!\\\"\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" }, + "EncryptionKey": "alias/aws/s3", "VpcConfig": { "SecurityGroupIds": [ { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 8a5a9decfc1e2..558dd072c7f3f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -153,6 +153,7 @@ export = { 'Image': 'aws/codebuild/standard:1.0', 'ComputeType': 'BUILD_GENERAL1_SMALL', }, + 'EncryptionKey': 'alias/aws/s3', }, }, }, @@ -331,6 +332,7 @@ export = { 'GitCloneDepth': 2, 'Type': 'CODECOMMIT', }, + 'EncryptionKey': 'alias/aws/s3', }, }, }, @@ -532,6 +534,7 @@ export = { }, 'Type': 'S3', }, + 'EncryptionKey': 'alias/aws/s3', }, }, }, @@ -800,6 +803,21 @@ export = { test.done(); }, + 'no KMS Key defaults to default S3 managed key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'MyProject'); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + EncryptionKey: 'alias/aws/s3', + })); + + test.done(); + }, + 'with a KMS Key adds decrypt permissions to the CodeBuild Role'(test: Test) { const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json index 0b47b23b12a50..d2d59b1fe8884 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json @@ -614,8 +614,9 @@ }, "Source": { "Type": "CODEPIPELINE" - } + }, + "EncryptionKey": "alias/aws/s3" } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json index 34a425b48a1a6..5570acefe6336 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json @@ -540,7 +540,8 @@ "Source": { "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": \"$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)\"\n },\n \"build\": {\n \"commands\": \"docker build -t $REPOSITORY_URI:latest .\"\n },\n \"post_build\": {\n \"commands\": [\n \"docker push $REPOSITORY_URI:latest\",\n \"printf '[{ \\\"name\\\": \\\"Container\\\", \\\"imageUri\\\": \\\"%s\\\" }]' $REPOSITORY_URI:latest > imagedefinitions.json\"\n ]\n }\n },\n \"artifacts\": {\n \"files\": \"imagedefinitions.json\"\n }\n}", "Type": "CODEPIPELINE" - } + }, + "EncryptionKey": "alias/aws/s3" } }, "MyPipelineRoleC0D47CA4": { @@ -1041,4 +1042,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index 178bba14bbc1c..ed269b2e7b524 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -226,7 +226,8 @@ ] }, "Type": "CODECOMMIT" - } + }, + "EncryptionKey": "alias/aws/s3" } }, "MyProjectStateChange2DAB75B7": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json index a3a245b0ddfd8..360eadea837cb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json @@ -152,7 +152,8 @@ "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"echo \\\"Hello, CodeBuild!\\\"\"\n ]\n }\n }\n}", "Type": "NO_SOURCE" }, - "Name": "MyTestProject" + "Name": "MyTestProject", + "EncryptionKey": "alias/aws/s3" } }, "StateMachineRoleB840431D": { @@ -258,4 +259,4 @@ } } } -} \ No newline at end of file +} From 350105a7f27b6f7fafb5e5dd7f6b3a81a1f5baae Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 28 Sep 2020 11:20:41 +0100 Subject: [PATCH 20/46] fix(secretsmanager): cannot import secrets if ARN is a token (#10568) The feature to support importing secrets by name (#10309) failed to handle scenarios where the secret ARN is a token, due to parsing the ARN to retrieve the secret name. fixes #10520 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 9 +++++-- .../aws-secretsmanager/test/test.secret.ts | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index c4539cca154ea..1e6bf8d2441a5 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Construct, IConstruct, IResource, RemovalPolicy, Resource, SecretValue, Stack } from '@aws-cdk/core'; +import { Construct, IConstruct, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; import { ResourcePolicy } from './policy'; import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule'; import * as secretsmanager from './secretsmanager.generated'; @@ -596,8 +596,13 @@ export interface SecretStringGenerator { /** Parses the secret name from the ARN. */ function parseSecretName(construct: IConstruct, secretArn: string) { - const resourceName = Stack.of(construct).parseArn(secretArn).resourceName; + const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName; if (resourceName) { + // Can't operate on the token to remove the SecretsManager suffix, so just return the full secret name + if (Token.isUnresolved(resourceName)) { + return resourceName; + } + // Secret resource names are in the format `${secretName}-${SecretsManager suffix}` const secretNameFromArn = resourceName.substr(0, resourceName.lastIndexOf('-')); if (secretNameFromArn) { return secretNameFromArn; } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 1b7c1e3063ed9..523ce501b9126 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -482,6 +482,30 @@ export = { test.done(); }, + 'import by secretArn supports tokens for ARNs'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stackA = new cdk.Stack(app, 'StackA'); + const stackB = new cdk.Stack(app, 'StackB'); + const secretA = new secretsmanager.Secret(stackA, 'SecretA'); + + // WHEN + const secretB = secretsmanager.Secret.fromSecretArn(stackB, 'SecretB', secretA.secretArn); + new cdk.CfnOutput(stackB, 'secretBSecretName', { value: secretB.secretName }); + + // THEN + test.equals(secretB.secretArn, secretA.secretArn); + expect(stackB).toMatch({ + Outputs: { + secretBSecretName: { + Value: { 'Fn::Select': [6, { 'Fn::Split': [':', { 'Fn::ImportValue': 'StackA:ExportsOutputRefSecretA188F281703FC8A52' }] }] }, + }, + }, + }); + + test.done(); + }, + 'import by attributes'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 97bfd10fef4c23404ec3f5c6a6ca68604fc78aa8 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 28 Sep 2020 16:01:59 +0200 Subject: [PATCH 21/46] fix(pipelines): stack tags (#10533) Apply stack tags to the stacks deployed using CDK Pipelines. Taking this opportunity to make tags easier to work with -- move them from metadata into cloud artifact properties. Fixes #9260. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/cloud-assembly/artifact-schema.ts | 7 + .../lib/cloud-assembly/metadata-schema.ts | 8 ++ .../cloud-assembly-schema/lib/manifest.ts | 87 +++++++++--- .../schema/cloud-assembly.schema.json | 7 + .../schema/cloud-assembly.version.json | 2 +- .../core/lib/stack-synthesizers/_shared.ts | 6 +- packages/@aws-cdk/core/lib/tag-manager.ts | 18 ++- packages/@aws-cdk/core/test/test.stack.ts | 20 ++- .../lib/artifacts/cloudformation-artifact.ts | 23 ++- .../cx-api/test/stack-artifact.test.ts | 132 ++++++++++++++++++ packages/@aws-cdk/cx-api/test/util.ts | 23 +++ .../lib/actions/deploy-cdk-stack-action.ts | 46 +++++- .../@aws-cdk/pipelines/test/pipeline.test.ts | 40 +++++- packages/aws-cdk/lib/cdk-toolkit.ts | 17 +-- 14 files changed, 390 insertions(+), 46 deletions(-) create mode 100644 packages/@aws-cdk/cx-api/test/stack-artifact.test.ts create mode 100644 packages/@aws-cdk/cx-api/test/util.ts diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts index 6a43b187475f5..cea67ab76263d 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts @@ -15,6 +15,13 @@ export interface AwsCloudFormationStackProperties { */ readonly parameters?: { [id: string]: string }; + /** + * Values for CloudFormation stack tags that should be passed when the stack is deployed. + * + * @default - No tags + */ + readonly tags?: { [id: string]: string }; + /** * The name to use for the CloudFormation stack. * @default - name derived from artifact ID diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts index 54cabf83554fc..5e9e4e1517145 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/metadata-schema.ts @@ -54,11 +54,19 @@ export interface FileAssetMetadataEntry extends BaseAssetMetadataEntry { export interface Tag { /** * Tag key. + * + * (In the actual file on disk this will be cased as "Key", and the structure is + * patched to match this structure upon loading: + * https://github.com/aws/aws-cdk/blob/4aadaa779b48f35838cccd4e25107b2338f05547/packages/%40aws-cdk/cloud-assembly-schema/lib/manifest.ts#L137) */ readonly key: string /** * Tag value. + * + * (In the actual file on disk this will be cased as "Value", and the structure is + * patched to match this structure upon loading: + * https://github.com/aws/aws-cdk/blob/4aadaa779b48f35838cccd4e25107b2338f05547/packages/%40aws-cdk/cloud-assembly-schema/lib/manifest.ts#L137) */ readonly value: string } diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts index 4c7c5229f0fc0..1e3ea7afa62e7 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -32,7 +32,7 @@ export class Manifest { * @param filePath - output file path. */ public static saveAssemblyManifest(manifest: assembly.AssemblyManifest, filePath: string) { - Manifest.saveManifest(manifest, filePath, ASSEMBLY_SCHEMA); + Manifest.saveManifest(manifest, filePath, ASSEMBLY_SCHEMA, Manifest.patchStackTagsOnWrite); } /** @@ -41,7 +41,7 @@ export class Manifest { * @param filePath - path to the manifest file. */ public static loadAssemblyManifest(filePath: string): assembly.AssemblyManifest { - return Manifest.loadManifest(filePath, ASSEMBLY_SCHEMA, obj => Manifest.patchStackTags(obj)); + return Manifest.loadManifest(filePath, ASSEMBLY_SCHEMA, Manifest.patchStackTagsOnRead); } /** @@ -51,7 +51,7 @@ export class Manifest { * @param filePath - output file path. */ public static saveAssetManifest(manifest: assets.AssetManifest, filePath: string) { - Manifest.saveManifest(manifest, filePath, ASSETS_SCHEMA); + Manifest.saveManifest(manifest, filePath, ASSETS_SCHEMA, Manifest.patchStackTagsOnRead); } /** @@ -118,9 +118,12 @@ export class Manifest { } - private static saveManifest(manifest: any, filePath: string, schema: jsonschema.Schema) { - const withVersion = { ...manifest, version: Manifest.version() }; + private static saveManifest(manifest: any, filePath: string, schema: jsonschema.Schema, preprocess?: (obj: any) => any) { + let withVersion = { ...manifest, version: Manifest.version() }; Manifest.validate(withVersion, schema); + if (preprocess) { + withVersion = preprocess(withVersion); + } fs.writeFileSync(filePath, JSON.stringify(withVersion, undefined, 2)); } @@ -148,23 +151,69 @@ export class Manifest { * Ideally, we would start writing the `camelCased` and translate to how CloudFormation expects it when needed. But this requires nasty * backwards-compatibility code and it just doesn't seem to be worth the effort. */ - private static patchStackTags(manifest: assembly.AssemblyManifest) { - for (const artifact of Object.values(manifest.artifacts || [])) { - if (artifact.type === assembly.ArtifactType.AWS_CLOUDFORMATION_STACK) { - for (const metadataEntries of Object.values(artifact.metadata || [])) { - for (const metadataEntry of metadataEntries) { - if (metadataEntry.type === assembly.ArtifactMetadataEntryType.STACK_TAGS && metadataEntry.data) { - const metadataAny = metadataEntry as any; - metadataAny.data = metadataAny.data.map((t: any) => ({ key: t.Key, value: t.Value })); - } - } - } - } - } + private static patchStackTagsOnRead(manifest: assembly.AssemblyManifest) { + return Manifest.replaceStackTags(manifest, tags => tags.map((diskTag: any) => ({ + key: diskTag.Key, + value: diskTag.Value, + }))); + } + + /** + * See explanation on `patchStackTagsOnRead` + * + * Translate stack tags metadata if it has the "right" casing. + */ + private static patchStackTagsOnWrite(manifest: assembly.AssemblyManifest) { + return Manifest.replaceStackTags(manifest, tags => tags.map(memTag => + // Might already be uppercased (because stack synthesis generates it in final form yet) + ('Key' in memTag ? memTag : { Key: memTag.key, Value: memTag.value }) as any, + )); + } - return manifest; + /** + * Recursively replace stack tags in the stack metadata + */ + private static replaceStackTags(manifest: assembly.AssemblyManifest, fn: Endofunctor): assembly.AssemblyManifest { + // Need to add in the `noUndefined`s because otherwise jest snapshot tests are going to freak out + // about the keys with values that are `undefined` (even though they would never be JSON.stringified) + return noUndefined({ + ...manifest, + artifacts: mapValues(manifest.artifacts, artifact => { + if (artifact.type !== assembly.ArtifactType.AWS_CLOUDFORMATION_STACK) { return artifact; } + return noUndefined({ + ...artifact, + metadata: mapValues(artifact.metadata, metadataEntries => metadataEntries.map(metadataEntry => { + if (metadataEntry.type !== assembly.ArtifactMetadataEntryType.STACK_TAGS || !metadataEntry.data) { return metadataEntry; } + return { + ...metadataEntry, + data: fn(metadataEntry.data as assembly.StackTagsMetadataEntry), + }; + })), + } as assembly.ArtifactManifest); + }), + }); } private constructor() {} +} + +type Endofunctor = (x: A) => A; +function mapValues(xs: Record | undefined, fn: (x: A) => B): Record | undefined { + if (!xs) { return undefined; } + const ret: Record | undefined = {}; + for (const [k, v] of Object.entries(xs)) { + ret[k] = fn(v); + } + return ret; } + +function noUndefined(xs: A): A { + const ret: any = {}; + for (const [k, v] of Object.entries(xs)) { + if (v !== undefined) { + ret[k] = v; + } + } + return ret; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json index 8c3e58485b12b..a154b78a0b508 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -275,6 +275,13 @@ "type": "string" } }, + "tags": { + "description": "Values for CloudFormation stack tags that should be passed when the stack is deployed. (Default - No tags)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "stackName": { "description": "The name to use for the CloudFormation stack. (Default - name derived from artifact ID)", "type": "string" diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json index 78d33700c0698..42cb403235c06 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"5.0.0"} +{"version":"6.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts index 5a57c423779df..233490968b31d 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts @@ -21,7 +21,6 @@ export function addStackArtifactToAssembly( // nested stack tags are applied at the AWS::CloudFormation::Stack resource // level and are not needed in the cloud assembly. - // TODO: move these to the cloud assembly artifact properties instead of metadata if (stack.tags.hasTags()) { stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.STACK_TAGS, stack.tags.renderTags()); } @@ -46,6 +45,7 @@ export function addStackArtifactToAssembly( const properties: cxschema.AwsCloudFormationStackProperties = { templateFile: stack.templateFile, terminationProtection: stack.terminationProtection, + tags: nonEmptyDict(stack.tags.tagValues()), ...stackProps, ...stackNameProperty, }; @@ -116,4 +116,8 @@ export function assertBound(x: A | undefined): asserts x is NonNullable { if (x === null && x === undefined) { throw new Error('You must call bindStack() first'); } +} + +function nonEmptyDict(xs: Record) { + return Object.keys(xs).length > 0 ? xs : undefined; } \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/tag-manager.ts b/packages/@aws-cdk/core/lib/tag-manager.ts index b1db219ef4440..2189af4bc4929 100644 --- a/packages/@aws-cdk/core/lib/tag-manager.ts +++ b/packages/@aws-cdk/core/lib/tag-manager.ts @@ -279,8 +279,18 @@ export class TagManager { * Renders tags into the proper format based on TagType */ public renderTags(): any { - const sortedTags = Array.from(this.tags.values()).sort((a, b) => a.key.localeCompare(b.key)); - return this.tagFormatter.formatTags(sortedTags); + return this.tagFormatter.formatTags(this.sortedTags); + } + + /** + * Render the tags in a readable format + */ + public tagValues(): Record { + const ret: Record = {}; + for (const tag of this.sortedTags) { + ret[tag.key] = tag.value; + } + return ret; } /** @@ -315,4 +325,8 @@ export class TagManager { } } } + + private get sortedTags() { + return Array.from(this.tags.values()).sort((a, b) => a.key.localeCompare(b.key)); + } } diff --git a/packages/@aws-cdk/core/test/test.stack.ts b/packages/@aws-cdk/core/test/test.stack.ts index a27a992b5fdfd..49a7ac671b0ad 100644 --- a/packages/@aws-cdk/core/test/test.stack.ts +++ b/packages/@aws-cdk/core/test/test.stack.ts @@ -897,7 +897,7 @@ export = { test.done(); }, - 'stack tags are reflected in the stack cloud assembly artifact'(test: Test) { + 'stack tags are reflected in the stack cloud assembly artifact metadata'(test: Test) { // GIVEN const app = new App({ stackTraces: false }); const stack1 = new Stack(app, 'stack1'); @@ -920,6 +920,24 @@ export = { test.done(); }, + 'stack tags are reflected in the stack artifact properties'(test: Test) { + // GIVEN + const app = new App({ stackTraces: false }); + const stack1 = new Stack(app, 'stack1'); + const stack2 = new Stack(stack1, 'stack2'); + + // WHEN + Tags.of(app).add('foo', 'bar'); + + // THEN + const asm = app.synth(); + const expected = { foo: 'bar' }; + + test.deepEqual(asm.getStackArtifact(stack1.artifactId).tags, expected); + test.deepEqual(asm.getStackArtifact(stack2.artifactId).tags, expected); + test.done(); + }, + 'Termination Protection is reflected in Cloud Assembly artifact'(test: Test) { // if the root is an app, invoke "synth" to avoid double synthesis const app = new App(); diff --git a/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts b/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts index 167d589753b63..807e58f1a411c 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts/cloudformation-artifact.ts @@ -26,6 +26,11 @@ export class CloudFormationStackArtifact extends CloudArtifact { */ public readonly parameters: { [id: string]: string }; + /** + * CloudFormation tags to pass to the stack. + */ + public readonly tags: { [id: string]: string }; + /** * The physical name of this stack. */ @@ -96,7 +101,11 @@ export class CloudFormationStackArtifact extends CloudArtifact { } this.environment = EnvironmentUtils.parse(artifact.environment); this.templateFile = properties.templateFile; - this.parameters = properties.parameters || { }; + this.parameters = properties.parameters ?? {}; + + // We get the tags from 'properties' if available (cloud assembly format >= 6.0.0), otherwise + // from the stack metadata + this.tags = properties.tags ?? this.tagsFromMetadata(); this.assumeRoleArn = properties.assumeRoleArn; this.cloudFormationExecutionRoleArn = properties.cloudFormationExecutionRoleArn; this.stackTemplateAssetObjectUrl = properties.stackTemplateAssetObjectUrl; @@ -130,4 +139,14 @@ export class CloudFormationStackArtifact extends CloudArtifact { } return this._template; } -} + + private tagsFromMetadata() { + const ret: Record = {}; + for (const metadataEntry of this.findMetadataByType(cxschema.ArtifactMetadataEntryType.STACK_TAGS)) { + for (const tag of (metadataEntry.data ?? []) as cxschema.StackTagsMetadataEntry) { + ret[tag.key] = tag.value; + } + } + return ret; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/stack-artifact.test.ts b/packages/@aws-cdk/cx-api/test/stack-artifact.test.ts new file mode 100644 index 0000000000000..1f4591f00177a --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/stack-artifact.test.ts @@ -0,0 +1,132 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '../lib'; +import { rimraf } from './util'; + +const stackBase = { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: 'aws://1222344/us-east-1', + properties: { + templateFile: 'bla.json', + }, +}; + +let builder: cxapi.CloudAssemblyBuilder; +beforeEach(() => { + builder = new cxapi.CloudAssemblyBuilder(); +}); + +afterEach(() => { + rimraf(builder.outdir); +}); + +test('read tags from artifact properties', () => { + // GIVEN + builder.addArtifact('Stack', { + ...stackBase, + properties: { + ...stackBase.properties, + tags: { + foo: 'bar', + }, + }, + }); + + // WHEN + const assembly = builder.buildAssembly(); + + // THEN + expect(assembly.getStackByName('Stack').tags).toEqual({ foo: 'bar' }); +}); + +test('stack tags get uppercased when written to Cloud Assembly', () => { + // Backwards compatibility test + // GIVEN + builder.addArtifact('Stack', { + ...stackBase, + metadata: { + '/Stack': [ + { + type: 'aws:cdk:stack-tags', + data: [{ key: 'foo', value: 'bar' }], + }, + ], + }, + }); + + // WHEN + const assembly = builder.buildAssembly(); + + // THEN + const manifestStructure = JSON.parse(fs.readFileSync(path.join(assembly.directory, 'manifest.json'), { encoding: 'utf-8' })); + expect(manifestStructure.artifacts.Stack.metadata['/Stack']).toEqual([ + { + type: 'aws:cdk:stack-tags', + data: [ + { + // Note: uppercase due to historical accident + Key: 'foo', + Value: 'bar', + }, + ], + }, + ]); +}); + +test('already uppercased stack tags get left alone', () => { + // Backwards compatibility test + // GIVEN + builder.addArtifact('Stack', { + ...stackBase, + metadata: { + '/Stack': [ + { + type: 'aws:cdk:stack-tags', + data: [{ Key: 'foo', Value: 'bar' } as any], + }, + ], + }, + }); + + // WHEN + const assembly = builder.buildAssembly(); + + // THEN + const manifestStructure = JSON.parse(fs.readFileSync(path.join(assembly.directory, 'manifest.json'), { encoding: 'utf-8' })); + expect(manifestStructure.artifacts.Stack.metadata['/Stack']).toEqual([ + { + type: 'aws:cdk:stack-tags', + data: [ + { + // Note: uppercase due to historical accident + Key: 'foo', + Value: 'bar', + }, + ], + }, + ]); +}); + + +test('read tags from stack metadata', () => { + // Backwards compatibility test + // GIVEN + builder.addArtifact('Stack', { + ...stackBase, + metadata: { + '/Stack': [ + { + type: 'aws:cdk:stack-tags', + data: [{ key: 'foo', value: 'bar' }], + }, + ], + }, + }); + + // WHEN + const assembly = builder.buildAssembly(); + + // THEN + expect(assembly.getStackByName('Stack').tags).toEqual({ foo: 'bar' }); +}); diff --git a/packages/@aws-cdk/cx-api/test/util.ts b/packages/@aws-cdk/cx-api/test/util.ts new file mode 100644 index 0000000000000..58cee8acf89c7 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/util.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +export function rimraf(fsPath: string) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } else { + fs.unlinkSync(fsPath); + } + } catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { throw e; } + } +} diff --git a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts index 902409c43a331..ee3038ad4372c 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs'; import * as path from 'path'; import * as cfn from '@aws-cdk/aws-cloudformation'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; @@ -112,6 +113,13 @@ export interface DeployCdkStackActionProps extends DeployCdkStackActionOptions { * @default - No dependencies */ readonly dependencyStackArtifactIds?: string[]; + + /** + * Template configuration path relative to the input artifact + * + * @default - No template configuration + */ + readonly templateConfigurationPath?: string; } /** @@ -156,12 +164,25 @@ export class DeployCdkStackAction implements codepipeline.IAction { // It should be easier to get this, but for now it is what it is. const appAsmRoot = assemblyBuilderOf(appOf(scope)).outdir; const fullTemplatePath = path.join(artifact.assembly.directory, artifact.templateFile); - const templatePath = path.relative(appAsmRoot, fullTemplatePath); + + let fullConfigPath; + if (Object.keys(artifact.tags).length > 0) { + fullConfigPath = `${fullTemplatePath}.config.json`; + + // Write the template configuration file (for parameters into CreateChangeSet call that + // cannot be configured any other way). They must come from a file, and there's unfortunately + // no better hook to write this file (`construct.onSynthesize()` would have been the prime candidate + // but that is being deprecated--and DeployCdkStackAction isn't even a construct). + writeTemplateConfiguration(fullConfigPath, { + Tags: artifact.tags, + }); + } return new DeployCdkStackAction({ actionRole, cloudFormationExecutionRole, - templatePath, + templatePath: path.relative(appAsmRoot, fullTemplatePath), + templateConfigurationPath: fullConfigPath ? path.relative(appAsmRoot, fullConfigPath) : undefined, region, stackArtifactId: artifact.id, dependencyStackArtifactIds: artifact.dependencies.filter(isStackArtifact).map(s => s.id), @@ -223,6 +244,7 @@ export class DeployCdkStackAction implements codepipeline.IAction { deploymentRole: props.cloudFormationExecutionRole, region: props.region, capabilities: [cfn.CloudFormationCapabilities.NAMED_IAM, cfn.CloudFormationCapabilities.AUTO_EXPAND], + templateConfiguration: props.templateConfigurationPath ? props.cloudAssemblyInput.atPath(props.templateConfigurationPath) : undefined, }); this.executeChangeSetAction = new cpactions.CloudFormationExecuteChangeSetAction({ actionName: `${baseActionName}.Deploy`, @@ -331,3 +353,23 @@ function isStackArtifact(a: cxapi.CloudArtifact): a is cxapi.CloudFormationStack // return a instanceof cxapi.CloudFormationStackArtifact; return a.constructor.name === 'CloudFormationStackArtifact'; } + +/** + * Template configuration in a CodePipeline + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-cfn-artifacts.html#w2ab1c13c17c15 + */ +interface TemplateConfiguration { + readonly Parameters?: Record; + readonly Tags?: Record; + readonly StackPolicy?: { + readonly Statements: Array>; + }; +} + +/** + * Write template configuration to the given file + */ +function writeTemplateConfiguration(filename: string, config: TemplateConfiguration) { + fs.writeFileSync(filename, JSON.stringify(config, undefined, 2), { encoding: 'utf-8' }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/pipeline.test.ts b/packages/@aws-cdk/pipelines/test/pipeline.test.ts index 7d4455959a8eb..f47149e5f8395 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline.test.ts @@ -1,8 +1,10 @@ -import { anything, arrayWith, deepObjectLike, encodedJson, objectLike, stringLike } from '@aws-cdk/assert'; +import * as fs from 'fs'; +import * as path from 'path'; +import { anything, arrayWith, Capture, deepObjectLike, encodedJson, objectLike, stringLike } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as cp from '@aws-cdk/aws-codepipeline'; import * as cpa from '@aws-cdk/aws-codepipeline-actions'; -import { Construct, Stack, Stage, StageProps, SecretValue } from '@aws-cdk/core'; +import { Construct, Stack, Stage, StageProps, SecretValue, Tags } from '@aws-cdk/core'; import * as cdkp from '../lib'; import { BucketStack, PIPELINE_ENV, stackTemplate, TestApp, TestGitHubNpmPipeline } from './testutil'; @@ -404,6 +406,40 @@ test('add another action to an existing stage', () => { }); }); +test('tags get reflected in pipeline', () => { + // WHEN + const stage = new OneStackApp(app, 'App'); + Tags.of(stage).add('CostCenter', 'F00B4R'); + pipeline.addApplicationStage(stage); + + // THEN + const templateConfig = Capture.aString(); + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'App', + Actions: arrayWith( + objectLike({ + Name: 'Stack.Prepare', + InputArtifacts: [objectLike({})], + Configuration: objectLike({ + StackName: 'App-Stack', + TemplateConfiguration: templateConfig.capture(stringLike('*::assembly-App/*.template.*json')), + }), + }), + ), + }), + }); + + const [, relConfigFile] = templateConfig.capturedValue.split('::'); + const absConfigFile = path.join(app.outdir, relConfigFile); + const configFile = JSON.parse(fs.readFileSync(absConfigFile, { encoding: 'utf-8' })); + expect(configFile).toEqual(expect.objectContaining({ + Tags: { + CostCenter: 'F00B4R', + }, + })); +}); + class OneStackApp extends Stage { constructor(scope: Construct, id: string, props?: StageProps) { super(scope, id, props); diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 62cd54f1d2068..c0ae230879a80 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -1,6 +1,5 @@ import * as path from 'path'; import { format } from 'util'; -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; @@ -626,21 +625,7 @@ export interface DestroyOptions { * @returns an array with the tags available in the stack metadata. */ function tagsForStack(stack: cxapi.CloudFormationStackArtifact): Tag[] { - const tagLists = stack.findMetadataByType(cxschema.ArtifactMetadataEntryType.STACK_TAGS).map( - // the tags in the cloud assembly are stored differently - // unfortunately. - x => toCloudFormationTags(x.data as cxschema.Tag[])); - return Array.prototype.concat([], ...tagLists); -} - -/** - * Transform tags as they are retrieved from the cloud assembly, - * to the way that CloudFormation expects them. (Different casing). - */ -function toCloudFormationTags(tags: cxschema.Tag[]): Tag[] { - return tags.map(t => { - return { Key: t.key, Value: t.value }; - }); + return Object.entries(stack.tags).map(([Key, Value]) => ({ Key, Value })); } export interface Tag { From d3df6c74f2566ec0862ed0cd93a6f15b645154e3 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Mon, 28 Sep 2020 16:29:06 +0200 Subject: [PATCH 22/46] chore: set up "Framework :: AWS CDK" PyPI classifiers (#10571) Configure the `jsii` targets for Python so that the `Framework :: AWS CDK` and `Framework :: AWS CDK :: 1` trove classifiers are present on the package metadata, for easier discovery in PyPI. See: https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+AWS+CDK Fixes aws/cdk-ops#393 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/alexa-ask/package.json | 6 +++++- packages/@aws-cdk/app-delivery/package.json | 6 +++++- packages/@aws-cdk/assets/package.json | 6 +++++- packages/@aws-cdk/aws-accessanalyzer/package.json | 6 +++++- packages/@aws-cdk/aws-acmpca/package.json | 6 +++++- packages/@aws-cdk/aws-amazonmq/package.json | 6 +++++- packages/@aws-cdk/aws-amplify/package.json | 6 +++++- packages/@aws-cdk/aws-apigateway/package.json | 6 +++++- packages/@aws-cdk/aws-apigatewayv2/package.json | 6 +++++- packages/@aws-cdk/aws-appconfig/package.json | 6 +++++- packages/@aws-cdk/aws-applicationautoscaling/package.json | 6 +++++- packages/@aws-cdk/aws-applicationinsights/package.json | 6 +++++- packages/@aws-cdk/aws-appmesh/package.json | 6 +++++- packages/@aws-cdk/aws-appstream/package.json | 6 +++++- packages/@aws-cdk/aws-appsync/package.json | 6 +++++- packages/@aws-cdk/aws-athena/package.json | 6 +++++- packages/@aws-cdk/aws-autoscaling-common/package.json | 6 +++++- packages/@aws-cdk/aws-autoscaling-hooktargets/package.json | 6 +++++- packages/@aws-cdk/aws-autoscaling/package.json | 6 +++++- packages/@aws-cdk/aws-autoscalingplans/package.json | 6 +++++- packages/@aws-cdk/aws-backup/package.json | 6 +++++- packages/@aws-cdk/aws-batch/package.json | 6 +++++- packages/@aws-cdk/aws-budgets/package.json | 6 +++++- packages/@aws-cdk/aws-cassandra/package.json | 6 +++++- packages/@aws-cdk/aws-ce/package.json | 6 +++++- packages/@aws-cdk/aws-certificatemanager/package.json | 6 +++++- packages/@aws-cdk/aws-chatbot/package.json | 6 +++++- packages/@aws-cdk/aws-cloud9/package.json | 6 +++++- packages/@aws-cdk/aws-cloudformation/package.json | 6 +++++- packages/@aws-cdk/aws-cloudfront-origins/package.json | 6 +++++- packages/@aws-cdk/aws-cloudfront/package.json | 6 +++++- packages/@aws-cdk/aws-cloudtrail/package.json | 6 +++++- packages/@aws-cdk/aws-cloudwatch-actions/package.json | 6 +++++- packages/@aws-cdk/aws-cloudwatch/package.json | 6 +++++- packages/@aws-cdk/aws-codebuild/package.json | 6 +++++- packages/@aws-cdk/aws-codecommit/package.json | 6 +++++- packages/@aws-cdk/aws-codedeploy/package.json | 6 +++++- packages/@aws-cdk/aws-codeguruprofiler/package.json | 6 +++++- packages/@aws-cdk/aws-codegurureviewer/package.json | 6 +++++- packages/@aws-cdk/aws-codepipeline-actions/package.json | 6 +++++- packages/@aws-cdk/aws-codepipeline/package.json | 6 +++++- packages/@aws-cdk/aws-codestar/package.json | 6 +++++- packages/@aws-cdk/aws-codestarconnections/package.json | 6 +++++- packages/@aws-cdk/aws-codestarnotifications/package.json | 6 +++++- packages/@aws-cdk/aws-cognito/package.json | 6 +++++- packages/@aws-cdk/aws-config/package.json | 6 +++++- packages/@aws-cdk/aws-datapipeline/package.json | 6 +++++- packages/@aws-cdk/aws-dax/package.json | 6 +++++- packages/@aws-cdk/aws-detective/package.json | 6 +++++- packages/@aws-cdk/aws-directoryservice/package.json | 6 +++++- packages/@aws-cdk/aws-dlm/package.json | 6 +++++- packages/@aws-cdk/aws-dms/package.json | 6 +++++- packages/@aws-cdk/aws-docdb/package.json | 6 +++++- packages/@aws-cdk/aws-dynamodb-global/package.json | 6 +++++- packages/@aws-cdk/aws-dynamodb/package.json | 6 +++++- packages/@aws-cdk/aws-ec2/package.json | 6 +++++- packages/@aws-cdk/aws-ecr-assets/package.json | 6 +++++- packages/@aws-cdk/aws-ecr/package.json | 6 +++++- packages/@aws-cdk/aws-ecs-patterns/package.json | 6 +++++- packages/@aws-cdk/aws-ecs/package.json | 6 +++++- packages/@aws-cdk/aws-efs/package.json | 6 +++++- packages/@aws-cdk/aws-eks-legacy/package.json | 6 +++++- packages/@aws-cdk/aws-eks/package.json | 6 +++++- packages/@aws-cdk/aws-elasticache/package.json | 6 +++++- packages/@aws-cdk/aws-elasticbeanstalk/package.json | 6 +++++- packages/@aws-cdk/aws-elasticloadbalancing/package.json | 6 +++++- .../aws-elasticloadbalancingv2-actions/package.json | 6 +++++- .../aws-elasticloadbalancingv2-targets/package.json | 6 +++++- packages/@aws-cdk/aws-elasticloadbalancingv2/package.json | 6 +++++- packages/@aws-cdk/aws-elasticsearch/package.json | 6 +++++- packages/@aws-cdk/aws-emr/package.json | 6 +++++- packages/@aws-cdk/aws-events-targets/package.json | 6 +++++- packages/@aws-cdk/aws-events/package.json | 6 +++++- packages/@aws-cdk/aws-eventschemas/package.json | 6 +++++- packages/@aws-cdk/aws-fms/package.json | 6 +++++- packages/@aws-cdk/aws-fsx/package.json | 6 +++++- packages/@aws-cdk/aws-gamelift/package.json | 6 +++++- packages/@aws-cdk/aws-globalaccelerator/package.json | 6 +++++- packages/@aws-cdk/aws-glue/package.json | 6 +++++- packages/@aws-cdk/aws-greengrass/package.json | 6 +++++- packages/@aws-cdk/aws-guardduty/package.json | 6 +++++- packages/@aws-cdk/aws-iam/package.json | 6 +++++- packages/@aws-cdk/aws-imagebuilder/package.json | 6 +++++- packages/@aws-cdk/aws-inspector/package.json | 6 +++++- packages/@aws-cdk/aws-iot/package.json | 6 +++++- packages/@aws-cdk/aws-iot1click/package.json | 6 +++++- packages/@aws-cdk/aws-iotanalytics/package.json | 6 +++++- packages/@aws-cdk/aws-iotevents/package.json | 6 +++++- packages/@aws-cdk/aws-iotthingsgraph/package.json | 6 +++++- packages/@aws-cdk/aws-kendra/package.json | 6 +++++- packages/@aws-cdk/aws-kinesis/package.json | 6 +++++- packages/@aws-cdk/aws-kinesisanalytics/package.json | 6 +++++- packages/@aws-cdk/aws-kinesisfirehose/package.json | 6 +++++- packages/@aws-cdk/aws-kms/package.json | 6 +++++- packages/@aws-cdk/aws-lakeformation/package.json | 6 +++++- packages/@aws-cdk/aws-lambda-destinations/package.json | 6 +++++- packages/@aws-cdk/aws-lambda-event-sources/package.json | 6 +++++- packages/@aws-cdk/aws-lambda-nodejs/package.json | 6 +++++- packages/@aws-cdk/aws-lambda-python/package.json | 6 +++++- packages/@aws-cdk/aws-lambda/package.json | 6 +++++- packages/@aws-cdk/aws-logs-destinations/package.json | 6 +++++- packages/@aws-cdk/aws-logs/package.json | 6 +++++- packages/@aws-cdk/aws-macie/package.json | 6 +++++- packages/@aws-cdk/aws-managedblockchain/package.json | 6 +++++- packages/@aws-cdk/aws-mediaconvert/package.json | 6 +++++- packages/@aws-cdk/aws-medialive/package.json | 6 +++++- packages/@aws-cdk/aws-mediastore/package.json | 6 +++++- packages/@aws-cdk/aws-msk/package.json | 6 +++++- packages/@aws-cdk/aws-neptune/package.json | 6 +++++- packages/@aws-cdk/aws-networkmanager/package.json | 6 +++++- packages/@aws-cdk/aws-opsworks/package.json | 6 +++++- packages/@aws-cdk/aws-opsworkscm/package.json | 6 +++++- packages/@aws-cdk/aws-pinpoint/package.json | 6 +++++- packages/@aws-cdk/aws-pinpointemail/package.json | 6 +++++- packages/@aws-cdk/aws-qldb/package.json | 6 +++++- packages/@aws-cdk/aws-ram/package.json | 6 +++++- packages/@aws-cdk/aws-rds/package.json | 6 +++++- packages/@aws-cdk/aws-redshift/package.json | 6 +++++- packages/@aws-cdk/aws-resourcegroups/package.json | 6 +++++- packages/@aws-cdk/aws-robomaker/package.json | 6 +++++- packages/@aws-cdk/aws-route53-patterns/package.json | 6 +++++- packages/@aws-cdk/aws-route53-targets/package.json | 6 +++++- packages/@aws-cdk/aws-route53/package.json | 6 +++++- packages/@aws-cdk/aws-route53resolver/package.json | 6 +++++- packages/@aws-cdk/aws-s3-assets/package.json | 6 +++++- packages/@aws-cdk/aws-s3-deployment/package.json | 6 +++++- packages/@aws-cdk/aws-s3-notifications/package.json | 6 +++++- packages/@aws-cdk/aws-s3/package.json | 6 +++++- packages/@aws-cdk/aws-sagemaker/package.json | 6 +++++- packages/@aws-cdk/aws-sam/package.json | 6 +++++- packages/@aws-cdk/aws-sdb/package.json | 6 +++++- packages/@aws-cdk/aws-secretsmanager/package.json | 6 +++++- packages/@aws-cdk/aws-securityhub/package.json | 6 +++++- packages/@aws-cdk/aws-servicecatalog/package.json | 6 +++++- packages/@aws-cdk/aws-servicediscovery/package.json | 6 +++++- packages/@aws-cdk/aws-ses-actions/package.json | 6 +++++- packages/@aws-cdk/aws-ses/package.json | 6 +++++- packages/@aws-cdk/aws-sns-subscriptions/package.json | 6 +++++- packages/@aws-cdk/aws-sns/package.json | 6 +++++- packages/@aws-cdk/aws-sqs/package.json | 6 +++++- packages/@aws-cdk/aws-ssm/package.json | 6 +++++- packages/@aws-cdk/aws-sso/package.json | 6 +++++- packages/@aws-cdk/aws-stepfunctions-tasks/package.json | 6 +++++- packages/@aws-cdk/aws-stepfunctions/package.json | 6 +++++- packages/@aws-cdk/aws-synthetics/package.json | 6 +++++- packages/@aws-cdk/aws-transfer/package.json | 6 +++++- packages/@aws-cdk/aws-waf/package.json | 6 +++++- packages/@aws-cdk/aws-wafregional/package.json | 6 +++++- packages/@aws-cdk/aws-wafv2/package.json | 6 +++++- packages/@aws-cdk/aws-workspaces/package.json | 6 +++++- packages/@aws-cdk/cdk-assets-schema/package.json | 6 +++++- .../cfnspec/build-tools/create-missing-libraries.ts | 4 ++++ packages/@aws-cdk/cloud-assembly-schema/package.json | 6 +++++- packages/@aws-cdk/cloudformation-include/package.json | 6 +++++- packages/@aws-cdk/core/package.json | 6 +++++- packages/@aws-cdk/custom-resources/package.json | 6 +++++- packages/@aws-cdk/cx-api/package.json | 6 +++++- packages/@aws-cdk/example-construct-library/package.json | 6 +++++- packages/@aws-cdk/pipelines/package.json | 6 +++++- packages/@aws-cdk/region-info/package.json | 6 +++++- packages/@aws-cdk/yaml-cfn/package.json | 6 +++++- tools/pkglint/lib/rules.ts | 3 +++ 162 files changed, 807 insertions(+), 160 deletions(-) diff --git a/packages/@aws-cdk/alexa-ask/package.json b/packages/@aws-cdk/alexa-ask/package.json index 825196baf23c2..94f22c5a50b59 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.alexa-ask", - "module": "aws_cdk.alexa_ask" + "module": "aws_cdk.alexa_ask", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index c85209e9eef41..1151128ca0e11 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -22,7 +22,11 @@ }, "python": { "distName": "aws-cdk.app-delivery", - "module": "aws_cdk.app_delivery" + "module": "aws_cdk.app_delivery", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "outdir": "dist", diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 99f052d188ab8..1c6288a584c9f 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.assets", - "module": "aws_cdk.assets" + "module": "aws_cdk.assets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-accessanalyzer/package.json b/packages/@aws-cdk/aws-accessanalyzer/package.json index 41c87fff5bdc1..cc3e5f4abadd7 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/package.json +++ b/packages/@aws-cdk/aws-accessanalyzer/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-accessanalyzer", - "module": "aws_cdk.aws_accessanalyzer" + "module": "aws_cdk.aws_accessanalyzer", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-acmpca/package.json b/packages/@aws-cdk/aws-acmpca/package.json index 1f6a8aacf5b35..4636ccbb5a254 100644 --- a/packages/@aws-cdk/aws-acmpca/package.json +++ b/packages/@aws-cdk/aws-acmpca/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-acmpca", - "module": "aws_cdk.aws_acmpca" + "module": "aws_cdk.aws_acmpca", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index b215adabe636d..57b006a6d8497 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-amazonmq", - "module": "aws_cdk.aws_amazonmq" + "module": "aws_cdk.aws_amazonmq", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index 6f7417f3363f8..ae5c91a801159 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-amplify", - "module": "aws_cdk.aws_amplify" + "module": "aws_cdk.aws_amplify", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 72534284e0d2f..90fbaadcf26ab 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-apigateway", - "module": "aws_cdk.aws_apigateway" + "module": "aws_cdk.aws_apigateway", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 945cb89bb4586..5bc75ca9013d2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-apigatewayv2", - "module": "aws_cdk.aws_apigatewayv2" + "module": "aws_cdk.aws_apigatewayv2", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-appconfig/package.json b/packages/@aws-cdk/aws-appconfig/package.json index 8eff1859225bf..882b6c95adab8 100644 --- a/packages/@aws-cdk/aws-appconfig/package.json +++ b/packages/@aws-cdk/aws-appconfig/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-appconfig", - "module": "aws_cdk.aws_appconfig" + "module": "aws_cdk.aws_appconfig", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index e866816981b40..874fea95fa0f0 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-applicationautoscaling", - "module": "aws_cdk.aws_applicationautoscaling" + "module": "aws_cdk.aws_applicationautoscaling", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-applicationinsights/package.json b/packages/@aws-cdk/aws-applicationinsights/package.json index a4a83155b62a9..2d01bb0af2495 100644 --- a/packages/@aws-cdk/aws-applicationinsights/package.json +++ b/packages/@aws-cdk/aws-applicationinsights/package.json @@ -24,7 +24,11 @@ }, "python": { "distName": "aws-cdk.aws-applicationinsights", - "module": "aws_cdk.aws_applicationinsights" + "module": "aws_cdk.aws_applicationinsights", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } } }, diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 47b943e957a4d..106bbf45d9ee0 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-appmesh", - "module": "aws_cdk.aws_appmesh" + "module": "aws_cdk.aws_appmesh", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index fe2a533988bcb..6f8a6657e471a 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-appstream", - "module": "aws_cdk.aws_appstream" + "module": "aws_cdk.aws_appstream", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 87f8c66b231e7..21930d695061e 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-appsync", - "module": "aws_cdk.aws_appsync" + "module": "aws_cdk.aws_appsync", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index 7f5a88705c65b..95e98d47c31f5 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-athena", - "module": "aws_cdk.aws_athena" + "module": "aws_cdk.aws_athena", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 8c423257b0285..61a1295700937 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-autoscaling-common", - "module": "aws_cdk.aws_autoscaling_common" + "module": "aws_cdk.aws_autoscaling_common", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index a2013f4b2d29a..cac6df3c172cf 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-autoscaling-hooktargets", - "module": "aws_cdk.aws_autoscaling_hooktargets" + "module": "aws_cdk.aws_autoscaling_hooktargets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 923e6c6adec08..3d8d1e4a80993 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-autoscaling", - "module": "aws_cdk.aws_autoscaling" + "module": "aws_cdk.aws_autoscaling", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index ac5c4b8477e52..388a29873ef12 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-autoscalingplans", - "module": "aws_cdk.aws_autoscalingplans" + "module": "aws_cdk.aws_autoscalingplans", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-backup/package.json b/packages/@aws-cdk/aws-backup/package.json index 6993e60ac380b..6622f7c75badc 100644 --- a/packages/@aws-cdk/aws-backup/package.json +++ b/packages/@aws-cdk/aws-backup/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-backup", - "module": "aws_cdk.aws_backup" + "module": "aws_cdk.aws_backup", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index ad24cb4da40c8..699a4fcf33b58 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-batch", - "module": "aws_cdk.aws_batch" + "module": "aws_cdk.aws_batch", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index 33c5cfc91a6e7..5620f490a2d6a 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-budgets", - "module": "aws_cdk.aws_budgets" + "module": "aws_cdk.aws_budgets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cassandra/package.json b/packages/@aws-cdk/aws-cassandra/package.json index 38acd5d388b30..165d47b79cb33 100644 --- a/packages/@aws-cdk/aws-cassandra/package.json +++ b/packages/@aws-cdk/aws-cassandra/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cassandra", - "module": "aws_cdk.aws_cassandra" + "module": "aws_cdk.aws_cassandra", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ce/package.json b/packages/@aws-cdk/aws-ce/package.json index 5ffd08fcc3ea9..86c100223e0f5 100644 --- a/packages/@aws-cdk/aws-ce/package.json +++ b/packages/@aws-cdk/aws-ce/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ce", - "module": "aws_cdk.aws_ce" + "module": "aws_cdk.aws_ce", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 61a55b83bcfa9..9ad0d12c3d0e1 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-certificatemanager", - "module": "aws_cdk.aws_certificatemanager" + "module": "aws_cdk.aws_certificatemanager", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index 59ddd0cd77453..ef17794163e93 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-chatbot", - "module": "aws_cdk.aws_chatbot" + "module": "aws_cdk.aws_chatbot", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index 2333a332791eb..de967a9e7a38d 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloud9", - "module": "aws_cdk.aws_cloud9" + "module": "aws_cdk.aws_cloud9", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 4eabd9c3a914b..d74b71bc9038d 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudformation", - "module": "aws_cdk.aws_cloudformation" + "module": "aws_cdk.aws_cloudformation", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 851ffefd9e715..70e167f8dd984 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudfront-origins", - "module": "aws_cdk.aws_cloudfront_origins" + "module": "aws_cdk.aws_cloudfront_origins", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index f1853646cda47..b3b40d712560e 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudfront", - "module": "aws_cdk.aws_cloudfront" + "module": "aws_cdk.aws_cloudfront", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 773aedf8a1c9a..67c6de96f918c 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudtrail", - "module": "aws_cdk.aws_cloudtrail" + "module": "aws_cdk.aws_cloudtrail", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/package.json b/packages/@aws-cdk/aws-cloudwatch-actions/package.json index b399039dafee7..3b9d6a5034f78 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/package.json +++ b/packages/@aws-cdk/aws-cloudwatch-actions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudwatch-actions", - "module": "aws_cdk.aws_cloudwatch_actions" + "module": "aws_cdk.aws_cloudwatch_actions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index ecf3d2229f8a6..10fcf3bfce240 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cloudwatch", - "module": "aws_cdk.aws_cloudwatch" + "module": "aws_cdk.aws_cloudwatch", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index edb147d12b59a..fb556ae6b7190 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codebuild", - "module": "aws_cdk.aws_codebuild" + "module": "aws_cdk.aws_codebuild", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 1b83f89ecef25..ef00706f6eae0 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codecommit", - "module": "aws_cdk.aws_codecommit" + "module": "aws_cdk.aws_codecommit", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 9862b256c6678..136cca68a108f 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codedeploy", - "module": "aws_cdk.aws_codedeploy" + "module": "aws_cdk.aws_codedeploy", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index 20285133f7606..b4a35197b310d 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codeguruprofiler", - "module": "aws_cdk.aws_codeguruprofiler" + "module": "aws_cdk.aws_codeguruprofiler", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codegurureviewer/package.json b/packages/@aws-cdk/aws-codegurureviewer/package.json index 637e03e8f98ec..4528848bdf5e6 100644 --- a/packages/@aws-cdk/aws-codegurureviewer/package.json +++ b/packages/@aws-cdk/aws-codegurureviewer/package.json @@ -24,7 +24,11 @@ }, "python": { "distName": "aws-cdk.aws-codegurureviewer", - "module": "aws_cdk.aws_codegurureviewer" + "module": "aws_cdk.aws_codegurureviewer", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } } }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index e95142fed69b5..49c0a224fa34b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codepipeline-actions", - "module": "aws_cdk.aws_codepipeline_actions" + "module": "aws_cdk.aws_codepipeline_actions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index f57169ef70cf4..3ee04f96196e9 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codepipeline", - "module": "aws_cdk.aws_codepipeline" + "module": "aws_cdk.aws_codepipeline", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codestar/package.json b/packages/@aws-cdk/aws-codestar/package.json index 1b813576d9694..65c610745c1a0 100644 --- a/packages/@aws-cdk/aws-codestar/package.json +++ b/packages/@aws-cdk/aws-codestar/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codestar", - "module": "aws_cdk.aws_codestar" + "module": "aws_cdk.aws_codestar", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codestarconnections/package.json b/packages/@aws-cdk/aws-codestarconnections/package.json index 979b46551f051..8605bb93ef47f 100644 --- a/packages/@aws-cdk/aws-codestarconnections/package.json +++ b/packages/@aws-cdk/aws-codestarconnections/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codestarconnections", - "module": "aws_cdk.aws_codestarconnections" + "module": "aws_cdk.aws_codestarconnections", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index 7262362dafa5a..05e6cbadabf23 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-codestarnotifications", - "module": "aws_cdk.aws_codestarnotifications" + "module": "aws_cdk.aws_codestarnotifications", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 68fc9bdcf835c..4b606b05fefff 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-cognito", - "module": "aws_cdk.aws_cognito" + "module": "aws_cdk.aws_cognito", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 7888a4c70479e..1904de7f4a660 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-config", - "module": "aws_cdk.aws_config" + "module": "aws_cdk.aws_config", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 3c1f8105739ad..1b24802929d73 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-datapipeline", - "module": "aws_cdk.aws_datapipeline" + "module": "aws_cdk.aws_datapipeline", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index b856b24216a89..a266a83a0124f 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-dax", - "module": "aws_cdk.aws_dax" + "module": "aws_cdk.aws_dax", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-detective/package.json b/packages/@aws-cdk/aws-detective/package.json index 6becc0bc0a150..fdbb9dc3a2362 100644 --- a/packages/@aws-cdk/aws-detective/package.json +++ b/packages/@aws-cdk/aws-detective/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-detective", - "module": "aws_cdk.aws_detective" + "module": "aws_cdk.aws_detective", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index f977f48e9a0ba..348ce00c60d1f 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-directoryservice", - "module": "aws_cdk.aws_directoryservice" + "module": "aws_cdk.aws_directoryservice", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index 3eba4edcc1fc5..f3123d4187356 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-dlm", - "module": "aws_cdk.aws_dlm" + "module": "aws_cdk.aws_dlm", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index dcae3650803f0..61637d58edad1 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-dms", - "module": "aws_cdk.aws_dms" + "module": "aws_cdk.aws_dms", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 2732be6d944da..0959ef2574981 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-docdb", - "module": "aws_cdk.aws_docdb" + "module": "aws_cdk.aws_docdb", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-dynamodb-global/package.json b/packages/@aws-cdk/aws-dynamodb-global/package.json index 5c81160c134b0..8e371e6d99d73 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/package.json @@ -27,7 +27,11 @@ }, "python": { "distName": "aws-cdk.aws-dynamodb-global", - "module": "aws_cdk.aws_dynamodb_global" + "module": "aws_cdk.aws_dynamodb_global", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] }, "dotnet": { "namespace": "Amazon.CDK.AWS.DynamoDB.Global", diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index d52338c60123e..402328d6cf997 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-dynamodb", - "module": "aws_cdk.aws_dynamodb" + "module": "aws_cdk.aws_dynamodb", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index da082c3f5571b..530fbd4a7473a 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ec2", - "module": "aws_cdk.aws_ec2" + "module": "aws_cdk.aws_ec2", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index e72a6f77b301f..a71af02e87eaa 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ecr-assets", - "module": "aws_cdk.aws_ecr_assets" + "module": "aws_cdk.aws_ecr_assets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 0521418925521..02715f1d1b27a 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ecr", - "module": "aws_cdk.aws_ecr" + "module": "aws_cdk.aws_ecr", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ecs-patterns/package.json b/packages/@aws-cdk/aws-ecs-patterns/package.json index a6f5c320dc9aa..5717edc48d756 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/package.json +++ b/packages/@aws-cdk/aws-ecs-patterns/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ecs-patterns", - "module": "aws_cdk.aws_ecs_patterns" + "module": "aws_cdk.aws_ecs_patterns", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 27c113cf8a54b..5052533ce989e 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ecs", - "module": "aws_cdk.aws_ecs" + "module": "aws_cdk.aws_ecs", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index ec64229c39fc6..810316d715094 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-efs", - "module": "aws_cdk.aws_efs" + "module": "aws_cdk.aws_efs", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index b91810dad9758..0cbb2b783a0b4 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-eks-legacy", - "module": "aws_cdk.aws_eks_legacy" + "module": "aws_cdk.aws_eks_legacy", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 8dfe97519e1d3..913aaeb024388 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-eks", - "module": "aws_cdk.aws_eks" + "module": "aws_cdk.aws_eks", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index 4e7bb129572bd..04e8b46390fd4 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticache", - "module": "aws_cdk.aws_elasticache" + "module": "aws_cdk.aws_elasticache", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index e0ce7edaa9258..dc04dcdf44aea 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticbeanstalk", - "module": "aws_cdk.aws_elasticbeanstalk" + "module": "aws_cdk.aws_elasticbeanstalk", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index bbd613800a969..7187de75b3ad0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticloadbalancing", - "module": "aws_cdk.aws_elasticloadbalancing" + "module": "aws_cdk.aws_elasticloadbalancing", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json index 69a2275f1c0d8..b63572c2da2d2 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticloadbalancingv2-actions", - "module": "aws_cdk.aws_elasticloadbalancingv2_actions" + "module": "aws_cdk.aws_elasticloadbalancingv2_actions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index bff2591ecdc89..96bf7c63d209c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticloadbalancingv2-targets", - "module": "aws_cdk.aws_elasticloadbalancingv2_targets" + "module": "aws_cdk.aws_elasticloadbalancingv2_targets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 1bf66849dfa9f..86c52cce535ca 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticloadbalancingv2", - "module": "aws_cdk.aws_elasticloadbalancingv2" + "module": "aws_cdk.aws_elasticloadbalancingv2", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index 3924a972f06b2..0597d412ed6fb 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-elasticsearch", - "module": "aws_cdk.aws_elasticsearch" + "module": "aws_cdk.aws_elasticsearch", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-emr/package.json b/packages/@aws-cdk/aws-emr/package.json index 494fe248be53d..775ed37d98345 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-emr", - "module": "aws_cdk.aws_emr" + "module": "aws_cdk.aws_emr", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 80a1d52f9240b..d38d1b60ca067 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-events-targets", - "module": "aws_cdk.aws_events_targets" + "module": "aws_cdk.aws_events_targets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index b1505b5bc050c..49601a8a03bb2 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-events", - "module": "aws_cdk.aws_events" + "module": "aws_cdk.aws_events", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-eventschemas/package.json b/packages/@aws-cdk/aws-eventschemas/package.json index c2844140889be..2f71cc896e3a4 100644 --- a/packages/@aws-cdk/aws-eventschemas/package.json +++ b/packages/@aws-cdk/aws-eventschemas/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-eventschemas", - "module": "aws_cdk.aws_eventschemas" + "module": "aws_cdk.aws_eventschemas", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-fms/package.json b/packages/@aws-cdk/aws-fms/package.json index 9dcdef1863963..f6820c068b8fd 100644 --- a/packages/@aws-cdk/aws-fms/package.json +++ b/packages/@aws-cdk/aws-fms/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-fms", - "module": "aws_cdk.aws_fms" + "module": "aws_cdk.aws_fms", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index f6187a29111af..8c04546c14f52 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-fsx", - "module": "aws_cdk.aws_fsx" + "module": "aws_cdk.aws_fsx", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 4ee1873b7aede..656f5f2979455 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-gamelift", - "module": "aws_cdk.aws_gamelift" + "module": "aws_cdk.aws_gamelift", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index c26fec36a9071..35c2fb7575466 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-globalaccelerator", - "module": "aws_cdk.aws_globalaccelerator" + "module": "aws_cdk.aws_globalaccelerator", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index ca858971dd057..0a3afa2014db5 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-glue", - "module": "aws_cdk.aws_glue" + "module": "aws_cdk.aws_glue", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-greengrass/package.json b/packages/@aws-cdk/aws-greengrass/package.json index b599c76dbbd52..d218c06a135d2 100644 --- a/packages/@aws-cdk/aws-greengrass/package.json +++ b/packages/@aws-cdk/aws-greengrass/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-greengrass", - "module": "aws_cdk.aws_greengrass" + "module": "aws_cdk.aws_greengrass", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index 69cbb56806f67..bb51020a6e8b1 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-guardduty", - "module": "aws_cdk.aws_guardduty" + "module": "aws_cdk.aws_guardduty", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index 3ff154d1e52ba..b7846eef1d72f 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iam", - "module": "aws_cdk.aws_iam" + "module": "aws_cdk.aws_iam", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-imagebuilder/package.json b/packages/@aws-cdk/aws-imagebuilder/package.json index bdf5876a166eb..f17015d928177 100644 --- a/packages/@aws-cdk/aws-imagebuilder/package.json +++ b/packages/@aws-cdk/aws-imagebuilder/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-imagebuilder", - "module": "aws_cdk.aws_imagebuilder" + "module": "aws_cdk.aws_imagebuilder", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index 193fdd6d9e67d..2fbbedd97ff9b 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-inspector", - "module": "aws_cdk.aws_inspector" + "module": "aws_cdk.aws_inspector", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index 2db587479e5c8..6d132bd45659d 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iot", - "module": "aws_cdk.aws_iot" + "module": "aws_cdk.aws_iot", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index 456e4850d38f4..852a47810aac3 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iot1click", - "module": "aws_cdk.aws_iot1click" + "module": "aws_cdk.aws_iot1click", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 07667a23b94e5..90de984540ef8 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iotanalytics", - "module": "aws_cdk.aws_iotanalytics" + "module": "aws_cdk.aws_iotanalytics", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 456ae624b19fb..804b64832bd44 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iotevents", - "module": "aws_cdk.aws_iotevents" + "module": "aws_cdk.aws_iotevents", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-iotthingsgraph/package.json b/packages/@aws-cdk/aws-iotthingsgraph/package.json index b54e9519b2aad..fa35752c6d52c 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/package.json +++ b/packages/@aws-cdk/aws-iotthingsgraph/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-iotthingsgraph", - "module": "aws_cdk.aws_iotthingsgraph" + "module": "aws_cdk.aws_iotthingsgraph", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-kendra/package.json b/packages/@aws-cdk/aws-kendra/package.json index 08d6d1209c917..bcc7bca3f8554 100644 --- a/packages/@aws-cdk/aws-kendra/package.json +++ b/packages/@aws-cdk/aws-kendra/package.json @@ -24,7 +24,11 @@ }, "python": { "distName": "aws-cdk.aws-kendra", - "module": "aws_cdk.aws_kendra" + "module": "aws_cdk.aws_kendra", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } } }, diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index 976e507c78e0e..a6cb174544b72 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-kinesis", - "module": "aws_cdk.aws_kinesis" + "module": "aws_cdk.aws_kinesis", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index 7b8b27681f321..5758ba9abaabd 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-kinesisanalytics", - "module": "aws_cdk.aws_kinesisanalytics" + "module": "aws_cdk.aws_kinesisanalytics", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index c1f716d537d38..c6e59d002e10f 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-kinesisfirehose", - "module": "aws_cdk.aws_kinesisfirehose" + "module": "aws_cdk.aws_kinesisfirehose", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 31f79371871ad..79afde054eab0 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-kms", - "module": "aws_cdk.aws_kms" + "module": "aws_cdk.aws_kms", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lakeformation/package.json b/packages/@aws-cdk/aws-lakeformation/package.json index 632c955c75447..d1078df39b644 100644 --- a/packages/@aws-cdk/aws-lakeformation/package.json +++ b/packages/@aws-cdk/aws-lakeformation/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lakeformation", - "module": "aws_cdk.aws_lakeformation" + "module": "aws_cdk.aws_lakeformation", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lambda-destinations/package.json b/packages/@aws-cdk/aws-lambda-destinations/package.json index 1e6783dd4f334..a813b3723c176 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/package.json +++ b/packages/@aws-cdk/aws-lambda-destinations/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lambda-destinations", - "module": "aws_cdk.aws_lambda_destinations" + "module": "aws_cdk.aws_lambda_destinations", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index d8459753b0292..905a3e709317b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lambda-event-sources", - "module": "aws_cdk.aws_lambda_event_sources" + "module": "aws_cdk.aws_lambda_event_sources", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 30f04975ca65b..da8d647f1301c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lambda-nodejs", - "module": "aws_cdk.aws_lambda_nodejs" + "module": "aws_cdk.aws_lambda_nodejs", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lambda-python/package.json b/packages/@aws-cdk/aws-lambda-python/package.json index 5715093b14f88..a3398d486d7c7 100644 --- a/packages/@aws-cdk/aws-lambda-python/package.json +++ b/packages/@aws-cdk/aws-lambda-python/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lambda-python", - "module": "aws_cdk.aws_lambda_python" + "module": "aws_cdk.aws_lambda_python", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index ae6fc1bd3506a..f1394d8950f0a 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-lambda", - "module": "aws_cdk.aws_lambda" + "module": "aws_cdk.aws_lambda", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-logs-destinations/package.json b/packages/@aws-cdk/aws-logs-destinations/package.json index e5c3a3ef14f78..385dab205a91d 100644 --- a/packages/@aws-cdk/aws-logs-destinations/package.json +++ b/packages/@aws-cdk/aws-logs-destinations/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-logs-destinations", - "module": "aws_cdk.aws_logs_destinations" + "module": "aws_cdk.aws_logs_destinations", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 91e847e740955..a4eadfdeba946 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-logs", - "module": "aws_cdk.aws_logs" + "module": "aws_cdk.aws_logs", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-macie/package.json b/packages/@aws-cdk/aws-macie/package.json index 0aa5b62ab052e..8bb07789a9ec1 100644 --- a/packages/@aws-cdk/aws-macie/package.json +++ b/packages/@aws-cdk/aws-macie/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-macie", - "module": "aws_cdk.aws_macie" + "module": "aws_cdk.aws_macie", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-managedblockchain/package.json b/packages/@aws-cdk/aws-managedblockchain/package.json index a56aa49f8ea96..78450647f4a65 100644 --- a/packages/@aws-cdk/aws-managedblockchain/package.json +++ b/packages/@aws-cdk/aws-managedblockchain/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-managedblockchain", - "module": "aws_cdk.aws_managedblockchain" + "module": "aws_cdk.aws_managedblockchain", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-mediaconvert/package.json b/packages/@aws-cdk/aws-mediaconvert/package.json index 054459c8e8f7b..df167a7059c03 100644 --- a/packages/@aws-cdk/aws-mediaconvert/package.json +++ b/packages/@aws-cdk/aws-mediaconvert/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-mediaconvert", - "module": "aws_cdk.aws_mediaconvert" + "module": "aws_cdk.aws_mediaconvert", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-medialive/package.json b/packages/@aws-cdk/aws-medialive/package.json index 3eee0925e2b3c..523db6cdb0951 100644 --- a/packages/@aws-cdk/aws-medialive/package.json +++ b/packages/@aws-cdk/aws-medialive/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-medialive", - "module": "aws_cdk.aws_medialive" + "module": "aws_cdk.aws_medialive", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-mediastore/package.json b/packages/@aws-cdk/aws-mediastore/package.json index 16b578b0e0a52..2fae56bae9d03 100644 --- a/packages/@aws-cdk/aws-mediastore/package.json +++ b/packages/@aws-cdk/aws-mediastore/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-mediastore", - "module": "aws_cdk.aws_mediastore" + "module": "aws_cdk.aws_mediastore", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-msk/package.json b/packages/@aws-cdk/aws-msk/package.json index b424ceefb383b..6ea82b33f9988 100644 --- a/packages/@aws-cdk/aws-msk/package.json +++ b/packages/@aws-cdk/aws-msk/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-msk", - "module": "aws_cdk.aws_msk" + "module": "aws_cdk.aws_msk", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index df23125bc8c40..f7293686b7aaa 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-neptune", - "module": "aws_cdk.aws_neptune" + "module": "aws_cdk.aws_neptune", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-networkmanager/package.json b/packages/@aws-cdk/aws-networkmanager/package.json index 26074e1ccd28a..7112c40b8da86 100644 --- a/packages/@aws-cdk/aws-networkmanager/package.json +++ b/packages/@aws-cdk/aws-networkmanager/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-networkmanager", - "module": "aws_cdk.aws_networkmanager" + "module": "aws_cdk.aws_networkmanager", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-opsworks/package.json b/packages/@aws-cdk/aws-opsworks/package.json index fafddad6199b3..2962110e06cce 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-opsworks", - "module": "aws_cdk.aws_opsworks" + "module": "aws_cdk.aws_opsworks", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-opsworkscm/package.json b/packages/@aws-cdk/aws-opsworkscm/package.json index 83ddae38f0d9a..8ed560f9bc326 100644 --- a/packages/@aws-cdk/aws-opsworkscm/package.json +++ b/packages/@aws-cdk/aws-opsworkscm/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-opsworkscm", - "module": "aws_cdk.aws_opsworkscm" + "module": "aws_cdk.aws_opsworkscm", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-pinpoint/package.json b/packages/@aws-cdk/aws-pinpoint/package.json index 85fceb5c5e2de..b2212b78cabf0 100644 --- a/packages/@aws-cdk/aws-pinpoint/package.json +++ b/packages/@aws-cdk/aws-pinpoint/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-pinpoint", - "module": "aws_cdk.aws_pinpoint" + "module": "aws_cdk.aws_pinpoint", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-pinpointemail/package.json b/packages/@aws-cdk/aws-pinpointemail/package.json index c46af8fa39dc6..b0f9756f2a08c 100644 --- a/packages/@aws-cdk/aws-pinpointemail/package.json +++ b/packages/@aws-cdk/aws-pinpointemail/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-pinpointemail", - "module": "aws_cdk.aws_pinpointemail" + "module": "aws_cdk.aws_pinpointemail", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-qldb/package.json b/packages/@aws-cdk/aws-qldb/package.json index 282c54c382fab..7fe121a6b482f 100644 --- a/packages/@aws-cdk/aws-qldb/package.json +++ b/packages/@aws-cdk/aws-qldb/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-qldb", - "module": "aws_cdk.aws_qldb" + "module": "aws_cdk.aws_qldb", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ram/package.json b/packages/@aws-cdk/aws-ram/package.json index b190ff47f16f2..fc5fd391233f6 100644 --- a/packages/@aws-cdk/aws-ram/package.json +++ b/packages/@aws-cdk/aws-ram/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ram", - "module": "aws_cdk.aws_ram" + "module": "aws_cdk.aws_ram", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index e45b2851a108f..4abc586d5a0d4 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-rds", - "module": "aws_cdk.aws_rds" + "module": "aws_cdk.aws_rds", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index ba6f6745d40f3..77790ff7c4b93 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-redshift", - "module": "aws_cdk.aws_redshift" + "module": "aws_cdk.aws_redshift", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-resourcegroups/package.json b/packages/@aws-cdk/aws-resourcegroups/package.json index 8c02fb8346b04..cde5efc080d7b 100644 --- a/packages/@aws-cdk/aws-resourcegroups/package.json +++ b/packages/@aws-cdk/aws-resourcegroups/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-resourcegroups", - "module": "aws_cdk.aws_resourcegroups" + "module": "aws_cdk.aws_resourcegroups", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-robomaker/package.json b/packages/@aws-cdk/aws-robomaker/package.json index f712920916864..49a3ec2db3b28 100644 --- a/packages/@aws-cdk/aws-robomaker/package.json +++ b/packages/@aws-cdk/aws-robomaker/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-robomaker", - "module": "aws_cdk.aws_robomaker" + "module": "aws_cdk.aws_robomaker", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-route53-patterns/package.json b/packages/@aws-cdk/aws-route53-patterns/package.json index ef708106293ec..138a02cefca6d 100644 --- a/packages/@aws-cdk/aws-route53-patterns/package.json +++ b/packages/@aws-cdk/aws-route53-patterns/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-route53-patterns", - "module": "aws_cdk.aws_route53_patterns" + "module": "aws_cdk.aws_route53_patterns", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index a877af56d17b8..7393055d1895c 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-route53-targets", - "module": "aws_cdk.aws_route53_targets" + "module": "aws_cdk.aws_route53_targets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 1c340eb48bf5a..3ac3f2a68525c 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-route53", - "module": "aws_cdk.aws_route53" + "module": "aws_cdk.aws_route53", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index c64c14efe58a0..25016c99ea9ad 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-route53resolver", - "module": "aws_cdk.aws_route53resolver" + "module": "aws_cdk.aws_route53resolver", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index ee888f697ab11..0d50839a6e9e7 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-s3-assets", - "module": "aws_cdk.aws_s3_assets" + "module": "aws_cdk.aws_s3_assets", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 90d443aa52769..4639915267e90 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-s3-deployment", - "module": "aws_cdk.aws_s3_deployment" + "module": "aws_cdk.aws_s3_deployment", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index d3748021e1ce9..f742665f9f944 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-s3-notifications", - "module": "aws_cdk.aws_s3_notifications" + "module": "aws_cdk.aws_s3_notifications", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 83150e561020d..fec39ed3100c9 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-s3", - "module": "aws_cdk.aws_s3" + "module": "aws_cdk.aws_s3", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index 27b7b63defe7d..db851cbf96644 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sagemaker", - "module": "aws_cdk.aws_sagemaker" + "module": "aws_cdk.aws_sagemaker", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index dcf3da7c7209b..46e05e90bb0f0 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sam", - "module": "aws_cdk.aws_sam" + "module": "aws_cdk.aws_sam", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index 28306afab851f..6287b044f5427 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sdb", - "module": "aws_cdk.aws_sdb" + "module": "aws_cdk.aws_sdb", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index 2cb9dd1a7c84d..5283f7cc04c5f 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-secretsmanager", - "module": "aws_cdk.aws_secretsmanager" + "module": "aws_cdk.aws_secretsmanager", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-securityhub/package.json b/packages/@aws-cdk/aws-securityhub/package.json index 9e83d4b6b198b..2465baa3de572 100644 --- a/packages/@aws-cdk/aws-securityhub/package.json +++ b/packages/@aws-cdk/aws-securityhub/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-securityhub", - "module": "aws_cdk.aws_securityhub" + "module": "aws_cdk.aws_securityhub", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index 4461cc42185cb..a4c3b37f8e1ec 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-servicecatalog", - "module": "aws_cdk.aws_servicecatalog" + "module": "aws_cdk.aws_servicecatalog", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index 15308644064a3..f90041bcfb1c7 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-servicediscovery", - "module": "aws_cdk.aws_servicediscovery" + "module": "aws_cdk.aws_servicediscovery", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ses-actions/package.json b/packages/@aws-cdk/aws-ses-actions/package.json index d03df20b359d7..3023c34244f5a 100644 --- a/packages/@aws-cdk/aws-ses-actions/package.json +++ b/packages/@aws-cdk/aws-ses-actions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ses-actions", - "module": "aws_cdk.aws_ses_actions" + "module": "aws_cdk.aws_ses_actions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index 366ebfe579cea..7f92bf6423a43 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ses", - "module": "aws_cdk.aws_ses" + "module": "aws_cdk.aws_ses", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sns-subscriptions/package.json b/packages/@aws-cdk/aws-sns-subscriptions/package.json index 012deb7b5776e..f7568ff46e143 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/package.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sns-subscriptions", - "module": "aws_cdk.aws_sns_subscriptions" + "module": "aws_cdk.aws_sns_subscriptions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 96bbf2ccee89f..fcf94d3403f0f 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sns", - "module": "aws_cdk.aws_sns" + "module": "aws_cdk.aws_sns", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "excludeTypescript": [ diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 4dbeb49535ea4..996743744aa5f 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-sqs", - "module": "aws_cdk.aws_sqs" + "module": "aws_cdk.aws_sqs", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 3220cb04f17ec..d7021983ba7b3 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-ssm", - "module": "aws_cdk.aws_ssm" + "module": "aws_cdk.aws_ssm", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-sso/package.json b/packages/@aws-cdk/aws-sso/package.json index ca07479857ec2..97c4875ce58e8 100644 --- a/packages/@aws-cdk/aws-sso/package.json +++ b/packages/@aws-cdk/aws-sso/package.json @@ -24,7 +24,11 @@ }, "python": { "distName": "aws-cdk.aws-sso", - "module": "aws_cdk.aws_sso" + "module": "aws_cdk.aws_sso", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } } }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 4cd9d02c114e4..af0ae0264a30f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-stepfunctions-tasks", - "module": "aws_cdk.aws_stepfunctions_tasks" + "module": "aws_cdk.aws_stepfunctions_tasks", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index 89254abcb7407..77d086a781c31 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-stepfunctions", - "module": "aws_cdk.aws_stepfunctions" + "module": "aws_cdk.aws_stepfunctions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index 19f8f1982a627..55611139e5af8 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-synthetics", - "module": "aws_cdk.aws_synthetics" + "module": "aws_cdk.aws_synthetics", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-transfer/package.json b/packages/@aws-cdk/aws-transfer/package.json index 037df5db1a49e..e198102ef0d05 100644 --- a/packages/@aws-cdk/aws-transfer/package.json +++ b/packages/@aws-cdk/aws-transfer/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-transfer", - "module": "aws_cdk.aws_transfer" + "module": "aws_cdk.aws_transfer", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 5061d49c96d24..4a6c0f4fe0e18 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-waf", - "module": "aws_cdk.aws_waf" + "module": "aws_cdk.aws_waf", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index a1dde0aa29202..2944c053d9d25 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-wafregional", - "module": "aws_cdk.aws_wafregional" + "module": "aws_cdk.aws_wafregional", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-wafv2/package.json b/packages/@aws-cdk/aws-wafv2/package.json index 1e4f6a918de64..6b4ffcf5a6f07 100644 --- a/packages/@aws-cdk/aws-wafv2/package.json +++ b/packages/@aws-cdk/aws-wafv2/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-wafv2", - "module": "aws_cdk.aws_wafv2" + "module": "aws_cdk.aws_wafv2", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index 72b2e25e19ce3..13ce6feb407ae 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.aws-workspaces", - "module": "aws_cdk.aws_workspaces" + "module": "aws_cdk.aws_workspaces", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index 94f2091e69426..a2f6e7a88f7e8 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.cdk-assets-schema", - "module": "aws_cdk.cdk_assets_schema" + "module": "aws_cdk.cdk_assets_schema", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index a4fe90f098011..ab0cd06c44861 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -124,6 +124,10 @@ async function main() { }, }, python: { + classifiers: [ + 'Framework :: AWS CDK', + 'Framework :: AWS CDK :: 1', + ], distName: pythonDistName, module: pythonModuleName, }, diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 41be3a4faa4fa..330990684ae18 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.cloud-assembly-schema", - "module": "aws_cdk.cloud_assembly_schema" + "module": "aws_cdk.cloud_assembly_schema", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 6aaf21ef1ff8d..ee082f18e4322 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.cloudformation-include", - "module": "aws_cdk.cloudformation_include" + "module": "aws_cdk.cloudformation_include", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index ac384aac42fb1..ebf59da5cb25f 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.core", - "module": "aws_cdk.core" + "module": "aws_cdk.core", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 98b961ab86b95..2f370ac8a5968 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.custom-resources", - "module": "aws_cdk.custom_resources" + "module": "aws_cdk.custom_resources", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index ed513afa55cd6..4f4bc15e84a3a 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.cx-api", - "module": "aws_cdk.cx_api" + "module": "aws_cdk.cx_api", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/example-construct-library/package.json b/packages/@aws-cdk/example-construct-library/package.json index 4768ce70bb5c9..1fcfaf962ed3c 100644 --- a/packages/@aws-cdk/example-construct-library/package.json +++ b/packages/@aws-cdk/example-construct-library/package.json @@ -24,7 +24,11 @@ }, "python": { "distName": "aws-cdk.example-construct-library", - "module": "aws_cdk.example_construct_library" + "module": "aws_cdk.example_construct_library", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index 86301e67b0a0b..1b965874cec02 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -105,7 +105,11 @@ }, "python": { "distName": "aws-cdk.pipelines", - "module": "aws_cdk.pipelines" + "module": "aws_cdk.pipelines", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/region-info/package.json b/packages/@aws-cdk/region-info/package.json index a4f2f01f227de..5ea296f02297d 100644 --- a/packages/@aws-cdk/region-info/package.json +++ b/packages/@aws-cdk/region-info/package.json @@ -23,7 +23,11 @@ }, "python": { "distName": "aws-cdk.region-info", - "module": "aws_cdk.region_info" + "module": "aws_cdk.region_info", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/packages/@aws-cdk/yaml-cfn/package.json b/packages/@aws-cdk/yaml-cfn/package.json index 86fbed1f10197..ac58c869160ba 100644 --- a/packages/@aws-cdk/yaml-cfn/package.json +++ b/packages/@aws-cdk/yaml-cfn/package.json @@ -42,7 +42,11 @@ }, "python": { "distName": "aws-cdk.yaml-cfn", - "module": "aws_cdk.yaml_cfn" + "module": "aws_cdk.yaml_cfn", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] } }, "projectReferences": true diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index ca50e7c03a8de..77fce1a03d423 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -525,8 +525,11 @@ export class JSIIPythonTarget extends ValidationRule { const moduleName = cdkModuleName(pkg.json.name); + // See: https://github.com/aws/jsii/blob/master/docs/configuration.md#configuring-python + expectJSON(this.name, pkg, 'jsii.targets.python.distName', moduleName.python.distName); expectJSON(this.name, pkg, 'jsii.targets.python.module', moduleName.python.module); + expectJSON(this.name, pkg, 'jsii.targets.python.classifiers', ['Framework :: AWS CDK', 'Framework :: AWS CDK :: 1']); } } From fb39803b62bbd0ff62bf421464559c759ef0a923 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 28 Sep 2020 18:33:51 +0200 Subject: [PATCH 23/46] fix(core): write Metadata resource in core framework (#10306) The Metadata resource used to be added by the CLI, which led to a bug. The better, less error-prone way to do it is to have the framework add the metadata resource to the stack template upon synthesis. The resources need to be added just-in-time (before synthesis), because if we do it in the constructor `node.setContext()` will stop working (for the `Stack` already having children). We only add the Metadata resource if we're running via the CLI. If we did not do this, all unit tests everywhere that use `toMatchTemplate()`/`toExactlyMatchTemplate()`/`toMatch()` will break. There are hundreds alone in our codebase, nevermind however many other ones are out there. The consequences of this are that we [still] will not record users who are doing in-memory synthesis. The CLI only does the work when the `runtimeInfo` field of the assembly is filled, which we just never do anymore. However, the code cannot be removed from the CLI because old versions of the framework might still set that field and expect the resource to be added to the template. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../schema/cloud-assembly.version.json | 2 +- packages/@aws-cdk/core/lib/app.ts | 19 ++- packages/@aws-cdk/core/lib/cfn-fn.ts | 28 ++++- .../core/lib/private/metadata-resource.ts | 92 +++++++++++++++ .../@aws-cdk/core/lib/private/synthesis.ts | 37 +++++- packages/@aws-cdk/core/lib/stack.ts | 26 ++++- packages/@aws-cdk/core/lib/stage.ts | 3 - packages/@aws-cdk/core/package.json | 4 +- packages/@aws-cdk/core/test/test.app.ts | 108 ++++++++++++++---- packages/@aws-cdk/core/test/test.rule.ts | 2 +- .../@aws-cdk/core/test/test.runtime-info.ts | 19 +-- packages/@aws-cdk/core/test/test.stack.ts | 18 +++ packages/@aws-cdk/core/test/test.stage.ts | 2 +- packages/@aws-cdk/core/test/test.synthesis.ts | 7 +- packages/@aws-cdk/cx-api/lib/app.ts | 4 +- .../@aws-cdk/cx-api/lib/cloud-assembly.ts | 2 + packages/@aws-cdk/pipelines/test/testutil.ts | 1 - packages/aws-cdk/lib/api/cxapp/exec.ts | 10 +- packages/aws-cdk/lib/settings.ts | 4 +- 19 files changed, 309 insertions(+), 79 deletions(-) create mode 100644 packages/@aws-cdk/core/lib/private/metadata-resource.ts diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json index 42cb403235c06..2211f30276a5e 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"6.0.0"} \ No newline at end of file +{"version":"6.0.0"} diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index cff51ff197ec6..eb095f801ee4f 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -35,11 +35,20 @@ export interface AppProps { readonly stackTraces?: boolean; /** - * Include runtime versioning information in cloud assembly manifest - * @default true runtime info is included unless `aws:cdk:disable-runtime-info` is set in the context. + * Include runtime versioning information in the Stacks of this app + * + * @deprecated use `versionReporting` instead + * @default Value of 'aws:cdk:version-reporting' context key */ readonly runtimeInfo?: boolean; + /** + * Include runtime versioning information in the Stacks of this app + * + * @default Value of 'aws:cdk:version-reporting' context key + */ + readonly analyticsReporting?: boolean; + /** * Additional context values for the application. * @@ -101,8 +110,10 @@ export class App extends Stage { this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); } - if (props.runtimeInfo === false) { - this.node.setContext(cxapi.DISABLE_VERSION_REPORTING, true); + const analyticsReporting = props.analyticsReporting ?? props.runtimeInfo; + + if (analyticsReporting !== undefined) { + this.node.setContext(cxapi.ANALYTICS_REPORTING_ENABLED_CONTEXT, analyticsReporting); } const autoSynth = props.autoSynth !== undefined ? props.autoSynth : cxapi.OUTDIR_ENV in process.env; diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index b05d41968a909..8add880b01674 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -196,12 +196,18 @@ export class Fn { * Returns true if all the specified conditions evaluate to true, or returns * false if any one of the conditions evaluates to false. ``Fn::And`` acts as * an AND operator. The minimum number of conditions that you can include is - * 2, and the maximum is 10. + * 1. * @param conditions conditions to AND * @returns an FnCondition token */ public static conditionAnd(...conditions: ICfnConditionExpression[]): ICfnConditionExpression { - return new FnAnd(...conditions); + if (conditions.length === 0) { + throw new Error('Fn.conditionAnd() needs at least one argument'); + } + if (conditions.length === 1) { + return conditions[0]; + } + return Fn.conditionAnd(..._inGroupsOf(conditions, 10).map(group => new FnAnd(...group))); } /** @@ -249,12 +255,18 @@ export class Fn { * Returns true if any one of the specified conditions evaluate to true, or * returns false if all of the conditions evaluates to false. ``Fn::Or`` acts * as an OR operator. The minimum number of conditions that you can include is - * 2, and the maximum is 10. + * 1. * @param conditions conditions that evaluates to true or false. * @returns an FnCondition token */ public static conditionOr(...conditions: ICfnConditionExpression[]): ICfnConditionExpression { - return new FnOr(...conditions); + if (conditions.length === 0) { + throw new Error('Fn.conditionOr() needs at least one argument'); + } + if (conditions.length === 1) { + return conditions[0]; + } + return Fn.conditionOr(..._inGroupsOf(conditions, 10).map(group => new FnOr(...group))); } /** @@ -748,3 +760,11 @@ class FnJoin implements IResolvable { return minimalCloudFormationJoin(this.delimiter, resolvedValues); } } + +function _inGroupsOf(array: T[], maxGroup: number): T[][] { + const result = new Array(); + for (let i = 0; i < array.length; i += maxGroup) { + result.push(array.slice(i, i + maxGroup)); + } + return result; +} diff --git a/packages/@aws-cdk/core/lib/private/metadata-resource.ts b/packages/@aws-cdk/core/lib/private/metadata-resource.ts new file mode 100644 index 0000000000000..09813ccb87faa --- /dev/null +++ b/packages/@aws-cdk/core/lib/private/metadata-resource.ts @@ -0,0 +1,92 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import { RegionInfo } from '@aws-cdk/region-info'; +import { CfnCondition } from '../cfn-condition'; +import { Fn } from '../cfn-fn'; +import { Aws } from '../cfn-pseudo'; +import { CfnResource } from '../cfn-resource'; +import { Construct } from '../construct-compat'; +import { Lazy } from '../lazy'; +import { Stack } from '../stack'; +import { Token } from '../token'; +import { collectRuntimeInformation } from './runtime-info'; + +/** + * Construct that will render the metadata resource + */ +export class MetadataResource extends Construct { + /** + * Clear the modules cache + * + * The next time the MetadataResource is rendered, it will do a lookup of the + * modules from the NodeJS module cache again. + * + * Used only for unit tests. + */ + public static clearModulesCache() { + this._modulesPropertyCache = undefined; + } + + /** + * Cached version of the _modulesProperty() accessor + * + * No point in calculating this fairly expensive list more than once. + */ + private static _modulesPropertyCache?: string; + + /** + * Calculate the modules property + */ + private static modulesProperty(): string { + if (this._modulesPropertyCache === undefined) { + this._modulesPropertyCache = formatModules(collectRuntimeInformation()); + } + return this._modulesPropertyCache; + } + + constructor(scope: Stack, id: string) { + super(scope, id); + + const metadataServiceExists = Token.isUnresolved(scope.region) || RegionInfo.get(scope.region).cdkMetadataResourceAvailable; + if (metadataServiceExists) { + const resource = new CfnResource(this, 'Default', { + type: 'AWS::CDK::Metadata', + properties: { + Modules: Lazy.stringValue({ produce: () => MetadataResource.modulesProperty() }), + }, + }); + + // In case we don't actually know the region, add a condition to determine it at deploy time + if (Token.isUnresolved(scope.region)) { + const condition = new CfnCondition(this, 'Condition', { + expression: makeCdkMetadataAvailableCondition(), + }); + + // To not cause undue template changes + condition.overrideLogicalId('CDKMetadataAvailable'); + + resource.cfnOptions.condition = condition; + } + } + } +} + +function makeCdkMetadataAvailableCondition() { + return Fn.conditionOr(...RegionInfo.regions + .filter(ri => ri.cdkMetadataResourceAvailable) + .map(ri => Fn.conditionEquals(Aws.REGION, ri.name))); +} + +function formatModules(runtime: cxapi.RuntimeInfo): string { + const modules = new Array(); + + // inject toolkit version to list of modules + const cliVersion = process.env[cxapi.CLI_VERSION_ENV]; + if (cliVersion) { + modules.push(`aws-cdk=${cliVersion}`); + } + + for (const key of Object.keys(runtime.libraries).sort()) { + modules.push(`${key}=${runtime.libraries[key]}`); + } + return modules.join(','); +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/private/synthesis.ts b/packages/@aws-cdk/core/lib/private/synthesis.ts index c67fa14d5b75e..c8243ec03c763 100644 --- a/packages/@aws-cdk/core/lib/private/synthesis.ts +++ b/packages/@aws-cdk/core/lib/private/synthesis.ts @@ -5,6 +5,7 @@ import { Aspects, IAspect } from '../aspect'; import { Construct, IConstruct, SynthesisOptions, ValidationError } from '../construct-compat'; import { Stack } from '../stack'; import { Stage, StageSynthesisOptions } from '../stage'; +import { MetadataResource } from './metadata-resource'; import { prepareApp } from './prepare-app'; import { TreeMetadata } from './tree-metadata'; @@ -14,6 +15,8 @@ export function synthesize(root: IConstruct, options: SynthesisOptions = { }): c invokeAspects(root); + injectMetadataResources(root); + // This is mostly here for legacy purposes as the framework itself does not use prepare anymore. prepareTree(root); @@ -35,9 +38,7 @@ export function synthesize(root: IConstruct, options: SynthesisOptions = { }): c // stacks to add themselves to the synthesized cloud assembly. synthesizeTree(root, builder); - return builder.buildAssembly({ - runtimeInfo: options.runtimeInfo, - }); + return builder.buildAssembly(); } /** @@ -110,6 +111,36 @@ function prepareTree(root: IConstruct) { visit(root, 'post', construct => construct.onPrepare()); } +/** + * Find all stacks and add Metadata Resources to all of them + * + * There is no good generic place to do this. Can't do it in the constructor + * (because adding a child construct makes it impossible to set context on the + * node), and the generic prepare phase is deprecated. + * + * Only do this on [parent] stacks (not nested stacks), don't do this when + * disabled by the user. + * + * Also, only when running via the CLI. If we do it unconditionally, + * all unit tests everywhere are going to break massively. I've spent a day + * fixing our own, but downstream users would be affected just as badly. + * + * Stop at Assembly boundaries. + */ +function injectMetadataResources(root: IConstruct) { + visit(root, 'post', construct => { + if (!Stack.isStack(construct) || !construct._versionReportingEnabled) { return; } + + // Because of https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/assert/lib/synth-utils.ts#L74 + // synthesize() may be called more than once on a stack in unit tests, and the below would break + // if we execute it a second time. Guard against the constructs already existing. + const CDKMetadata = 'CDKMetadata'; + if (construct.node.tryFindChild(CDKMetadata)) { return; } + + new MetadataResource(construct, CDKMetadata); + }); +} + /** * Synthesize children in post-order into the given builder * diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 68b82dadfbb7d..8d9c57fe91942 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -127,6 +127,14 @@ export interface StackProps { * @default false */ readonly terminationProtection?: boolean; + + /** + * Include runtime versioning information in this Stack + * + * @default `analyticsReporting` setting of containing `App`, or value of + * 'aws:cdk:version-reporting' context key + */ + readonly analyticsReporting?: boolean; } /** @@ -281,6 +289,15 @@ export class Stack extends Construct implements ITaggable { */ public readonly synthesizer: IStackSynthesizer; + /** + * Whether version reporting is enabled for this stack + * + * Controls whether the CDK Metadata resource is injected + * + * @internal + */ + public readonly _versionReportingEnabled: boolean; + /** * Logical ID generation strategy */ @@ -370,6 +387,10 @@ export class Stack extends Construct implements ITaggable { this.templateFile = `${this.artifactId}.template.json`; + // Not for nested stacks + this._versionReportingEnabled = (props.analyticsReporting ?? this.node.tryGetContext(cxapi.ANALYTICS_REPORTING_ENABLED_CONTEXT)) + && !this.nestedStackParent; + this.synthesizer = props.synthesizer ?? (newStyleSynthesisContext ? new DefaultStackSynthesizer() : new LegacyStackSynthesizer()); @@ -722,10 +743,11 @@ export class Stack extends Construct implements ITaggable { // this right now, so some parts still happen here. const builder = session.assembly; + const template = this._toCloudFormation(); + // write the CloudFormation template as a JSON file const outPath = path.join(builder.outdir, this.templateFile); - const text = JSON.stringify(this._toCloudFormation(), undefined, 2); - fs.writeFileSync(outPath, text); + fs.writeFileSync(outPath, JSON.stringify(template, undefined, 2)); for (const ctx of this._missingContext) { builder.addMissing(ctx); diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index 94296a6d167ee..bf18e89d89313 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -2,7 +2,6 @@ import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Node } from 'constructs'; import { Construct } from './construct-compat'; import { Environment } from './environment'; -import { collectRuntimeInformation } from './private/runtime-info'; import { synthesize } from './private/synthesis'; /** @@ -172,10 +171,8 @@ export class Stage extends Construct { */ public synth(options: StageSynthesisOptions = { }): cxapi.CloudAssembly { if (!this.assembly || options.force) { - const runtimeInfo = this.node.tryGetContext(cxapi.DISABLE_VERSION_REPORTING) ? undefined : collectRuntimeInformation(); this.assembly = synthesize(this, { skipValidation: options.skipValidation, - runtimeInfo, }); } diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index ebf59da5cb25f..d3cf4a0a1c656 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -178,6 +178,7 @@ "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.4", "fs-extra": "^9.0.1", "minimatch": "^3.0.4" @@ -190,7 +191,8 @@ "peerDependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "constructs": "^3.0.4" + "constructs": "^3.0.4", + "@aws-cdk/region-info": "0.0.0" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" diff --git a/packages/@aws-cdk/core/test/test.app.ts b/packages/@aws-cdk/core/test/test.app.ts index 3feeae07829a6..ac14bcff7e620 100644 --- a/packages/@aws-cdk/core/test/test.app.ts +++ b/packages/@aws-cdk/core/test/test.app.ts @@ -4,10 +4,10 @@ import { Test } from 'nodeunit'; import { CfnResource, Construct, Stack, StackProps } from '../lib'; import { Annotations } from '../lib/annotations'; import { App, AppProps } from '../lib/app'; +import { MetadataResource } from '../lib/private/metadata-resource'; function withApp(props: AppProps, block: (app: App) => void): cxapi.CloudAssembly { const app = new App({ - runtimeInfo: false, stackTraces: false, ...props, }); @@ -245,11 +245,13 @@ export = { test.done(); }, - 'runtime library versions disabled'(test: Test) { - const context: any = {}; - context[cxapi.DISABLE_VERSION_REPORTING] = true; - - const assembly = withApp(context, app => { + /** + * Runtime library versions are now synthesized into the Stack templates directly + * + * The are not emitted into Cloud Assembly metadata anymore + */ + 'runtime library versions are not emitted in asm anymore'(test: Test) { + const assembly = withApp({ analyticsReporting: true }, app => { const stack = new Stack(app, 'stack1'); new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); }); @@ -259,52 +261,83 @@ export = { }, 'runtime library versions'(test: Test) { - const response = withApp({ runtimeInfo: true }, app => { + MetadataResource.clearModulesCache(); + + const response = withApp({ analyticsReporting: true }, app => { const stack = new Stack(app, 'stack1'); new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); }); - const libs = (response.runtime && response.runtime.libraries) || {}; + const stackTemplate = response.getStackByName('stack1').template; + const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); // eslint-disable-next-line @typescript-eslint/no-require-imports const version = require('../package.json').version; test.deepEqual(libs['@aws-cdk/core'], version); test.deepEqual(libs['@aws-cdk/cx-api'], version); test.deepEqual(libs['jsii-runtime'], `node.js/${process.version}`); + + test.done(); + }, + + 'CDK version'(test: Test) { + MetadataResource.clearModulesCache(); + + withCliVersion(() => { + const response = withApp({ analyticsReporting: true }, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); + + const stackTemplate = response.getStackByName('stack1').template; + const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + test.deepEqual(libs['aws-cdk'], '1.2.3'); + }); + test.done(); }, 'jsii-runtime version loaded from JSII_AGENT'(test: Test) { process.env.JSII_AGENT = 'Java/1.2.3.4'; + MetadataResource.clearModulesCache(); - const response = withApp({ runtimeInfo: true }, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); + withCliVersion(() => { + const response = withApp({ analyticsReporting: true }, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); - const libs = (response.runtime && response.runtime.libraries) || {}; - test.deepEqual(libs['jsii-runtime'], 'Java/1.2.3.4'); + const stackTemplate = response.getStackByName('stack1').template; + const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); + + test.deepEqual(libs['jsii-runtime'], 'Java/1.2.3.4'); + }); delete process.env.JSII_AGENT; test.done(); }, 'version reporting includes only @aws-cdk, aws-cdk and jsii libraries'(test: Test) { - const response = withApp({ runtimeInfo: true }, app => { + MetadataResource.clearModulesCache(); + + const response = withApp({ analyticsReporting: true }, app => { const stack = new Stack(app, 'stack1'); new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); }); - const libs = (response.runtime && response.runtime.libraries) || {}; + const stackTemplate = response.getStackByName('stack1').template; + const libs = parseModules(stackTemplate.Resources?.CDKMetadata?.Properties?.Modules); + const libNames = Object.keys(libs).sort(); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const version = require('../package.json').version; - test.deepEqual(libs, { - '@aws-cdk/core': version, - '@aws-cdk/cx-api': version, - '@aws-cdk/cloud-assembly-schema': version, - 'jsii-runtime': `node.js/${process.version}`, - }); + test.deepEqual(libNames, [ + '@aws-cdk/cloud-assembly-schema', + '@aws-cdk/core', + '@aws-cdk/cx-api', + '@aws-cdk/region-info', + 'jsii-runtime', + ]); test.done(); }, @@ -385,3 +418,30 @@ class MyConstruct extends Construct { new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.tryGetContext('ctx1') } }); } } + +function parseModules(x?: string): Record { + if (x === undefined) { return {}; } + + const ret: Record = {}; + for (const clause of x.split(',')) { + const [key, value] = clause.split('='); + if (key !== undefined && value !== undefined) { + ret[key] = value; + } + } + return ret; +} + +/** + * Set the CLI_VERSION_ENV environment variable + * + * This is necessary to get the Stack to emit the metadata resource + */ +function withCliVersion(block: () => A): A { + process.env[cxapi.CLI_VERSION_ENV] = '1.2.3'; + try { + return block(); + } finally { + delete process.env[cxapi.CLI_VERSION_ENV]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/test.rule.ts b/packages/@aws-cdk/core/test/test.rule.ts index 62ef0611c9a38..8385183333855 100644 --- a/packages/@aws-cdk/core/test/test.rule.ts +++ b/packages/@aws-cdk/core/test/test.rule.ts @@ -19,7 +19,7 @@ export = { AssertDescription: 'lhs equals rhs', }, { - Assert: { 'Fn::Not': [{ 'Fn::And': [{ 'Fn::Contains': [['hello', 'world'], 'world'] }] }] }, + Assert: { 'Fn::Not': [{ 'Fn::Contains': [['hello', 'world'], 'world'] }] }, AssertDescription: 'some assertion', }, ], diff --git a/packages/@aws-cdk/core/test/test.runtime-info.ts b/packages/@aws-cdk/core/test/test.runtime-info.ts index 762348378d5ab..46d8e42f85048 100644 --- a/packages/@aws-cdk/core/test/test.runtime-info.ts +++ b/packages/@aws-cdk/core/test/test.runtime-info.ts @@ -21,14 +21,7 @@ export = { const runtimeInfo = collectRuntimeInformation(); // eslint-disable-next-line @typescript-eslint/no-require-imports - const version = require('../package.json').version; - test.deepEqual(runtimeInfo.libraries, { - '@aws-cdk/core': version, - '@aws-cdk/cx-api': version, - '@aws-cdk/cloud-assembly-schema': version, - '@aws-solutions-konstruk/foo': mockVersion, - 'jsii-runtime': `node.js/${process.version}`, - }); + test.deepEqual(runtimeInfo.libraries['@aws-solutions-konstruk/foo'], mockVersion); test.done(); }, @@ -48,15 +41,7 @@ export = { const runtimeInfo = collectRuntimeInformation(); // eslint-disable-next-line @typescript-eslint/no-require-imports - const version = require('../package.json').version; - test.deepEqual(runtimeInfo.libraries, { - '@aws-cdk/core': version, - '@aws-cdk/cx-api': version, - '@aws-cdk/cloud-assembly-schema': version, - '@aws-solutions-konstruk/foo': mockVersion, // picks up the module from the other test. - 'aws-rfdk': mockVersion, - 'jsii-runtime': `node.js/${process.version}`, - }); + test.deepEqual(runtimeInfo.libraries['aws-rfdk'], mockVersion); test.done(); }, diff --git a/packages/@aws-cdk/core/test/test.stack.ts b/packages/@aws-cdk/core/test/test.stack.ts index 49a7ac671b0ad..409c096da3610 100644 --- a/packages/@aws-cdk/core/test/test.stack.ts +++ b/packages/@aws-cdk/core/test/test.stack.ts @@ -993,6 +993,24 @@ export = { test.done(); }, + + 'version reporting can be configured on the app'(test: Test) { + const app = new App({ analyticsReporting: true }); + test.ok(new Stack(app, 'Stack')._versionReportingEnabled); + test.done(); + }, + + 'version reporting can be configured with context'(test: Test) { + const app = new App({ context: { 'aws:cdk:version-reporting': true } }); + test.ok(new Stack(app, 'Stack')._versionReportingEnabled); + test.done(); + }, + + 'version reporting can be configured on the stack'(test: Test) { + const app = new App(); + test.ok(new Stack(app, 'Stack', { analyticsReporting: true })._versionReportingEnabled); + test.done(); + }, }; class StackWithPostProcessor extends Stack { diff --git a/packages/@aws-cdk/core/test/test.stage.ts b/packages/@aws-cdk/core/test/test.stage.ts index f816458c6199e..52fe1c14c4d21 100644 --- a/packages/@aws-cdk/core/test/test.stage.ts +++ b/packages/@aws-cdk/core/test/test.stage.ts @@ -209,7 +209,7 @@ export = { 'Assemblies can be deeply nested'(test: Test) { // GIVEN - const app = new App({ runtimeInfo: false, treeMetadata: false }); + const app = new App({ treeMetadata: false }); const level1 = new Stage(app, 'StageLevel1'); const level2 = new Stage(level1, 'StageLevel2'); diff --git a/packages/@aws-cdk/core/test/test.synthesis.ts b/packages/@aws-cdk/core/test/test.synthesis.ts index 02b96b733d3c1..3d8c0fad35cf1 100644 --- a/packages/@aws-cdk/core/test/test.synthesis.ts +++ b/packages/@aws-cdk/core/test/test.synthesis.ts @@ -2,16 +2,11 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; import * as cdk from '../lib'; function createModernApp() { - return new cdk.App({ - context: { - [cxapi.DISABLE_VERSION_REPORTING]: 'true', // for test reproducibility - }, - }); + return new cdk.App(); } export = { diff --git a/packages/@aws-cdk/cx-api/lib/app.ts b/packages/@aws-cdk/cx-api/lib/app.ts index 41283679f0db2..24be06efc6797 100644 --- a/packages/@aws-cdk/cx-api/lib/app.ts +++ b/packages/@aws-cdk/cx-api/lib/app.ts @@ -11,9 +11,9 @@ export const PATH_METADATA_ENABLE_CONTEXT = 'aws:cdk:enable-path-metadata'; /** - * Disable the collection and reporting of version information. + * Enable the collection and reporting of version information. */ -export const DISABLE_VERSION_REPORTING = 'aws:cdk:disable-version-reporting'; +export const ANALYTICS_REPORTING_ENABLED_CONTEXT = 'aws:cdk:version-reporting'; /** * If this is set, asset staging is disabled. This means that assets will not be copied to diff --git a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts index 8155d8a2c33ca..05210ed891eed 100644 --- a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts +++ b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts @@ -369,6 +369,8 @@ export interface AssemblyBuildOptions { /** * Include the specified runtime information (module versions) in manifest. * @default - if this option is not specified, runtime info will not be included + * @deprecated All template modifications that should result from this should + * have already been inserted into the template. */ readonly runtimeInfo?: RuntimeInfo; } diff --git a/packages/@aws-cdk/pipelines/test/testutil.ts b/packages/@aws-cdk/pipelines/test/testutil.ts index 821b795105365..d0573130e3ede 100644 --- a/packages/@aws-cdk/pipelines/test/testutil.ts +++ b/packages/@aws-cdk/pipelines/test/testutil.ts @@ -21,7 +21,6 @@ export class TestApp extends App { }, stackTraces: false, autoSynth: false, - runtimeInfo: false, treeMetadata: false, ...props, }); diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 735e9a7622e19..dc74967e948a3 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -16,25 +16,21 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom await populateDefaultEnvironmentIfNeeded(aws, env); const pathMetadata: boolean = config.settings.get(['pathMetadata']) ?? true; - if (pathMetadata) { context[cxapi.PATH_METADATA_ENABLE_CONTEXT] = true; } const assetMetadata: boolean = config.settings.get(['assetMetadata']) ?? true; - if (assetMetadata) { context[cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT] = true; } const versionReporting: boolean = config.settings.get(['versionReporting']) ?? true; - - if (!versionReporting) { - context[cxapi.DISABLE_VERSION_REPORTING] = true; - } + if (versionReporting) { context[cxapi.ANALYTICS_REPORTING_ENABLED_CONTEXT] = true; } + // We need to keep on doing this for framework version from before this flag was deprecated. + if (!versionReporting) { context['aws:cdk:disable-version-reporting'] = true; } const stagingEnabled = config.settings.get(['staging']) ?? true; - if (!stagingEnabled) { context[cxapi.DISABLE_ASSET_STAGING_CONTEXT] = true; } diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index af9193d9a6456..b091ba493c6dd 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -54,7 +54,7 @@ export class Configuration { public context = new Context(); public readonly defaultConfig = new Settings({ - versionReporting: true, + analyticsReporting: true, pathMetadata: true, output: 'cdk.out', }); @@ -239,7 +239,7 @@ export class Settings { bucketName: argv.bootstrapBucketName, kmsKeyId: argv.bootstrapKmsKeyId, }, - versionReporting: argv.versionReporting, + analyticsReporting: argv.versionReporting, staging: argv.staging, output: argv.output, progress: argv.progress, From 37723e91a68583eeb442ef2f1716b7de6c653fa9 Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Mon, 28 Sep 2020 19:55:36 -0700 Subject: [PATCH 24/46] chore(cfnspec): add junit.xml to ignore files (#10566) When generating a new module, add `junit.xml` to `.npmignore` and `.gitignore`, this is required by `aws-lint` rule. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index ab0cd06c44861..96d958fa11710 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -211,6 +211,7 @@ async function main() { 'nyc.config.js', '!.eslintrc.js', '!jest.config.js', + 'junit.xml', ]); await write('.npmignore', [ @@ -237,6 +238,7 @@ async function main() { '', '.eslintrc.js', 'jest.config.js', + 'junit.xml', ]); await write('lib/index.ts', [ From a76428bd8559e497cd631d17ff9af2a01792b4c2 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 29 Sep 2020 07:03:34 +0100 Subject: [PATCH 25/46] chore: migrate constructs to use "constructs" module (#10506) This commits expands on a previous [commit]. A few CDK modules have been migrated to use the `Construct` and `IConstruct` from the "constructs" module instead of the "@aws-cdk/core" module. Migrating modules to use the "constructs" module, prevents merge conflicts from arising in these files when the construct compatibility layer in "@aws-cdk/core" module is removed in the v2 branch. [commit]: https://github.com/aws/aws-cdk/commit/c17969926a2644fc9607dd9b5f105450ed64309a ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/alexa-ask/package.json | 5 ++++- .../@aws-cdk/aws-accessanalyzer/package.json | 5 ++++- packages/@aws-cdk/aws-acmpca/package.json | 5 ++++- packages/@aws-cdk/aws-amazonmq/package.json | 5 ++++- packages/@aws-cdk/aws-apigateway/lib/api-key.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/authorizer.ts | 3 ++- .../aws-apigateway/lib/authorizers/lambda.ts | 3 ++- .../aws-apigateway/lib/base-path-mapping.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/deployment.ts | 5 +++-- .../@aws-cdk/aws-apigateway/lib/domain-name.ts | 3 ++- .../aws-apigateway/lib/gateway-response.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/lambda-api.ts | 4 ++-- packages/@aws-cdk/aws-apigateway/lib/method.ts | 3 ++- packages/@aws-cdk/aws-apigateway/lib/model.ts | 3 ++- .../aws-apigateway/lib/rate-limited-api-key.ts | 3 ++- .../aws-apigateway/lib/requestvalidator.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/resource.ts | 3 ++- packages/@aws-cdk/aws-apigateway/lib/restapi.ts | 3 ++- packages/@aws-cdk/aws-apigateway/lib/stage.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 3 ++- .../@aws-cdk/aws-apigateway/lib/vpc-link.ts | 3 ++- packages/@aws-cdk/aws-apigateway/package.json | 5 ++++- .../aws-apigatewayv2/lib/common/domain-name.ts | 3 ++- .../aws-apigatewayv2/lib/http/api-mapping.ts | 3 ++- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 3 ++- .../aws-apigatewayv2/lib/http/integration.ts | 9 +++++++-- .../@aws-cdk/aws-apigatewayv2/lib/http/route.ts | 3 ++- .../@aws-cdk/aws-apigatewayv2/lib/http/stage.ts | 3 ++- packages/@aws-cdk/aws-apigatewayv2/package.json | 5 ++++- packages/@aws-cdk/aws-appconfig/package.json | 5 ++++- .../lib/base-scalable-attribute.ts | 3 ++- .../lib/scalable-target.ts | 3 ++- .../lib/step-scaling-action.ts | 3 ++- .../lib/step-scaling-policy.ts | 3 ++- .../lib/target-tracking-scaling-policy.ts | 3 ++- .../aws-applicationautoscaling/package.json | 5 ++++- .../aws-applicationinsights/package.json | 5 ++++- packages/@aws-cdk/aws-appmesh/lib/mesh.ts | 8 ++++---- packages/@aws-cdk/aws-appmesh/lib/route.ts | 10 +++++----- .../@aws-cdk/aws-appmesh/lib/virtual-node.ts | 10 +++++----- .../@aws-cdk/aws-appmesh/lib/virtual-router.ts | 12 ++++++------ .../@aws-cdk/aws-appmesh/lib/virtual-service.ts | 10 +++++----- packages/@aws-cdk/aws-appmesh/package.json | 5 ++++- packages/@aws-cdk/aws-appstream/package.json | 5 ++++- packages/@aws-cdk/aws-athena/package.json | 5 ++++- .../aws-autoscaling-common/package.json | 5 +++++ .../aws-autoscaling/lib/auto-scaling-group.ts | 3 ++- .../aws-autoscaling/lib/lifecycle-hook.ts | 3 ++- .../aws-autoscaling/lib/scheduled-action.ts | 3 ++- .../aws-autoscaling/lib/step-scaling-action.ts | 13 +++++++------ .../aws-autoscaling/lib/step-scaling-policy.ts | 13 +++++++------ .../lib/target-tracking-scaling-policy.ts | 11 ++++++----- packages/@aws-cdk/aws-autoscaling/package.json | 5 ++++- .../@aws-cdk/aws-autoscalingplans/package.json | 5 ++++- packages/@aws-cdk/aws-budgets/package.json | 5 ++++- packages/@aws-cdk/aws-cassandra/package.json | 5 ++++- packages/@aws-cdk/aws-ce/package.json | 5 ++++- .../aws-certificatemanager/lib/certificate.ts | 3 ++- .../lib/dns-validated-certificate.ts | 3 ++- .../aws-certificatemanager/package.json | 5 ++++- .../lib/slack-channel-configuration.ts | 7 ++++--- packages/@aws-cdk/aws-chatbot/package.json | 5 ++++- packages/@aws-cdk/aws-cloud9/lib/environment.ts | 5 +++-- packages/@aws-cdk/aws-cloud9/package.json | 5 ++++- .../aws-cloudfront-origins/package.json | 5 ++++- .../@aws-cdk/aws-cloudfront/lib/distribution.ts | 13 +++++++++---- .../lib/origin_access_identity.ts | 7 ++++--- .../aws-cloudfront/lib/web_distribution.ts | 5 +++-- packages/@aws-cdk/aws-cloudfront/package.json | 5 ++++- .../@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts | 3 ++- packages/@aws-cdk/aws-cloudtrail/package.json | 5 ++++- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 3 ++- .../aws-cloudwatch/lib/composite-alarm.ts | 3 ++- .../@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 3 ++- packages/@aws-cdk/aws-cloudwatch/package.json | 5 ++++- .../@aws-cdk/aws-codecommit/lib/repository.ts | 3 ++- packages/@aws-cdk/aws-codecommit/package.json | 5 ++++- .../aws-codedeploy/lib/ecs/application.ts | 3 ++- .../aws-codedeploy/lib/ecs/deployment-config.ts | 4 ++-- .../aws-codedeploy/lib/ecs/deployment-group.ts | 6 +++--- .../aws-codedeploy/lib/lambda/application.ts | 3 ++- .../lib/lambda/deployment-config.ts | 4 ++-- .../lib/lambda/deployment-group.ts | 8 ++++---- .../aws-codedeploy/lib/server/application.ts | 3 ++- .../lib/server/deployment-config.ts | 5 +++-- .../lib/server/deployment-group.ts | 9 +++++---- packages/@aws-cdk/aws-codedeploy/package.json | 5 ++++- .../aws-codeguruprofiler/lib/profiling-group.ts | 3 ++- .../@aws-cdk/aws-codeguruprofiler/package.json | 8 ++++++-- .../@aws-cdk/aws-codegurureviewer/package.json | 5 ++++- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 5 +++-- packages/@aws-cdk/aws-codepipeline/package.json | 5 ++++- .../aws-codestar/lib/github-repository.ts | 3 ++- packages/@aws-cdk/aws-codestar/package.json | 5 ++++- .../aws-codestarconnections/package.json | 5 ++++- .../aws-codestarnotifications/package.json | 5 ++++- packages/@aws-cdk/aws-datapipeline/package.json | 5 ++++- packages/@aws-cdk/aws-dax/package.json | 5 ++++- packages/@aws-cdk/aws-detective/package.json | 5 ++++- .../@aws-cdk/aws-directoryservice/package.json | 5 ++++- packages/@aws-cdk/aws-dlm/package.json | 5 ++++- packages/@aws-cdk/aws-dms/package.json | 5 ++++- packages/@aws-cdk/aws-docdb/lib/cluster.ts | 3 ++- .../@aws-cdk/aws-docdb/lib/database-secret.ts | 3 ++- packages/@aws-cdk/aws-docdb/lib/instance.ts | 6 +++--- .../@aws-cdk/aws-docdb/lib/parameter-group.ts | 3 ++- packages/@aws-cdk/aws-docdb/package.json | 5 ++++- packages/@aws-cdk/aws-ec2/lib/bastion-host.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/instance.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/network-acl.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/security-group.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/util.ts | 4 ++-- packages/@aws-cdk/aws-ec2/lib/volume.ts | 14 ++++++++++---- .../aws-ec2/lib/vpc-endpoint-service.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 3 ++- packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts | 13 +++++++++---- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 17 +++++++++++------ packages/@aws-cdk/aws-ec2/lib/vpn.ts | 5 +++-- packages/@aws-cdk/aws-ec2/package.json | 5 ++++- packages/@aws-cdk/aws-efs/lib/access-point.ts | 3 ++- .../@aws-cdk/aws-efs/lib/efs-file-system.ts | 3 ++- packages/@aws-cdk/aws-efs/package.json | 5 ++++- packages/@aws-cdk/aws-elasticache/package.json | 5 ++++- .../@aws-cdk/aws-elasticbeanstalk/package.json | 5 ++++- .../lib/load-balancer.ts | 3 ++- .../aws-elasticloadbalancing/package.json | 5 ++++- .../package.json | 5 ++++- .../lib/alb/application-listener-certificate.ts | 3 ++- .../lib/alb/application-listener-rule.ts | 3 ++- .../lib/alb/application-listener.ts | 3 ++- .../lib/alb/application-load-balancer.ts | 3 ++- .../lib/alb/application-target-group.ts | 5 +++-- .../lib/nlb/network-listener.ts | 3 ++- .../lib/nlb/network-load-balancer.ts | 3 ++- .../lib/nlb/network-target-group.ts | 7 ++++--- .../lib/shared/base-listener.ts | 3 ++- .../lib/shared/base-load-balancer.ts | 3 ++- .../lib/shared/base-target-group.ts | 3 ++- .../lib/shared/imported.ts | 3 ++- .../aws-elasticloadbalancingv2/package.json | 5 ++++- .../@aws-cdk/aws-elasticsearch/package.json | 5 ++++- packages/@aws-cdk/aws-emr/package.json | 5 ++++- packages/@aws-cdk/aws-events/lib/event-bus.ts | 3 ++- packages/@aws-cdk/aws-events/lib/rule.ts | 5 +++-- packages/@aws-cdk/aws-events/package.json | 5 ++++- packages/@aws-cdk/aws-eventschemas/package.json | 5 ++++- packages/@aws-cdk/aws-fms/package.json | 5 ++++- .../@aws-cdk/aws-fsx/lib/lustre-file-system.ts | 3 ++- packages/@aws-cdk/aws-fsx/package.json | 5 ++++- packages/@aws-cdk/aws-gamelift/package.json | 5 ++++- packages/@aws-cdk/aws-glue/lib/database.ts | 3 ++- packages/@aws-cdk/aws-glue/lib/table.ts | 3 ++- packages/@aws-cdk/aws-glue/package.json | 5 ++++- packages/@aws-cdk/aws-greengrass/package.json | 5 ++++- packages/@aws-cdk/aws-guardduty/package.json | 5 ++++- packages/@aws-cdk/aws-iam/lib/group.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/lazy-role.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/managed-policy.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/oidc-provider.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/policy.ts | 3 ++- .../aws-iam/lib/private/immutable-role.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/role.ts | 5 +++-- .../@aws-cdk/aws-iam/lib/unknown-principal.ts | 5 +++-- packages/@aws-cdk/aws-iam/lib/user.ts | 3 ++- packages/@aws-cdk/aws-iam/lib/util.ts | 3 ++- packages/@aws-cdk/aws-iam/package.json | 3 +++ packages/@aws-cdk/aws-imagebuilder/package.json | 5 ++++- packages/@aws-cdk/aws-inspector/package.json | 5 ++++- packages/@aws-cdk/aws-iot/package.json | 5 ++++- packages/@aws-cdk/aws-iot1click/package.json | 5 ++++- packages/@aws-cdk/aws-iotanalytics/package.json | 5 ++++- packages/@aws-cdk/aws-iotevents/package.json | 5 ++++- .../@aws-cdk/aws-iotthingsgraph/package.json | 5 ++++- packages/@aws-cdk/aws-kendra/package.json | 5 ++++- packages/@aws-cdk/aws-kinesis/lib/stream.ts | 3 ++- packages/@aws-cdk/aws-kinesis/package.json | 5 ++++- .../@aws-cdk/aws-kinesisanalytics/package.json | 5 ++++- .../@aws-cdk/aws-kinesisfirehose/package.json | 5 ++++- packages/@aws-cdk/aws-kms/lib/alias.ts | 3 ++- packages/@aws-cdk/aws-kms/lib/key.ts | 9 +++++---- packages/@aws-cdk/aws-kms/package.json | 5 ++++- .../@aws-cdk/aws-lakeformation/package.json | 5 ++++- packages/@aws-cdk/aws-lambda/lib/alias.ts | 2 +- .../aws-lambda/lib/event-invoke-config.ts | 3 ++- .../aws-lambda/lib/event-source-mapping.ts | 5 +++-- packages/@aws-cdk/aws-lambda/lib/function.ts | 3 ++- .../@aws-cdk/aws-lambda/lib/lambda-version.ts | 3 ++- packages/@aws-cdk/aws-lambda/lib/layers.ts | 3 ++- .../@aws-cdk/aws-lambda/lib/log-retention.ts | 4 ++-- .../@aws-cdk/aws-lambda/lib/singleton-lambda.ts | 3 ++- packages/@aws-cdk/aws-lambda/lib/util.ts | 2 +- packages/@aws-cdk/aws-lambda/package.json | 5 ++++- .../aws-logs/lib/cross-account-destination.ts | 3 ++- packages/@aws-cdk/aws-logs/lib/log-group.ts | 3 ++- packages/@aws-cdk/aws-logs/lib/log-retention.ts | 5 +++-- packages/@aws-cdk/aws-logs/lib/log-stream.ts | 3 ++- packages/@aws-cdk/aws-logs/lib/metric-filter.ts | 3 ++- .../aws-logs/lib/subscription-filter.ts | 9 +++++++-- packages/@aws-cdk/aws-logs/package.json | 5 ++++- packages/@aws-cdk/aws-macie/package.json | 5 ++++- .../@aws-cdk/aws-managedblockchain/package.json | 5 ++++- packages/@aws-cdk/aws-mediaconvert/package.json | 5 ++++- packages/@aws-cdk/aws-medialive/package.json | 5 ++++- packages/@aws-cdk/aws-mediastore/package.json | 5 ++++- packages/@aws-cdk/aws-msk/package.json | 5 ++++- packages/@aws-cdk/aws-neptune/package.json | 5 ++++- .../@aws-cdk/aws-networkmanager/package.json | 5 ++++- packages/@aws-cdk/aws-opsworks/package.json | 5 ++++- packages/@aws-cdk/aws-opsworkscm/package.json | 5 ++++- packages/@aws-cdk/aws-pinpoint/package.json | 5 ++++- .../@aws-cdk/aws-pinpointemail/package.json | 5 ++++- packages/@aws-cdk/aws-qldb/package.json | 5 ++++- packages/@aws-cdk/aws-ram/package.json | 5 ++++- packages/@aws-cdk/aws-redshift/lib/cluster.ts | 3 ++- .../aws-redshift/lib/database-secret.ts | 2 +- .../aws-redshift/lib/parameter-group.ts | 3 ++- .../@aws-cdk/aws-redshift/lib/subnet-group.ts | 3 ++- packages/@aws-cdk/aws-redshift/package.json | 5 ++++- .../@aws-cdk/aws-resourcegroups/package.json | 5 ++++- packages/@aws-cdk/aws-robomaker/package.json | 5 ++++- .../@aws-cdk/aws-route53/lib/hosted-zone.ts | 3 ++- packages/@aws-cdk/aws-route53/lib/record-set.ts | 3 ++- packages/@aws-cdk/aws-route53/package.json | 5 ++++- .../@aws-cdk/aws-route53resolver/package.json | 5 ++++- packages/@aws-cdk/aws-s3-assets/lib/asset.ts | 3 ++- packages/@aws-cdk/aws-s3-assets/package.json | 5 ++++- .../aws-s3-deployment/lib/bucket-deployment.ts | 3 ++- .../@aws-cdk/aws-s3-deployment/package.json | 5 ++++- packages/@aws-cdk/aws-sagemaker/package.json | 5 ++++- packages/@aws-cdk/aws-sam/package.json | 5 ++++- packages/@aws-cdk/aws-sdb/package.json | 5 ++++- .../@aws-cdk/aws-secretsmanager/lib/policy.ts | 3 ++- .../aws-secretsmanager/lib/rotation-schedule.ts | 3 ++- .../aws-secretsmanager/lib/secret-rotation.ts | 9 +++++++-- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 3 ++- .../@aws-cdk/aws-secretsmanager/package.json | 5 ++++- packages/@aws-cdk/aws-securityhub/package.json | 5 ++++- .../@aws-cdk/aws-servicecatalog/package.json | 5 ++++- .../lib/alias-target-instance.ts | 4 ++-- .../aws-servicediscovery/lib/cname-instance.ts | 4 ++-- .../aws-servicediscovery/lib/http-namespace.ts | 3 ++- .../aws-servicediscovery/lib/ip-instance.ts | 4 ++-- .../aws-servicediscovery/lib/non-ip-instance.ts | 4 ++-- .../lib/private-dns-namespace.ts | 3 ++- .../lib/public-dns-namespace.ts | 3 ++- .../aws-servicediscovery/lib/service.ts | 3 ++- .../@aws-cdk/aws-servicediscovery/package.json | 5 ++++- packages/@aws-cdk/aws-sns/lib/policy.ts | 3 ++- packages/@aws-cdk/aws-sns/lib/subscription.ts | 3 ++- packages/@aws-cdk/aws-sns/lib/topic.ts | 3 ++- packages/@aws-cdk/aws-sns/package.json | 5 ++++- packages/@aws-cdk/aws-sqs/lib/policy.ts | 3 ++- packages/@aws-cdk/aws-sqs/lib/queue.ts | 3 ++- packages/@aws-cdk/aws-sqs/package.json | 5 ++++- packages/@aws-cdk/aws-ssm/lib/parameter.ts | 7 ++++--- packages/@aws-cdk/aws-ssm/lib/util.ts | 3 ++- packages/@aws-cdk/aws-ssm/package.json | 5 ++++- packages/@aws-cdk/aws-sso/package.json | 5 ++++- .../@aws-cdk/aws-stepfunctions/lib/activity.ts | 3 ++- .../aws-stepfunctions/lib/state-machine.ts | 3 ++- .../aws-stepfunctions/lib/states/choice.ts | 4 ++-- .../lib/states/custom-state.ts | 4 ++-- .../aws-stepfunctions/lib/states/fail.ts | 4 ++-- .../aws-stepfunctions/lib/states/map.ts | 4 ++-- .../aws-stepfunctions/lib/states/parallel.ts | 4 ++-- .../aws-stepfunctions/lib/states/pass.ts | 4 ++-- .../aws-stepfunctions/lib/states/state.ts | 7 ++++--- .../aws-stepfunctions/lib/states/succeed.ts | 4 ++-- .../aws-stepfunctions/lib/states/task-base.ts | 3 ++- .../aws-stepfunctions/lib/states/task.ts | 3 ++- .../aws-stepfunctions/lib/states/wait.ts | 3 ++- .../@aws-cdk/aws-stepfunctions/package.json | 5 ++++- packages/@aws-cdk/aws-synthetics/lib/canary.ts | 3 ++- packages/@aws-cdk/aws-synthetics/package.json | 5 ++++- packages/@aws-cdk/aws-transfer/package.json | 5 ++++- packages/@aws-cdk/aws-waf/package.json | 5 ++++- packages/@aws-cdk/aws-wafregional/package.json | 5 ++++- packages/@aws-cdk/aws-wafv2/package.json | 5 ++++- packages/@aws-cdk/aws-workspaces/package.json | 5 ++++- .../build-tools/create-missing-libraries.ts | 3 +++ packages/@aws-cdk/core/lib/annotations.ts | 6 +++--- packages/@aws-cdk/core/lib/context-provider.ts | 4 ++-- packages/@aws-cdk/core/lib/private/resolve.ts | 8 ++++++-- packages/@aws-cdk/core/lib/resource.ts | 5 ++++- packages/@aws-cdk/core/lib/token.ts | 2 +- .../aws-custom-resource/aws-custom-resource.ts | 3 ++- .../lib/provider-framework/provider.ts | 7 ++++--- packages/@aws-cdk/custom-resources/package.json | 5 ++++- packages/awslint/bin/awslint.ts | 2 +- packages/decdk/lib/cdk-schema.ts | 2 +- packages/decdk/lib/jsii2schema.ts | 8 ++++---- 291 files changed, 923 insertions(+), 400 deletions(-) diff --git a/packages/@aws-cdk/alexa-ask/package.json b/packages/@aws-cdk/alexa-ask/package.json index 94f22c5a50b59..8435ce066a29c 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "Alexa::ASK", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-accessanalyzer/package.json b/packages/@aws-cdk/aws-accessanalyzer/package.json index cc3e5f4abadd7..cba163f1e1914 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/package.json +++ b/packages/@aws-cdk/aws-accessanalyzer/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::AccessAnalyzer", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-acmpca/package.json b/packages/@aws-cdk/aws-acmpca/package.json index 4636ccbb5a254..7941394fee2af 100644 --- a/packages/@aws-cdk/aws-acmpca/package.json +++ b/packages/@aws-cdk/aws-acmpca/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ACMPCA", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index 57b006a6d8497..70b7db473e7fc 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::AmazonMQ", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index dc705e73c939e..30501ff513130 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,4 +1,5 @@ -import { Construct, IResource as IResourceBase, Resource } from '@aws-cdk/core'; +import { IResource as IResourceBase, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from './resource'; import { RestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts index ea414a1c43584..8a4c5a020bb9a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts @@ -1,4 +1,5 @@ -import { Construct, Resource, ResourceProps } from '@aws-cdk/core'; +import { Resource, ResourceProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AuthorizationType } from './method'; import { IRestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index caf98e5131676..1235b3e0e32fb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Lazy, Stack } from '@aws-cdk/core'; +import { Duration, Lazy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnAuthorizer } from '../apigateway.generated'; import { Authorizer, IAuthorizer } from '../authorizer'; import { IRestApi } from '../restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts index 9a23139df0ee4..6026fe3eaecd3 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts @@ -1,4 +1,5 @@ -import { Construct, Resource, Token } from '@aws-cdk/core'; +import { Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnBasePathMapping } from './apigateway.generated'; import { IDomainName } from './domain-name'; import { IRestApi, RestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index e09f0cf1dde35..877af8df471b3 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -1,5 +1,6 @@ import * as crypto from 'crypto'; -import { Construct, Lazy, RemovalPolicy, Resource, CfnResource } from '@aws-cdk/core'; +import { Construct as CoreConstruct, Lazy, RemovalPolicy, Resource, CfnResource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDeployment } from './apigateway.generated'; import { Method } from './method'; import { IRestApi, RestApi, SpecRestApi, RestApiBase } from './restapi'; @@ -132,7 +133,7 @@ class LatestDeploymentResource extends CfnDeployment { private readonly originalLogicalId: string; private readonly api: IRestApi; - constructor(scope: Construct, id: string, props: LatestDeploymentResourceProps) { + constructor(scope: CoreConstruct, id: string, props: LatestDeploymentResourceProps) { super(scope, id, { description: props.description, restApiId: props.restApi.restApiId, diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index f44ebf953dcac..f3b5074fb65a5 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -1,5 +1,6 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; -import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; +import { IResource, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDomainName } from './apigateway.generated'; import { BasePathMapping, BasePathMappingOptions } from './base-path-mapping'; import { EndpointType, IRestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts index 62957167fa881..e28ba7355000a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnGatewayResponse } from './apigateway.generated'; import { IRestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts index 9b3e43da970fd..1e7a64c20b3d7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts @@ -1,5 +1,5 @@ import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { LambdaIntegration } from './integrations'; import { Method } from './method'; import { ProxyResource, Resource } from './resource'; @@ -42,7 +42,7 @@ export interface LambdaRestApiProps extends RestApiProps { * add resources and methods to the API. */ export class LambdaRestApi extends RestApi { - constructor(scope: cdk.Construct, id: string, props: LambdaRestApiProps) { + constructor(scope: Construct, id: string, props: LambdaRestApiProps) { if ((props.options && props.options.defaultIntegration) || props.defaultIntegration) { throw new Error('Cannot specify "defaultIntegration" since Lambda integration is automatically defined'); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 172eb77cd1877..e4cfa78c0584e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -1,4 +1,5 @@ -import { Construct, Resource, Stack } from '@aws-cdk/core'; +import { Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { Authorizer, IAuthorizer } from './authorizer'; import { Integration, IntegrationConfig } from './integration'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/model.ts b/packages/@aws-cdk/aws-apigateway/lib/model.ts index 088dee9b98750..277c0e4fa20b7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/model.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/model.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnModel, CfnModelProps } from './apigateway.generated'; import * as jsonSchema from './json-schema'; import { IRestApi, RestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts index 041d34c5ba99d..a82892b112ef7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ApiKey, ApiKeyProps, IApiKey } from './api-key'; import { QuotaSettings, ThrottleSettings, UsagePlan, UsagePlanPerApiStage } from './usage-plan'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts b/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts index ce56903f79a0d..02d3e014c24da 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnRequestValidator, CfnRequestValidatorProps } from './apigateway.generated'; import { IRestApi, RestApi } from './restapi'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index c36f699199a14..1d2c889a8e427 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -1,4 +1,5 @@ -import { Construct, IResource as IResourceBase, Resource as ResourceConstruct } from '@aws-cdk/core'; +import { IResource as IResourceBase, Resource as ResourceConstruct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnResource, CfnResourceProps } from './apigateway.generated'; import { Cors, CorsOptions } from './cors'; import { Integration } from './integration'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 612b92af1986e..0ab0b458d0b39 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,6 +1,7 @@ import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index 387983a19d86d..67046e5ae3117 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AccessLogFormat, IAccessLogDestination } from './access-log'; import { CfnStage } from './apigateway.generated'; import { Deployment } from './deployment'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 7c2313dd3c025..6e1c5a4266a9e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -1,4 +1,5 @@ -import { Construct, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Lazy, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IApiKey } from './api-key'; import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; import { Method } from './method'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 9c036a18e582d..85bdf63d0022c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -1,5 +1,6 @@ import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnVpcLink } from './apigateway.generated'; /** diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 90fbaadcf26ab..ccd888c50c0c9 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::ApiGateway" + "cloudformation": "AWS::ApiGateway", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts index 93234807bbf09..eaef6605ef897 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts @@ -1,5 +1,6 @@ import { ICertificate } from '@aws-cdk/aws-certificatemanager'; -import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; +import { IResource, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDomainName, CfnDomainNameProps } from '../apigatewayv2.generated'; /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts index 17461f258b288..98410038708d3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApiMapping, CfnApiMappingProps } from '../apigatewayv2.generated'; import { IApiMapping, IDomainName } from '../common'; import { IHttpApi } from '../http/api'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index a15d8e929c6f6..7678a7bbe99e4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, IResource, Resource } from '@aws-cdk/core'; +import { Duration, IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; import { DefaultDomainMappingOptions } from '../http/stage'; import { IHttpRouteIntegration } from './integration'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 236f533ee746e..237177f31957a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -1,9 +1,14 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; import { IHttpApi } from './api'; import { HttpMethod, IHttpRoute } from './route'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Represents an Integration for an HTTP API. */ @@ -126,7 +131,7 @@ export interface HttpRouteIntegrationBindOptions { * If the `HttpRouteIntegration` being bound creates additional constructs, * this will be used as their parent scope. */ - readonly scope: Construct; + readonly scope: CoreConstruct; } /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 2f65902a6aaee..6f13c46d9e3d5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnRoute, CfnRouteProps } from '../apigatewayv2.generated'; import { IRoute } from '../common'; import { IHttpApi } from './api'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index a9e7de3ce9af2..986649f5301e5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -1,4 +1,5 @@ -import { Construct, Resource, Stack } from '@aws-cdk/core'; +import { Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnStage } from '../apigatewayv2.generated'; import { CommonStageOptions, IDomainName, IStage } from '../common'; import { IHttpApi } from './api'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 5bc75ca9013d2..24fdaedfee400 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ApiGatewayV2", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-appconfig/package.json b/packages/@aws-cdk/aws-appconfig/package.json index 882b6c95adab8..138ac518ef269 100644 --- a/packages/@aws-cdk/aws-appconfig/package.json +++ b/packages/@aws-cdk/aws-appconfig/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::AppConfig", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts index 5407fba139087..ee955d6d120d1 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ScalableTarget, ScalingSchedule, ServiceNamespace } from './scalable-target'; import { BasicStepScalingPolicyProps } from './step-scaling-policy'; import { BasicTargetTrackingScalingPolicyProps } from './target-tracking-scaling-policy'; @@ -46,7 +47,7 @@ export interface BaseScalableAttributeProps extends EnableScalingProps { export abstract class BaseScalableAttribute extends cdk.Construct { private target: ScalableTarget; - public constructor(scope: cdk.Construct, id: string, protected readonly props: BaseScalableAttributeProps) { + public constructor(scope: Construct, id: string, protected readonly props: BaseScalableAttributeProps) { super(scope, id); this.target = new ScalableTarget(this, 'Target', { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index 5cf3f273e891f..14bf3f4913b34 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource, Lazy, Resource, withResolved } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, withResolved } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnScalableTarget } from './applicationautoscaling.generated'; import { Schedule } from './schedule'; import { BasicStepScalingPolicyProps, StepScalingPolicy } from './step-scaling-policy'; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts index fac49b523e8ea..44eb88e9475a5 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnScalingPolicy } from './applicationautoscaling.generated'; import { IScalableTarget } from './scalable-target'; @@ -74,7 +75,7 @@ export class StepScalingAction extends cdk.Construct { private readonly adjustments = new Array(); - constructor(scope: cdk.Construct, id: string, props: StepScalingActionProps) { + constructor(scope: Construct, id: string, props: StepScalingActionProps) { super(scope, id); // Cloudformation requires either the ResourceId, ScalableDimension, and ServiceNamespace diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts index 8b7b6fd8e5bea..dcead1cf8e37a 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -1,6 +1,7 @@ import { findAlarmThresholds, normalizeIntervals } from '@aws-cdk/aws-autoscaling-common'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IScalableTarget } from './scalable-target'; import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action'; @@ -68,7 +69,7 @@ export class StepScalingPolicy extends cdk.Construct { public readonly upperAlarm?: cloudwatch.Alarm; public readonly upperAction?: StepScalingAction; - constructor(scope: cdk.Construct, id: string, props: StepScalingPolicyProps) { + constructor(scope: Construct, id: string, props: StepScalingPolicyProps) { super(scope, id); if (props.scalingSteps.length < 2) { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index 59fc471a55673..5270177629f2e 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -1,5 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnScalingPolicy } from './applicationautoscaling.generated'; import { IScalableTarget } from './scalable-target'; @@ -120,7 +121,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { */ public readonly scalingPolicyArn: string; - constructor(scope: cdk.Construct, id: string, props: TargetTrackingScalingPolicyProps) { + constructor(scope: Construct, id: string, props: TargetTrackingScalingPolicyProps) { if ((props.customMetric === undefined) === (props.predefinedMetric === undefined)) { throw new Error('Exactly one of \'customMetric\' or \'predefinedMetric\' must be specified.'); } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 874fea95fa0f0..348f11078418d 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::ApplicationAutoScaling" + "cloudformation": "AWS::ApplicationAutoScaling", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-applicationinsights/package.json b/packages/@aws-cdk/aws-applicationinsights/package.json index 2d01bb0af2495..0d9cb18068cee 100644 --- a/packages/@aws-cdk/aws-applicationinsights/package.json +++ b/packages/@aws-cdk/aws-applicationinsights/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ApplicationInsights", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index 8e39cb4657d1e..2aaded50f99bc 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnMesh } from './appmesh.generated'; import { VirtualNode, VirtualNodeBaseProps } from './virtual-node'; import { VirtualRouter, VirtualRouterBaseProps } from './virtual-router'; @@ -129,7 +129,7 @@ export class Mesh extends MeshBase { /** * Import an existing mesh by arn */ - public static fromMeshArn(scope: cdk.Construct, id: string, meshArn: string): IMesh { + public static fromMeshArn(scope: Construct, id: string, meshArn: string): IMesh { const parts = cdk.Stack.of(scope).parseArn(meshArn); class Import extends MeshBase { @@ -143,7 +143,7 @@ export class Mesh extends MeshBase { /** * Import an existing mesh by name */ - public static fromMeshName(scope: cdk.Construct, id: string, meshName: string): IMesh { + public static fromMeshName(scope: Construct, id: string, meshName: string): IMesh { const arn = cdk.Stack.of(scope).formatArn({ service: 'appmesh', resource: 'mesh', @@ -168,7 +168,7 @@ export class Mesh extends MeshBase { */ public readonly meshArn: string; - constructor(scope: cdk.Construct, id: string, props: MeshProps = {}) { + constructor(scope: Construct, id: string, props: MeshProps = {}) { super(scope, id, { physicalName: props.meshName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 8534ad70dcf14..0dfeac897375a 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnRoute } from './appmesh.generated'; import { IMesh } from './mesh'; import { IVirtualNode } from './virtual-node'; @@ -113,14 +113,14 @@ export class Route extends cdk.Resource implements IRoute { /** * Import an existing route given an ARN */ - public static fromRouteArn(scope: cdk.Construct, id: string, routeArn: string): IRoute { + public static fromRouteArn(scope: Construct, id: string, routeArn: string): IRoute { return new ImportedRoute(scope, id, { routeArn }); } /** * Import an existing route given its name */ - public static fromRouteName(scope: cdk.Construct, id: string, meshName: string, virtualRouterName: string, routeName: string): IRoute { + public static fromRouteName(scope: Construct, id: string, meshName: string, virtualRouterName: string, routeName: string): IRoute { return new ImportedRoute(scope, id, { meshName, virtualRouterName, routeName }); } @@ -143,7 +143,7 @@ export class Route extends cdk.Resource implements IRoute { private readonly httpRoute?: CfnRoute.HttpRouteProperty; private readonly tcpRoute?: CfnRoute.TcpRouteProperty; - constructor(scope: cdk.Construct, id: string, props: RouteProps) { + constructor(scope: Construct, id: string, props: RouteProps) { super(scope, id, { physicalName: props.routeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); @@ -251,7 +251,7 @@ class ImportedRoute extends cdk.Resource implements IRoute { */ public readonly routeArn: string; - constructor(scope: cdk.Construct, id: string, props: RouteAttributes) { + constructor(scope: Construct, id: string, props: RouteAttributes) { super(scope, id); if (props.routeArn) { diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index f7a2548a48e7b..8e48b5143dec8 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -1,6 +1,6 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnVirtualNode } from './appmesh.generated'; import { IMesh } from './mesh'; import { HealthCheck, PortMapping, Protocol, VirtualNodeListener } from './shared-interfaces'; @@ -214,14 +214,14 @@ export class VirtualNode extends VirtualNodeBase { /** * Import an existing VirtualNode given an ARN */ - public static fromVirtualNodeArn(scope: cdk.Construct, id: string, virtualNodeArn: string): IVirtualNode { + public static fromVirtualNodeArn(scope: Construct, id: string, virtualNodeArn: string): IVirtualNode { return new ImportedVirtualNode(scope, id, { virtualNodeArn }); } /** * Import an existing VirtualNode given its name */ - public static fromVirtualNodeName(scope: cdk.Construct, id: string, meshName: string, virtualNodeName: string): IVirtualNode { + public static fromVirtualNodeName(scope: Construct, id: string, meshName: string, virtualNodeName: string): IVirtualNode { return new ImportedVirtualNode(scope, id, { meshName, virtualNodeName, @@ -243,7 +243,7 @@ export class VirtualNode extends VirtualNodeBase { */ public readonly mesh: IMesh; - constructor(scope: cdk.Construct, id: string, props: VirtualNodeProps) { + constructor(scope: Construct, id: string, props: VirtualNodeProps) { super(scope, id, { physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); @@ -325,7 +325,7 @@ class ImportedVirtualNode extends VirtualNodeBase { */ public readonly virtualNodeArn: string; - constructor(scope: cdk.Construct, id: string, props: VirtualNodeAttributes) { + constructor(scope: Construct, id: string, props: VirtualNodeAttributes) { super(scope, id); if (props.virtualNodeArn) { diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index cbfab4d0f7d8a..05fcbb29d5f71 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnVirtualRouter } from './appmesh.generated'; import { IMesh, Mesh } from './mesh'; import { Route, RouteBaseProps } from './route'; @@ -108,21 +108,21 @@ export class VirtualRouter extends VirtualRouterBase { /** * Import an existing VirtualRouter given an ARN */ - public static fromVirtualRouterArn(scope: cdk.Construct, id: string, virtualRouterArn: string): IVirtualRouter { + public static fromVirtualRouterArn(scope: Construct, id: string, virtualRouterArn: string): IVirtualRouter { return new ImportedVirtualRouter(scope, id, { virtualRouterArn }); } /** * Import an existing VirtualRouter given names */ - public static fromVirtualRouterName(scope: cdk.Construct, id: string, meshName: string, virtualRouterName: string): IVirtualRouter { + public static fromVirtualRouterName(scope: Construct, id: string, meshName: string, virtualRouterName: string): IVirtualRouter { return new ImportedVirtualRouter(scope, id, { meshName, virtualRouterName }); } /** * Import an existing virtual router given attributes */ - public static fromVirtualRouterAttributes(scope: cdk.Construct, id: string, attrs: VirtualRouterAttributes): IVirtualRouter { + public static fromVirtualRouterAttributes(scope: Construct, id: string, attrs: VirtualRouterAttributes): IVirtualRouter { return new ImportedVirtualRouter(scope, id, attrs); } @@ -143,7 +143,7 @@ export class VirtualRouter extends VirtualRouterBase { private readonly listeners = new Array(); - constructor(scope: cdk.Construct, id: string, props: VirtualRouterProps) { + constructor(scope: Construct, id: string, props: VirtualRouterProps) { super(scope, id, { physicalName: props.virtualRouterName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); @@ -219,7 +219,7 @@ class ImportedVirtualRouter extends VirtualRouterBase { private _mesh?: IMesh; - constructor(scope: cdk.Construct, id: string, props: VirtualRouterAttributes) { + constructor(scope: Construct, id: string, props: VirtualRouterAttributes) { super(scope, id); if (props.mesh) { diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index fc235960141d9..00b48f2d39d80 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnVirtualService } from './appmesh.generated'; import { IMesh } from './mesh'; import { IVirtualNode } from './virtual-node'; @@ -75,7 +75,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { /** * Import an existing VirtualService given an ARN */ - public static fromVirtualServiceArn(scope: cdk.Construct, id: string, virtualServiceArn: string): IVirtualService { + public static fromVirtualServiceArn(scope: Construct, id: string, virtualServiceArn: string): IVirtualService { return new ImportedVirtualService(scope, id, { virtualServiceArn, }); @@ -84,7 +84,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { /** * Import an existing VirtualService given mesh and service names */ - public static fromVirtualServiceName(scope: cdk.Construct, id: string, meshName: string, virtualServiceName: string): IVirtualService { + public static fromVirtualServiceName(scope: Construct, id: string, meshName: string, virtualServiceName: string): IVirtualService { return new ImportedVirtualService(scope, id, { meshName, virtualServiceName, @@ -104,7 +104,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { private readonly virtualServiceProvider?: CfnVirtualService.VirtualServiceProviderProperty; private readonly mesh: IMesh; - constructor(scope: cdk.Construct, id: string, props: VirtualServiceProps) { + constructor(scope: Construct, id: string, props: VirtualServiceProps) { super(scope, id, { physicalName: props.virtualServiceName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); @@ -198,7 +198,7 @@ class ImportedVirtualService extends cdk.Resource implements IVirtualService { */ public readonly virtualServiceArn: string; - constructor(scope: cdk.Construct, id: string, props: VirtualServiceAttributes) { + constructor(scope: Construct, id: string, props: VirtualServiceAttributes) { super(scope, id); if (props.virtualServiceArn) { diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 106bbf45d9ee0..94a4d5d7fe0b5 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -53,7 +53,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::AppMesh" + "cloudformation": "AWS::AppMesh", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "nyc": { "statements": 75 diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index 6f8a6657e471a..d26317e17b8ad 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::AppStream", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index 95e98d47c31f5..a33bffbb14686 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -34,7 +34,10 @@ }, "cdk-build": { "cloudformation": "AWS::Athena", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 61a1295700937..dd4879e599206 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -110,5 +110,10 @@ "maturity": "experimental", "awscdkio": { "announce": false + }, + "cdk-build": { + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } } } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index a030cead38957..8303b73c29dbb 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -6,9 +6,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import { - Annotations, CfnAutoScalingRollingUpdate, Construct, Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack, + Annotations, CfnAutoScalingRollingUpdate, Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack, Tokenization, withResolved, Tags, } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated'; import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook'; import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action'; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts index 9c6f1d7f15322..4e4e8408ad326 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Duration, IResource, Resource } from '@aws-cdk/core'; +import { Duration, IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnLifecycleHook } from './autoscaling.generated'; import { ILifecycleHookTarget } from './lifecycle-hook-target'; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts index a38c9d88bac64..71fde34af72cd 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnScheduledAction } from './autoscaling.generated'; import { Schedule } from './schedule'; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts index 78ea9cbd0e791..76ba4685c0a38 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts @@ -1,4 +1,5 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct as CoreConstruct, Duration, Lazy } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnScalingPolicy } from './autoscaling.generated'; @@ -16,14 +17,14 @@ export interface StepScalingActionProps { * * @default The default cooldown configured on the AutoScalingGroup */ - readonly cooldown?: cdk.Duration; + readonly cooldown?: Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - readonly estimatedInstanceWarmup?: cdk.Duration; + readonly estimatedInstanceWarmup?: Duration; /** * How the adjustment numbers are interpreted @@ -59,7 +60,7 @@ export interface StepScalingActionProps { * * This Action must be used as the target of a CloudWatch alarm to take effect. */ -export class StepScalingAction extends cdk.Construct { +export class StepScalingAction extends CoreConstruct { /** * ARN of the scaling policy */ @@ -67,7 +68,7 @@ export class StepScalingAction extends cdk.Construct { private readonly adjustments = new Array(); - constructor(scope: cdk.Construct, id: string, props: StepScalingActionProps) { + constructor(scope: Construct, id: string, props: StepScalingActionProps) { super(scope, id); const resource = new CfnScalingPolicy(this, 'Resource', { @@ -78,7 +79,7 @@ export class StepScalingAction extends cdk.Construct { adjustmentType: props.adjustmentType, minAdjustmentMagnitude: props.minAdjustmentMagnitude, metricAggregationType: props.metricAggregationType, - stepAdjustments: cdk.Lazy.anyValue({ produce: () => this.adjustments }), + stepAdjustments: Lazy.anyValue({ produce: () => this.adjustments }), }); this.scalingPolicyArn = resource.ref; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts index e9d530804d77c..a3a417bd126d5 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts @@ -1,6 +1,7 @@ import { findAlarmThresholds, normalizeIntervals } from '@aws-cdk/aws-autoscaling-common'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import * as cdk from '@aws-cdk/core'; +import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action'; @@ -29,14 +30,14 @@ export interface BasicStepScalingPolicyProps { * * @default Default cooldown period on your AutoScalingGroup */ - readonly cooldown?: cdk.Duration; + readonly cooldown?: Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - readonly estimatedInstanceWarmup?: cdk.Duration; + readonly estimatedInstanceWarmup?: Duration; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -63,13 +64,13 @@ export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { * * Implemented using one or more CloudWatch alarms and Step Scaling Policies. */ -export class StepScalingPolicy extends cdk.Construct { +export class StepScalingPolicy extends CoreConstruct { public readonly lowerAlarm?: cloudwatch.Alarm; public readonly lowerAction?: StepScalingAction; public readonly upperAlarm?: cloudwatch.Alarm; public readonly upperAction?: StepScalingAction; - constructor(scope: cdk.Construct, id: string, props: StepScalingPolicyProps) { + constructor(scope: Construct, id: string, props: StepScalingPolicyProps) { super(scope, id); if (props.scalingSteps.length < 2) { @@ -210,7 +211,7 @@ class StepScalingAlarmAction implements cloudwatch.IAlarmAction { constructor(private readonly stepScalingAction: StepScalingAction) { } - public bind(_scope: cdk.Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { + public bind(_scope: CoreConstruct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { return { alarmActionArn: this.stepScalingAction.scalingPolicyArn }; } } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts index c57bd62008093..5631016066761 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts @@ -1,5 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import * as cdk from '@aws-cdk/core'; +import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnScalingPolicy } from './autoscaling.generated'; @@ -29,14 +30,14 @@ export interface BaseTargetTrackingProps { * * @default - The default cooldown configured on the AutoScalingGroup. */ - readonly cooldown?: cdk.Duration; + readonly cooldown?: Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default - Same as the cooldown. */ - readonly estimatedInstanceWarmup?: cdk.Duration; + readonly estimatedInstanceWarmup?: Duration; } /** @@ -97,7 +98,7 @@ export interface TargetTrackingScalingPolicyProps extends BasicTargetTrackingSca readonly autoScalingGroup: IAutoScalingGroup; } -export class TargetTrackingScalingPolicy extends cdk.Construct { +export class TargetTrackingScalingPolicy extends CoreConstruct { /** * ARN of the scaling policy */ @@ -108,7 +109,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { */ private resource: CfnScalingPolicy; - constructor(scope: cdk.Construct, id: string, props: TargetTrackingScalingPolicyProps) { + constructor(scope: Construct, id: string, props: TargetTrackingScalingPolicyProps) { if ((props.customMetric === undefined) === (props.predefinedMetric === undefined)) { throw new Error('Exactly one of \'customMetric\' or \'predefinedMetric\' must be specified.'); } diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 3d8d1e4a80993..b84adf0699b09 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::AutoScaling", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index 388a29873ef12..be56095ec1992 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::AutoScalingPlans", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index 5620f490a2d6a..0d685503f4828 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Budgets", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cassandra/package.json b/packages/@aws-cdk/aws-cassandra/package.json index 165d47b79cb33..46adae57ddd00 100644 --- a/packages/@aws-cdk/aws-cassandra/package.json +++ b/packages/@aws-cdk/aws-cassandra/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Cassandra", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-ce/package.json b/packages/@aws-cdk/aws-ce/package.json index 86c100223e0f5..dff5201588b1f 100644 --- a/packages/@aws-cdk/aws-ce/package.json +++ b/packages/@aws-cdk/aws-ce/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CE", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts index 9bd10343e1321..df3d3988847ad 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts @@ -1,5 +1,6 @@ import * as route53 from '@aws-cdk/aws-route53'; -import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; +import { IResource, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnCertificate } from './certificatemanager.generated'; import { apexDomain } from './util'; diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index 99e341928fc52..655361ba5b4b7 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -3,6 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as route53 from '@aws-cdk/aws-route53'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CertificateProps, ICertificate } from './certificate'; /** @@ -60,7 +61,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat private hostedZoneId: string; private domainName: string; - constructor(scope: cdk.Construct, id: string, props: DnsValidatedCertificateProps) { + constructor(scope: Construct, id: string, props: DnsValidatedCertificateProps) { super(scope, id); this.domainName = props.domainName; diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 9ad0d12c3d0e1..9985cf3e1d912 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::CertificateManager", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts index e686d5b8b3209..425c432fdc5a4 100644 --- a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts +++ b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts @@ -3,6 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnSlackChannelConfiguration } from './chatbot.generated'; /** @@ -193,7 +194,7 @@ export class SlackChannelConfiguration extends SlackChannelConfigurationBase { * * @returns a reference to the existing Slack channel configuration */ - public static fromSlackChannelConfigurationArn(scope: cdk.Construct, id: string, slackChannelConfigurationArn: string): ISlackChannelConfiguration { + public static fromSlackChannelConfigurationArn(scope: Construct, id: string, slackChannelConfigurationArn: string): ISlackChannelConfiguration { const re = /^slack-channel\//; const resourceName = cdk.Stack.of(scope).parseArn(slackChannelConfigurationArn).resourceName as string; @@ -220,7 +221,7 @@ export class SlackChannelConfiguration extends SlackChannelConfigurationBase { */ readonly slackChannelConfigurationName = resourceName.substring('slack-channel/'.length); - constructor(s: cdk.Construct, i: string) { + constructor(s: Construct, i: string) { super(s, i); this.grantPrincipal = new iam.UnknownPrincipal({ resource: this }); } @@ -251,7 +252,7 @@ export class SlackChannelConfiguration extends SlackChannelConfigurationBase { readonly grantPrincipal: iam.IPrincipal; - constructor(scope: cdk.Construct, id: string, props: SlackChannelConfigurationProps) { + constructor(scope: Construct, id: string, props: SlackChannelConfigurationProps) { super(scope, id, { physicalName: props.slackChannelConfigurationName, }); diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index ef17794163e93..35b468f95a27b 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Chatbot", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cloud9/lib/environment.ts b/packages/@aws-cdk/aws-cloud9/lib/environment.ts index d414069e2788b..a28b2627c3b34 100644 --- a/packages/@aws-cdk/aws-cloud9/lib/environment.ts +++ b/packages/@aws-cdk/aws-cloud9/lib/environment.ts @@ -1,6 +1,7 @@ import * as codecommit from '@aws-cdk/aws-codecommit'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnEnvironmentEC2 } from '../lib/cloud9.generated'; /** @@ -79,7 +80,7 @@ export class Ec2Environment extends cdk.Resource implements IEc2Environment { /** * import from EnvironmentEc2Name */ - public static fromEc2EnvironmentName(scope: cdk.Construct, id: string, ec2EnvironmentName: string): IEc2Environment { + public static fromEc2EnvironmentName(scope: Construct, id: string, ec2EnvironmentName: string): IEc2Environment { class Import extends cdk.Resource { public ec2EnvironmentName = ec2EnvironmentName; public ec2EnvironmentArn = cdk.Stack.of(this).formatArn({ @@ -120,7 +121,7 @@ export class Ec2Environment extends cdk.Resource implements IEc2Environment { */ public readonly vpc: ec2.IVpc; - constructor(scope: cdk.Construct, id: string, props: Ec2EnvironmentProps) { + constructor(scope: Construct, id: string, props: Ec2EnvironmentProps) { super(scope, id); this.vpc = props.vpc; diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index de967a9e7a38d..de9042b93d715 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Cloud9", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 70e167f8dd984..e8810f8903394 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -51,7 +51,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 117e79cc26345..39624ab1c1da8 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,12 +1,17 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; +import { Construct, Node } from 'constructs'; import { CfnDistribution } from './cloudfront.generated'; import { GeoRestriction } from './geo-restriction'; import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin'; import { CacheBehavior } from './private/cache-behavior'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Interface for CloudFront distributions */ @@ -314,8 +319,8 @@ export class Distribution extends Resource implements IDistribution { return existingOrigin.originGroupId ?? existingOrigin.originId; } else { const originIndex = this.boundOrigins.length + 1; - const scope = new Construct(this, `Origin${originIndex}`); - const originId = scope.node.uniqueId; + const scope = new CoreConstruct(this, `Origin${originIndex}`); + const originId = Node.of(scope).uniqueId; const originBindConfig = origin.bind(scope, { originId }); if (!originBindConfig.failoverConfig) { this.boundOrigins.push({ origin, originId, ...originBindConfig }); @@ -324,7 +329,7 @@ export class Distribution extends Resource implements IDistribution { throw new Error('An Origin cannot use an Origin with its own failover configuration as its fallback origin!'); } const groupIndex = this.originGroups.length + 1; - const originGroupId = new Construct(this, `OriginGroup${groupIndex}`).node.uniqueId; + const originGroupId = Node.of(new CoreConstruct(this, `OriginGroup${groupIndex}`)).uniqueId; this.boundOrigins.push({ origin, originId, originGroupId, ...originBindConfig }); const failoverOriginId = this.addOrigin(originBindConfig.failoverConfig.failoverOrigin, true); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts index f62c5ad4d0228..dbb58110c29cf 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnCloudFrontOriginAccessIdentity } from './cloudfront.generated'; /** @@ -62,14 +63,14 @@ export class OriginAccessIdentity extends OriginAccessIdentityBase implements IO * Creates a OriginAccessIdentity by providing the OriginAccessIdentityName */ public static fromOriginAccessIdentityName( - scope: cdk.Construct, + scope: Construct, id: string, originAccessIdentityName: string): IOriginAccessIdentity { class Import extends OriginAccessIdentityBase { public readonly originAccessIdentityName = originAccessIdentityName; public readonly grantPrincipal = new iam.ArnPrincipal(this.arn()); - constructor(s: cdk.Construct, i: string) { + constructor(s: Construct, i: string) { super(s, i, { physicalName: originAccessIdentityName }); } } @@ -103,7 +104,7 @@ export class OriginAccessIdentity extends OriginAccessIdentityBase implements IO */ private readonly resource: CfnCloudFrontOriginAccessIdentity; - constructor(scope: cdk.Construct, id: string, props?: OriginAccessIdentityProps) { + constructor(scope: Construct, id: string, props?: OriginAccessIdentityProps) { super(scope, id); // Comment has a max length of 128. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 972726dac93af..6889e50739f7e 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -3,6 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDistribution } from './cloudfront.generated'; import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; import { GeoRestriction } from './geo-restriction'; @@ -693,7 +694,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu /** * Creates a construct that represents an external (imported) distribution. */ - public static fromDistributionAttributes(scope: cdk.Construct, id: string, attrs: CloudFrontWebDistributionAttributes): IDistribution { + public static fromDistributionAttributes(scope: Construct, id: string, attrs: CloudFrontWebDistributionAttributes): IDistribution { return new class extends cdk.Resource implements IDistribution { public readonly domainName: string; public readonly distributionDomainName: string; @@ -756,7 +757,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu [SSLMethod.VIP]: [SecurityPolicyProtocol.SSL_V3, SecurityPolicyProtocol.TLS_V1], }; - constructor(scope: cdk.Construct, id: string, props: CloudFrontWebDistributionProps) { + constructor(scope: Construct, id: string, props: CloudFrontWebDistributionProps) { super(scope, id); let distributionConfig: CfnDistribution.DistributionConfigProperty = { diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index b3b40d712560e..d5642eb629385 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::CloudFront", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 6337437619918..d98c34b8011de 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -5,7 +5,8 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; -import { Construct, Resource, Stack } from '@aws-cdk/core'; +import { Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnTrail } from './cloudtrail.generated'; /** diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 67c6de96f918c..bdddf764b1fe6 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::CloudTrail", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index a159a97f36cbe..94268ae282fcb 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,4 +1,5 @@ -import { Construct, Lazy, Stack, Token } from '@aws-cdk/core'; +import { Lazy, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AlarmBase, IAlarm } from './alarm-base'; import { CfnAlarm, CfnAlarmProps } from './cloudwatch.generated'; import { HorizontalAnnotation } from './graph'; diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts index 4d20f5f3f1300..6f889b5762b58 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts @@ -1,4 +1,5 @@ -import { Construct, Lazy, Stack } from '@aws-cdk/core'; +import { Lazy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AlarmBase, IAlarm, IAlarmRule } from './alarm-base'; import { CfnCompositeAlarm } from './cloudwatch.generated'; diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 21f266e1375fe..f4a17d634f32f 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,4 +1,5 @@ -import { Construct, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDashboard } from './cloudwatch.generated'; import { Column, Row } from './layout'; import { IWidget } from './widget'; diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index 10fcf3bfce240..094f36c166e4b 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::CloudWatch" + "cloudformation": "AWS::CloudWatch", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index b1cc7bd5986eb..f86e2f4a82a11 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,6 +1,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IConstruct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IConstruct, Construct } from 'constructs'; import { CfnRepository } from './codecommit.generated'; export interface IRepository extends IResource { diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index ef00706f6eae0..6bc68e1268bd8 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::CodeCommit" + "cloudformation": "AWS::CodeCommit", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "nyc": { "statements": 30, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts index 7ab5833afbb10..e8a06374523b3 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts index 9ebcc42724db8..c531cdd56c965 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { arnForDeploymentConfig } from '../utils'; /** @@ -35,7 +35,7 @@ export class EcsDeploymentConfig { * @param ecsDeploymentConfigName the name of the referenced custom Deployment Configuration * @returns a Construct representing a reference to an existing custom Deployment Configuration */ - public static fromEcsDeploymentConfigName(_scope: cdk.Construct, _id: string, ecsDeploymentConfigName: string): IEcsDeploymentConfig { + public static fromEcsDeploymentConfigName(_scope: Construct, _id: string, ecsDeploymentConfigName: string): IEcsDeploymentConfig { return deploymentConfig(ecsDeploymentConfigName); } 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 376e86aef2806..5b893bc9f4f6e 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { arnForDeploymentGroup } from '../utils'; import { IEcsApplication } from './application'; import { EcsDeploymentConfig, IEcsDeploymentConfig } from './deployment-config'; @@ -49,7 +49,7 @@ export class EcsDeploymentGroup { * @returns a Construct representing a reference to an existing Deployment Group */ public static fromEcsDeploymentGroupAttributes( - scope: cdk.Construct, + scope:Construct, id: string, attrs: EcsDeploymentGroupAttributes): IEcsDeploymentGroup { return new ImportedEcsDeploymentGroup(scope, id, attrs); @@ -92,7 +92,7 @@ class ImportedEcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentG public readonly deploymentGroupArn: string; public readonly deploymentConfig: IEcsDeploymentConfig; - constructor(scope: cdk.Construct, id: string, props: EcsDeploymentGroupAttributes) { + constructor(scope:Construct, id: string, props: EcsDeploymentGroupAttributes) { super(scope, id); this.application = props.application; this.deploymentGroupName = props.deploymentGroupName; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts index a01d96bb0f029..309bd02ba3647 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts index 77a71202809eb..ebbd4df337529 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { arnForDeploymentConfig } from '../utils'; /** @@ -56,7 +56,7 @@ export class LambdaDeploymentConfig { * @param props the properties of the referenced custom Deployment Configuration * @returns a Construct representing a reference to an existing custom Deployment Configuration */ - public static import(_scope: cdk.Construct, _id: string, props: LambdaDeploymentConfigImportProps): ILambdaDeploymentConfig { + public static import(_scope:Construct, _id: string, props: LambdaDeploymentConfigImportProps): ILambdaDeploymentConfig { return deploymentConfig(props.deploymentConfigName); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index c3cc9ba0fab15..7586fd51728c5 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -2,7 +2,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../utils'; @@ -130,7 +130,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy * @returns a Construct representing a reference to an existing Deployment Group */ public static fromLambdaDeploymentGroupAttributes( - scope: cdk.Construct, + scope: Construct, id: string, attrs: LambdaDeploymentGroupAttributes): ILambdaDeploymentGroup { return new ImportedLambdaDeploymentGroup(scope, id, attrs); @@ -146,7 +146,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy private preHook?: lambda.IFunction; private postHook?: lambda.IFunction; - constructor(scope: cdk.Construct, id: string, props: LambdaDeploymentGroupProps) { + constructor(scope: Construct, id: string, props: LambdaDeploymentGroupProps) { super(scope, id, { physicalName: props.deploymentGroupName, }); @@ -282,7 +282,7 @@ class ImportedLambdaDeploymentGroup extends cdk.Resource implements ILambdaDeplo public readonly deploymentGroupArn: string; public readonly deploymentConfig: ILambdaDeploymentConfig; - constructor(scope: cdk.Construct, id: string, props: LambdaDeploymentGroupAttributes) { + constructor(scope:Construct, id: string, props: LambdaDeploymentGroupAttributes) { super(scope, id); this.application = props.application; this.deploymentGroupName = props.deploymentGroupName; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts index a4690ad3a05bf..6de47a5fff381 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts index fda322283cdd5..b6937db3a475d 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDeploymentConfig } from '../codedeploy.generated'; import { arnForDeploymentConfig } from '../utils'; @@ -93,7 +94,7 @@ export class ServerDeploymentConfig extends cdk.Resource implements IServerDeplo * @returns a Construct representing a reference to an existing custom Deployment Configuration */ public static fromServerDeploymentConfigName( - scope: cdk.Construct, + scope: Construct, id: string, serverDeploymentConfigName: string): IServerDeploymentConfig { @@ -105,7 +106,7 @@ export class ServerDeploymentConfig extends cdk.Resource implements IServerDeplo public readonly deploymentConfigName: string; public readonly deploymentConfigArn: string; - constructor(scope: cdk.Construct, id: string, props: ServerDeploymentConfigProps) { + constructor(scope: Construct, id: string, props: ServerDeploymentConfigProps) { super(scope, id, { physicalName: props.deploymentConfigName, }); diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 3aa3f5915cded..9a3d912bb1e0d 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -4,6 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../utils'; @@ -71,7 +72,7 @@ abstract class ServerDeploymentGroupBase extends cdk.Resource implements IServer public readonly deploymentConfig: IServerDeploymentConfig; public abstract readonly autoScalingGroups?: autoscaling.IAutoScalingGroup[]; - constructor(scope: cdk.Construct, id: string, deploymentConfig?: IServerDeploymentConfig, props?: cdk.ResourceProps) { + constructor(scope: Construct, id: string, deploymentConfig?: IServerDeploymentConfig, props?: cdk.ResourceProps) { super(scope, id, props); this.deploymentConfig = deploymentConfig || ServerDeploymentConfig.ONE_AT_A_TIME; } @@ -84,7 +85,7 @@ class ImportedServerDeploymentGroup extends ServerDeploymentGroupBase { public readonly deploymentGroupArn: string; public readonly autoScalingGroups?: autoscaling.AutoScalingGroup[] = undefined; - constructor(scope: cdk.Construct, id: string, props: ServerDeploymentGroupAttributes) { + constructor(scope: Construct, id: string, props: ServerDeploymentGroupAttributes) { super(scope, id, props.deploymentConfig); this.application = props.application; @@ -247,7 +248,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { * @returns a Construct representing a reference to an existing Deployment Group */ public static fromServerDeploymentGroupAttributes( - scope: cdk.Construct, + scope: Construct, id: string, attrs: ServerDeploymentGroupAttributes): IServerDeploymentGroup { return new ImportedServerDeploymentGroup(scope, id, attrs); @@ -263,7 +264,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { private readonly codeDeployBucket: s3.IBucket; private readonly alarms: cloudwatch.IAlarm[]; - constructor(scope: cdk.Construct, id: string, props: ServerDeploymentGroupProps = {}) { + constructor(scope: Construct, id: string, props: ServerDeploymentGroupProps = {}) { super(scope, id, props.deploymentConfig, { physicalName: props.deploymentGroupName, }); diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 136cca68a108f..27e9e2b715745 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::CodeDeploy" + "cloudformation": "AWS::CodeDeploy", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "nyc": { "statements": 79 diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index 2a28fdcaa7822..70d159fe6fd8c 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -1,5 +1,6 @@ import { Grant, IGrantable } from '@aws-cdk/aws-iam'; -import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnProfilingGroup } from './codeguruprofiler.generated'; /** diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index b4a35197b310d..8140b965fec4a 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodeGuruProfiler", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", @@ -78,7 +81,8 @@ }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "constructs": "^3.0.4" }, "peerDependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-codegurureviewer/package.json b/packages/@aws-cdk/aws-codegurureviewer/package.json index 4528848bdf5e6..ff4a52bd233d8 100644 --- a/packages/@aws-cdk/aws-codegurureviewer/package.json +++ b/packages/@aws-cdk/aws-codegurureviewer/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodeGuruReviewer", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index f43a42238745b..4aacc589b3689 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -3,9 +3,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { - App, BootstraplessSynthesizer, Construct, DefaultStackSynthesizer, + App, BootstraplessSynthesizer, Construct as CoreConstruct, DefaultStackSynthesizer, IStackSynthesizer, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token, TokenComparison, } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ActionCategory, IAction, IPipeline, IStage } from './action'; import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionSupportConstruct, CrossRegionSupportStack } from './cross-region-support-stack'; @@ -363,7 +364,7 @@ export class Pipeline extends PipelineBase { } /** @internal */ - public _attachActionToPipeline(stage: Stage, action: IAction, actionScope: Construct): FullActionDescriptor { + public _attachActionToPipeline(stage: Stage, action: IAction, actionScope: CoreConstruct): FullActionDescriptor { // handle cross-region actions here const crossRegionInfo = this.ensureReplicationResourcesExistFor(action); diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 3ee04f96196e9..69abb3b50581d 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodePipeline", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "nyc": { "statements": 50, diff --git a/packages/@aws-cdk/aws-codestar/lib/github-repository.ts b/packages/@aws-cdk/aws-codestar/lib/github-repository.ts index 0afd45eb0c826..5156ce211b9a8 100644 --- a/packages/@aws-cdk/aws-codestar/lib/github-repository.ts +++ b/packages/@aws-cdk/aws-codestar/lib/github-repository.ts @@ -1,5 +1,6 @@ import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as codestar from './codestar.generated'; /** @@ -87,7 +88,7 @@ export class GitHubRepository extends cdk.Resource implements IGitHubRepository public readonly owner: string; public readonly repo: string; - constructor(scope: cdk.Construct, id: string, props: GitHubRepositoryProps) { + constructor(scope: Construct, id: string, props: GitHubRepositoryProps) { super(scope, id); const resource = new codestar.CfnGitHubRepository(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-codestar/package.json b/packages/@aws-cdk/aws-codestar/package.json index 65c610745c1a0..5eeb0b43db158 100644 --- a/packages/@aws-cdk/aws-codestar/package.json +++ b/packages/@aws-cdk/aws-codestar/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodeStar", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-codestarconnections/package.json b/packages/@aws-cdk/aws-codestarconnections/package.json index 8605bb93ef47f..76647d2d6375e 100644 --- a/packages/@aws-cdk/aws-codestarconnections/package.json +++ b/packages/@aws-cdk/aws-codestarconnections/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodeStarConnections", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index 05e6cbadabf23..ffbefc15ca846 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::CodeStarNotifications", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 1b24802929d73..349c70d00ebfb 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::DataPipeline", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index a266a83a0124f..d2aba2cfb2787 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::DAX", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-detective/package.json b/packages/@aws-cdk/aws-detective/package.json index fdbb9dc3a2362..d99077efe02f7 100644 --- a/packages/@aws-cdk/aws-detective/package.json +++ b/packages/@aws-cdk/aws-detective/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Detective", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index 348ce00c60d1f..2625ba019a9c6 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::DirectoryService", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index f3123d4187356..6f4a8134dd5e6 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::DLM", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index 61637d58edad1..af3bd1ce5e56c 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::DMS", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-docdb/lib/cluster.ts b/packages/@aws-cdk/aws-docdb/lib/cluster.ts index 339ef8f0b51a0..529f446c265b9 100644 --- a/packages/@aws-cdk/aws-docdb/lib/cluster.ts +++ b/packages/@aws-cdk/aws-docdb/lib/cluster.ts @@ -1,7 +1,8 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { CfnResource, Construct, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; +import { CfnResource, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './docdb.generated'; diff --git a/packages/@aws-cdk/aws-docdb/lib/database-secret.ts b/packages/@aws-cdk/aws-docdb/lib/database-secret.ts index 25a94410674aa..605609b4b6ab2 100644 --- a/packages/@aws-cdk/aws-docdb/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-docdb/lib/database-secret.ts @@ -1,6 +1,7 @@ import { IKey } from '@aws-cdk/aws-kms'; import { ISecret, Secret } from '@aws-cdk/aws-secretsmanager'; -import { Aws, Construct } from '@aws-cdk/core'; +import { Aws } from '@aws-cdk/core'; +import { Construct } from 'constructs'; /** * Construction properties for a DatabaseSecret. diff --git a/packages/@aws-cdk/aws-docdb/lib/instance.ts b/packages/@aws-cdk/aws-docdb/lib/instance.ts index 7c69c626703d4..3208bda7107f7 100644 --- a/packages/@aws-cdk/aws-docdb/lib/instance.ts +++ b/packages/@aws-cdk/aws-docdb/lib/instance.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; - +import { Construct } from 'constructs'; import { IDatabaseCluster } from './cluster-ref'; import { CfnDBInstance } from './docdb.generated'; import { Endpoint } from './endpoint'; @@ -66,7 +66,7 @@ abstract class DatabaseInstanceBase extends cdk.Resource implements IDatabaseIns /** * Import an existing database instance. */ - public static fromDatabaseInstanceAttributes(scope: cdk.Construct, id: string, attrs: DatabaseInstanceAttributes): IDatabaseInstance { + public static fromDatabaseInstanceAttributes(scope: Construct, id: string, attrs: DatabaseInstanceAttributes): IDatabaseInstance { class Import extends DatabaseInstanceBase implements IDatabaseInstance { public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly instanceIdentifier = attrs.instanceIdentifier; @@ -197,7 +197,7 @@ export class DatabaseInstance extends DatabaseInstanceBase implements IDatabaseI */ public readonly instanceEndpoint: Endpoint; - constructor(scope: cdk.Construct, id: string, props: DatabaseInstanceProps) { + constructor(scope: Construct, id: string, props: DatabaseInstanceProps) { super(scope, id); const instance = new CfnDBInstance(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-docdb/lib/parameter-group.ts b/packages/@aws-cdk/aws-docdb/lib/parameter-group.ts index f98c74cbcf403..5a9e377916f2d 100644 --- a/packages/@aws-cdk/aws-docdb/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-docdb/lib/parameter-group.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDBClusterParameterGroup } from './docdb.generated'; /** diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 0959ef2574981..4e1f18ea39357 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::DocDB", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts index 9795551abf5a4..dbc6c45fe415a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts +++ b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts @@ -1,5 +1,6 @@ import { IPrincipal, IRole, PolicyStatement } from '@aws-cdk/aws-iam'; -import { CfnOutput, Construct, Resource, Stack } from '@aws-cdk/core'; +import { CfnOutput, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AmazonLinuxGeneration, InstanceClass, InstanceSize, InstanceType } from '.'; import { Connections } from './connections'; import { IInstance, Instance } from './instance'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index 7a4ef214101b3..61fb6e20d46b1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -1,7 +1,8 @@ import * as crypto from 'crypto'; import * as iam from '@aws-cdk/aws-iam'; -import { Annotations, Construct, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '@aws-cdk/core'; +import { Annotations, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CloudFormationInit } from './cfn-init'; import { Connections, IConnectable } from './connections'; import { CfnInstance } from './ec2.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts index f2d9f75bfbeee..bea1b030d2c9a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts +++ b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnNetworkAcl, CfnNetworkAclEntry, CfnSubnetNetworkAclAssociation } from './ec2.generated'; import { AclCidr, AclTraffic } from './network-acl-types'; import { ISubnet, IVpc, SubnetSelection } from './vpc'; diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 868db46dd462a..3681c61f686dc 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,4 +1,5 @@ -import { Annotations, Construct, IResource, Lazy, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core'; +import { Annotations, IResource, Lazy, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Connections } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; import { IPeer } from './peer'; diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index f43296b5f75d2..56fef8b2a97f5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ISubnet, Subnet, SubnetType } from './vpc'; /** @@ -70,7 +70,7 @@ export class ImportSubnetGroup { this.names = this.normalizeNames(names, defaultSubnetName(type), nameField); } - public import(scope: cdk.Construct): ISubnet[] { + public import(scope: Construct): ISubnet[] { return range(this.subnetIds.length).map(i => { const k = Math.floor(i / this.availabilityZones.length); return Subnet.fromSubnetAttributes(scope, subnetId(this.names[k], i), { diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index e7ce7d52b2977..9270f6d81addb 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -2,10 +2,16 @@ import * as crypto from 'crypto'; import { AccountRootPrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IKey, ViaServicePrincipal } from '@aws-cdk/aws-kms'; -import { Annotations, Construct, IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags } from '@aws-cdk/core'; +import { Annotations, IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags } from '@aws-cdk/core'; +import { Construct, Node } from 'constructs'; import { CfnInstance, CfnVolume } from './ec2.generated'; import { IInstance } from './instance'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + + /** * Block device */ @@ -508,7 +514,7 @@ abstract class VolumeBase extends Resource implements IVolume { // The ResourceTag condition requires that all resources involved in the operation have // the given tag, so we tag this and all constructs given. Tags.of(this).add(tagKey, tagValue); - constructs.forEach(construct => Tags.of(construct).add(tagKey, tagValue)); + constructs.forEach(construct => Tags.of(construct as CoreConstruct).add(tagKey, tagValue)); return result; } @@ -537,7 +543,7 @@ abstract class VolumeBase extends Resource implements IVolume { // The ResourceTag condition requires that all resources involved in the operation have // the given tag, so we tag this and all constructs given. Tags.of(this).add(tagKey, tagValue); - constructs.forEach(construct => Tags.of(construct).add(tagKey, tagValue)); + constructs.forEach(construct => Tags.of(construct as CoreConstruct).add(tagKey, tagValue)); return result; } @@ -558,7 +564,7 @@ abstract class VolumeBase extends Resource implements IVolume { private calculateResourceTagValue(constructs: Construct[]): string { const md5 = crypto.createHash('md5'); - constructs.forEach(construct => md5.update(construct.node.uniqueId)); + constructs.forEach(construct => md5.update(Node.of(construct).uniqueId)); return md5.digest('hex'); } } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts index 71380476225f3..d5d043eef0dc0 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts @@ -1,6 +1,7 @@ import { ArnPrincipal } from '@aws-cdk/aws-iam'; -import { Aws, Construct, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Aws, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import { Default, RegionInfo } from '@aws-cdk/region-info'; +import { Construct } from 'constructs'; import { CfnVPCEndpointService, CfnVPCEndpointServicePermissions } from './ec2.generated'; /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 6f913dd1d858b..2770ed0f6a5b6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Aws, Construct, ContextProvider, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Aws, ContextProvider, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { Peer } from './peer'; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts index cb6dd75ad49eb..bcfb8602aa595 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts @@ -1,10 +1,15 @@ import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, IResource, PhysicalName, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, PhysicalName, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnFlowLog } from './ec2.generated'; import { ISubnet, IVpc } from './vpc'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * A FlowLog * @@ -134,7 +139,7 @@ export abstract class FlowLogDestination { /** * Generates a flow log destination configuration */ - public abstract bind(scope: Construct, flowLog: FlowLog): FlowLogDestinationConfig; + public abstract bind(scope: CoreConstruct, flowLog: FlowLog): FlowLogDestinationConfig; } /** @@ -180,7 +185,7 @@ class S3Destination extends FlowLogDestination { super(); } - public bind(scope: Construct, _flowLog: FlowLog): FlowLogDestinationConfig { + public bind(scope: CoreConstruct, _flowLog: FlowLog): FlowLogDestinationConfig { let s3Bucket: s3.IBucket; if (this.props.s3Bucket === undefined) { s3Bucket = new s3.Bucket(scope, 'Bucket', { @@ -205,7 +210,7 @@ class CloudWatchLogsDestination extends FlowLogDestination { super(); } - public bind(scope: Construct, _flowLog: FlowLog): FlowLogDestinationConfig { + public bind(scope: CoreConstruct, _flowLog: FlowLog): FlowLogDestinationConfig { let iamRole: iam.IRole; let logGroup: logs.ILogGroup; if (this.props.iamRole === undefined) { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 18ea936893ccb..3e6d5731d8baa 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,9 +1,10 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { - Annotations, ConcreteDependable, Construct, ContextProvider, DependableTrait, IConstruct, + Annotations, ConcreteDependable, ContextProvider, DependableTrait, IConstruct, IDependable, IResource, Lazy, Resource, Stack, Token, Tags, } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; +import { Construct, Node } from 'constructs'; import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment, CfnVPNGatewayRoutePropagation, @@ -18,6 +19,10 @@ import { FlowLog, FlowLogOptions, FlowLogResourceType } from './vpc-flow-logs'; import { VpcLookupOptions } from './vpc-lookup'; import { EnableVpnGatewayOptions, VpnConnection, VpnConnectionOptions, VpnConnectionType, VpnGateway } from './vpn'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + const VPC_SUBNET_SYMBOL = Symbol.for('@aws-cdk/aws-ec2.VpcSubnet'); export interface ISubnet extends IResource { @@ -1610,8 +1615,8 @@ export class Subnet extends Resource implements ISubnet { public associateNetworkAcl(id: string, networkAcl: INetworkAcl) { this._networkAcl = networkAcl; - const scope = Construct.isConstruct(networkAcl) ? networkAcl : this; - const other = Construct.isConstruct(networkAcl) ? this : networkAcl; + const scope = CoreConstruct.isConstruct(networkAcl) ? networkAcl : this; + const other = CoreConstruct.isConstruct(networkAcl) ? this : networkAcl; new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { networkAcl, subnet: this, @@ -1916,7 +1921,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat if (!attrs.routeTableId) { const ref = Token.isUnresolved(attrs.subnetId) - ? `at '${scope.node.path}/${id}'` + ? `at '${Node.of(scope).path}/${id}'` : `'${attrs.subnetId}'`; // eslint-disable-next-line max-len Annotations.of(this).addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); @@ -1948,8 +1953,8 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat } public associateNetworkAcl(id: string, networkAcl: INetworkAcl): void { - const scope = Construct.isConstruct(networkAcl) ? networkAcl : this; - const other = Construct.isConstruct(networkAcl) ? this : networkAcl; + const scope = CoreConstruct.isConstruct(networkAcl) ? networkAcl : this; + const other = CoreConstruct.isConstruct(networkAcl) ? this : networkAcl; new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { networkAcl, subnet: this, diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 740bec3f1be6b..409a7599e787e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -1,6 +1,7 @@ import * as net from 'net'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnCustomerGateway, CfnVPNConnection, @@ -154,7 +155,7 @@ export class VpnGateway extends cdk.Resource implements IVpnGateway { */ public readonly gatewayId: string; - constructor(scope: cdk.Construct, id: string, props: VpnGatewayProps) { + constructor(scope: Construct, id: string, props: VpnGatewayProps) { super(scope, id); // This is 'Default' instead of 'Resource', because using 'Default' will generate @@ -213,7 +214,7 @@ export class VpnConnection extends cdk.Resource implements IVpnConnection { public readonly customerGatewayIp: string; public readonly customerGatewayAsn: number; - constructor(scope: cdk.Construct, id: string, props: VpnConnectionProps) { + constructor(scope: Construct, id: string, props: VpnConnectionProps) { super(scope, id); if (!props.vpc.vpnGatewayId) { diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 530fbd4a7473a..3b4c3c0e04ccb 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::EC2", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-efs/lib/access-point.ts b/packages/@aws-cdk/aws-efs/lib/access-point.ts index 04399991cddb6..7f43d88523bf1 100644 --- a/packages/@aws-cdk/aws-efs/lib/access-point.ts +++ b/packages/@aws-cdk/aws-efs/lib/access-point.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IFileSystem } from './efs-file-system'; import { CfnAccessPoint } from './efs.generated'; diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index 775eaea806af6..ce5fc4eac2e9c 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,6 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; -import { ConcreteDependable, Construct, IDependable, IResource, RemovalPolicy, Resource, Size, Tags } from '@aws-cdk/core'; +import { ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Tags } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AccessPoint, AccessPointOptions } from './access-point'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 810316d715094..2e297e49cf73c 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::EFS", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index 04e8b46390fd4..ab6e459939236 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::ElastiCache", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index dc04dcdf44aea..1f907f7aea14a 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::ElasticBeanstalk", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 21ef3060fc786..2bb95496a4c74 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -2,7 +2,8 @@ import { Connections, IConnectable, ISecurityGroup, IVpc, Peer, Port, SecurityGroup, SelectedSubnets, SubnetSelection, SubnetType, } from '@aws-cdk/aws-ec2'; -import { Construct, Duration, Lazy, Resource } from '@aws-cdk/core'; +import { Duration, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnLoadBalancer } from './elasticloadbalancing.generated'; /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index 7187de75b3ad0..a2f49664d154d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancing", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index 96bf7c63d209c..d5148b9709815 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -102,6 +102,9 @@ "announce": false }, "cdk-build": { - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts index d8497da664e5b..4dab6b23ba2b0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnListenerCertificate } from '../elasticloadbalancingv2.generated'; import { IListenerCertificate } from '../shared/listener-certificate'; import { IApplicationListener } from './application-listener'; @@ -36,7 +37,7 @@ export interface ApplicationListenerCertificateProps { * Add certificates to a listener */ export class ApplicationListenerCertificate extends cdk.Construct { - constructor(scope: cdk.Construct, id: string, props: ApplicationListenerCertificateProps) { + constructor(scope: Construct, id: string, props: ApplicationListenerCertificateProps) { super(scope, id); if (!props.certificateArns && !props.certificates) { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index ca80758306777..fe22b97653430 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnListenerRule } from '../elasticloadbalancingv2.generated'; import { IListenerAction } from '../shared/listener-action'; import { IApplicationListener } from './application-listener'; @@ -205,7 +206,7 @@ export class ApplicationListenerRule extends cdk.Construct { private readonly listener: IApplicationListener; private action?: IListenerAction; - constructor(scope: cdk.Construct, id: string, props: ApplicationListenerRuleProps) { + constructor(scope: Construct, id: string, props: ApplicationListenerRuleProps) { super(scope, id); this.conditions = props.conditions || []; 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 65d6465cb9efe..d088008e7ce23 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Construct, Duration, IResource, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Duration, IResource, Lazy, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { ApplicationProtocol, IpAddressType, SslPolicy } from '../shared/enums'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index a7678a51dfdf5..278cd8a5fbc44 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,6 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { Construct, Duration, Lazy, Resource } from '@aws-cdk/core'; +import { Duration, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { IpAddressType, ApplicationProtocol } from '../shared/enums'; import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 2e2cfec3089f6..915711cf0e185 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -1,6 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { Annotations, Construct, Duration, IConstruct } from '@aws-cdk/core'; +import { Annotations, Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { IConstruct, Construct } from 'constructs'; import { BaseTargetGroupProps, ITargetGroup, loadBalancerNameFromListenerArn, LoadBalancerTargetProps, TargetGroupAttributes, TargetGroupBase, TargetGroupImportProps, @@ -159,7 +160,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat listener.registerConnectable(member.connectable, member.portRange); } this.listeners.push(listener); - this.loadBalancerAttachedDependencies.add(associatingConstruct || listener); + this.loadBalancerAttachedDependencies.add((associatingConstruct || listener) as CoreConstruct); } /** 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 b6ad9b4bde696..17857a2ed4187 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, IResource, Resource } from '@aws-cdk/core'; +import { Duration, IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { Protocol, SslPolicy } from '../shared/enums'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index 28e23cc56a257..40743f03464a4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -2,7 +2,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; import { IBucket } from '@aws-cdk/aws-s3'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { BaseNetworkListenerProps, NetworkListener } from './network-listener'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index eff0a6efc0c97..8ac9769995dda 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseTargetGroupProps, HealthCheck, ITargetGroup, loadBalancerNameFromListenerArn, LoadBalancerTargetProps, TargetGroupAttributes, TargetGroupBase, TargetGroupImportProps, @@ -50,7 +51,7 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge /** * Import an existing target group */ - public static fromTargetGroupAttributes(scope: cdk.Construct, id: string, attrs: TargetGroupAttributes): INetworkTargetGroup { + public static fromTargetGroupAttributes(scope: Construct, id: string, attrs: TargetGroupAttributes): INetworkTargetGroup { return new ImportedNetworkTargetGroup(scope, id, attrs); } @@ -59,13 +60,13 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge * * @deprecated Use `fromTargetGroupAttributes` instead */ - public static import(scope: cdk.Construct, id: string, props: TargetGroupImportProps): INetworkTargetGroup { + public static import(scope: Construct, id: string, props: TargetGroupImportProps): INetworkTargetGroup { return NetworkTargetGroup.fromTargetGroupAttributes(scope, id, props); } private readonly listeners: INetworkListener[]; - constructor(scope: cdk.Construct, id: string, props: NetworkTargetGroupProps) { + constructor(scope: Construct, id: string, props: NetworkTargetGroupProps) { const proto = props.protocol || Protocol.TCP; validateNetworkProtocol(proto); 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 8f92a086b1d6b..7866e522e0375 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -1,4 +1,5 @@ -import { Annotations, Construct, Lazy, Resource } from '@aws-cdk/core'; +import { Annotations, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnListener } from '../elasticloadbalancingv2.generated'; import { IListenerAction } from './listener-action'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index a5644a22c2ca0..1ca544187038a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -1,8 +1,9 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; import { RegionInfo } from '@aws-cdk/region-info'; +import { Construct } from 'constructs'; import { CfnLoadBalancer } from '../elasticloadbalancingv2.generated'; import { Attributes, ifUndefined, renderAttributes } from './util'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index bb7d211cac80d..fbe3e54b5b757 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnTargetGroup } from '../elasticloadbalancingv2.generated'; import { Protocol, TargetType } from './enums'; import { Attributes, renderAttributes } from './util'; @@ -222,7 +223,7 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr */ private readonly resource: CfnTargetGroup; - constructor(scope: cdk.Construct, id: string, baseProps: BaseTargetGroupProps, additionalProps: any) { + constructor(scope: Construct, id: string, baseProps: BaseTargetGroupProps, additionalProps: any) { super(scope, id); if (baseProps.deregistrationDelay !== undefined) { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts index 104f11b67d264..52643b49a97cd 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ITargetGroup, TargetGroupImportProps } from './base-target-group'; /** @@ -20,7 +21,7 @@ export abstract class ImportedTargetGroupBase extends cdk.Construct implements I */ public readonly loadBalancerAttached: cdk.IDependable = new cdk.ConcreteDependable(); - constructor(scope: cdk.Construct, id: string, props: TargetGroupImportProps) { + constructor(scope: Construct, id: string, props: TargetGroupImportProps) { super(scope, id); this.targetGroupArn = props.targetGroupArn; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 86c52cce535ca..f674af8d93d7b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancingV2", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index 0597d412ed6fb..c58a9eb82ed20 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Elasticsearch", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-emr/package.json b/packages/@aws-cdk/aws-emr/package.json index 775ed37d98345..da8094218409d 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::EMR", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index 366c259685527..db0bf52f65317 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnEventBus } from './events.generated'; /** diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 1b19b5f1174a9..ca0dcd24bf45e 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,4 +1,5 @@ -import { App, Construct, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { App, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct, Node } from 'constructs'; import { IEventBus } from './event-bus'; import { EventPattern } from './event-pattern'; import { CfnEventBusPolicy, CfnRule } from './events.generated'; @@ -225,7 +226,7 @@ export class Rule extends Resource implements IRule { if (!sourceApp || !App.isApp(sourceApp)) { throw new Error('Event stack which uses cross-account targets must be part of a CDK app'); } - const targetApp = targetProps.targetResource.node.root; + const targetApp = Node.of(targetProps.targetResource).root; if (!targetApp || !App.isApp(targetApp)) { throw new Error('Target stack which uses cross-account event targets must be part of a CDK app'); } diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index 49601a8a03bb2..fe8af611759f3 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::Events" + "cloudformation": "AWS::Events", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-eventschemas/package.json b/packages/@aws-cdk/aws-eventschemas/package.json index 2f71cc896e3a4..2ad4983c3be32 100644 --- a/packages/@aws-cdk/aws-eventschemas/package.json +++ b/packages/@aws-cdk/aws-eventschemas/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::EventSchemas", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-fms/package.json b/packages/@aws-cdk/aws-fms/package.json index f6820c068b8fd..47ffdc69a57ef 100644 --- a/packages/@aws-cdk/aws-fms/package.json +++ b/packages/@aws-cdk/aws-fms/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::FMS", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts b/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts index f147677544e74..9d27da5bca3cd 100644 --- a/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts +++ b/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts @@ -1,5 +1,6 @@ import { Connections, ISecurityGroup, ISubnet, Port, SecurityGroup } from '@aws-cdk/aws-ec2'; -import { Aws, Construct, Token } from '@aws-cdk/core'; +import { Aws, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { FileSystemAttributes, FileSystemBase, FileSystemProps, IFileSystem } from './file-system'; import { CfnFileSystem } from './fsx.generated'; import { LustreMaintenanceTime } from './maintenance-time'; diff --git a/packages/@aws-cdk/aws-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index 8c04546c14f52..21da2047de0df 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::FSx", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 656f5f2979455..306ffd6f328d3 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::GameLift", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index f548317570012..8b359251f54d2 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnDatabase } from './glue.generated'; export interface IDatabase extends IResource { diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 126d2cfef0226..59b5e551ebc03 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -1,7 +1,8 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, Fn, IResource, Resource, Stack } from '@aws-cdk/core'; +import { Fn, IResource, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { DataFormat } from './data-format'; import { IDatabase } from './database'; import { CfnTable } from './glue.generated'; diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index 0a3afa2014db5..c8ce2ebc7da42 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Glue", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-greengrass/package.json b/packages/@aws-cdk/aws-greengrass/package.json index d218c06a135d2..fa80e4b30ba52 100644 --- a/packages/@aws-cdk/aws-greengrass/package.json +++ b/packages/@aws-cdk/aws-greengrass/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Greengrass", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index bb51020a6e8b1..617b4eeec97fe 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::GuardDuty", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index d04df1948662c..6fc5963fdbeeb 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,4 +1,5 @@ -import { Construct, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnGroup } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy } from './managed-policy'; diff --git a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts index 8d299c5d14bb6..f5973d663e399 100644 --- a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts +++ b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Grant } from './grant'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -34,7 +35,7 @@ export class LazyRole extends cdk.Resource implements IRole { private readonly policies = new Array(); private readonly managedPolicies = new Array(); - constructor(scope: cdk.Construct, id: string, private readonly props: LazyRoleProps) { + constructor(scope: Construct, id: string, private readonly props: LazyRoleProps) { super(scope, id); } diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 9c5553d4655eb..8393f71f58a0d 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -1,4 +1,5 @@ -import { Construct, IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IGroup } from './group'; import { CfnManagedPolicy } from './iam.generated'; import { PolicyDocument } from './policy-document'; diff --git a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts index 352c6a7bf70b0..3e4dda8a4d7cb 100644 --- a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts +++ b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts @@ -1,5 +1,6 @@ import * as path from 'path'; -import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, IResource, Resource, Token } from '@aws-cdk/core'; +import { CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, IResource, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; const RESOURCE_TYPE = 'Custom::AWSCDKOpenIdConnectProvider'; diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index 1d1441f710128..c4fefd21b189d 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IGroup } from './group'; import { CfnPolicy } from './iam.generated'; import { PolicyDocument } from './policy-document'; diff --git a/packages/@aws-cdk/aws-iam/lib/private/immutable-role.ts b/packages/@aws-cdk/aws-iam/lib/private/immutable-role.ts index f3d03b29ec69b..6f5bf102175ef 100644 --- a/packages/@aws-cdk/aws-iam/lib/private/immutable-role.ts +++ b/packages/@aws-cdk/aws-iam/lib/private/immutable-role.ts @@ -1,4 +1,5 @@ -import { ConcreteDependable, Construct, DependableTrait, Resource } from '@aws-cdk/core'; +import { ConcreteDependable, DependableTrait, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Grant } from '../grant'; import { IManagedPolicy } from '../managed-policy'; import { Policy } from '../policy'; diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 42ccfc707582b..3a11bd7fae2d9 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, Lazy, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { Duration, Lazy, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { Construct, Node } from 'constructs'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -423,7 +424,7 @@ export class Role extends Resource implements IRole { */ public withoutPolicyUpdates(): IRole { if (!this.immutableRole) { - this.immutableRole = new ImmutableRole(this.node.scope as Construct, `ImmutableRole${this.node.id}`, this); + this.immutableRole = new ImmutableRole(Node.of(this).scope as Construct, `ImmutableRole${this.node.id}`, this); } return this.immutableRole; diff --git a/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts b/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts index 1228ba26ff585..7b06dd27fe381 100644 --- a/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts +++ b/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts @@ -1,4 +1,5 @@ -import { Annotations, ConcreteDependable, IConstruct, Stack } from '@aws-cdk/core'; +import { Annotations, ConcreteDependable, Stack } from '@aws-cdk/core'; +import { IConstruct, Node } from 'constructs'; import { PolicyStatement } from './policy-statement'; import { AddToPrincipalPolicyResult, IPrincipal, PrincipalPolicyFragment } from './principals'; @@ -34,7 +35,7 @@ export class UnknownPrincipal implements IPrincipal { } public get policyFragment(): PrincipalPolicyFragment { - throw new Error(`Cannot get policy fragment of ${this.resource.node.path}, resource imported without a role`); + throw new Error(`Cannot get policy fragment of ${Node.of(this.resource).path}, resource imported without a role`); } public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 1491d2c454e70..aec5bf735c0ad 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,4 +1,5 @@ -import { Aws, Construct, Lazy, Resource, SecretValue, Stack } from '@aws-cdk/core'; +import { Aws, Lazy, Resource, SecretValue, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IGroup } from './group'; import { CfnUser } from './iam.generated'; import { IIdentity } from './identity-base'; diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index 74f7aebed294c..545d5bc9de439 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -1,4 +1,5 @@ -import { DefaultTokenResolver, IConstruct, Lazy, StringConcat, Tokenization } from '@aws-cdk/core'; +import { DefaultTokenResolver, Lazy, StringConcat, Tokenization } from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; import { IPolicy } from './policy'; const MAX_POLICY_NAME_LEN = 128; diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index b7846eef1d72f..06752ea376812 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -53,6 +53,9 @@ }, "cdk-build": { "cloudformation": "AWS::IAM", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + }, "jest": true }, "keywords": [ diff --git a/packages/@aws-cdk/aws-imagebuilder/package.json b/packages/@aws-cdk/aws-imagebuilder/package.json index f17015d928177..256a3b079a760 100644 --- a/packages/@aws-cdk/aws-imagebuilder/package.json +++ b/packages/@aws-cdk/aws-imagebuilder/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ImageBuilder", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index 2fbbedd97ff9b..bc46eef38a438 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Inspector", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index 6d132bd45659d..b9d7a26a10987 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::IoT", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index 852a47810aac3..34f6ad2fc8b5c 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::IoT1Click", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 90de984540ef8..02bffc449abc1 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::IoTAnalytics", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 804b64832bd44..299bbb8da8861 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::IoTEvents", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-iotthingsgraph/package.json b/packages/@aws-cdk/aws-iotthingsgraph/package.json index fa35752c6d52c..26e80a170a192 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/package.json +++ b/packages/@aws-cdk/aws-iotthingsgraph/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::IoTThingsGraph", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-kendra/package.json b/packages/@aws-cdk/aws-kendra/package.json index bcc7bca3f8554..1e94ce21c0553 100644 --- a/packages/@aws-cdk/aws-kendra/package.json +++ b/packages/@aws-cdk/aws-kendra/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Kendra", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index 98f60e401eb5f..90263b9d0286a 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Aws, CfnCondition, Construct, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Aws, CfnCondition, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnStream } from './kinesis.generated'; const READ_OPERATIONS = [ diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index a6cb174544b72..b4e7c21809883 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Kinesis", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index 5758ba9abaabd..020baefcf1f97 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -56,7 +56,10 @@ "AWS::KinesisAnalytics", "AWS::KinesisAnalyticsV2" ], - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index c6e59d002e10f..dac3480ca17bb 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::KinesisFirehose", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index 8525bdc2f6549..7e622ef95fae9 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IKey } from './key'; import { CfnAlias } from './kms.generated'; diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index cad47395d791b..5dbf1aa54191a 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IConstruct, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; +import { IConstruct, Construct } from 'constructs'; import { Alias } from './alias'; import { CfnKey } from './kms.generated'; @@ -208,7 +209,7 @@ abstract class KeyBase extends Resource implements IKey { */ private granteeStackDependsOnKeyStack(grantee: iam.IGrantable): string | undefined { const grantPrincipal = grantee.grantPrincipal; - if (!(Construct.isConstruct(grantPrincipal))) { + if (!(grantPrincipal instanceof Construct)) { return undefined; } // this logic should only apply to newly created @@ -236,7 +237,7 @@ abstract class KeyBase extends Resource implements IKey { } private isGranteeFromAnotherRegion(grantee: iam.IGrantable): boolean { - if (!(Construct.isConstruct(grantee))) { + if (!(grantee instanceof Construct)) { return false; } const bucketStack = Stack.of(this); @@ -245,7 +246,7 @@ abstract class KeyBase extends Resource implements IKey { } private isGranteeFromAnotherAccount(grantee: iam.IGrantable): boolean { - if (!(Construct.isConstruct(grantee))) { + if (!(grantee instanceof Construct)) { return false; } const bucketStack = Stack.of(this); diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 79afde054eab0..6d59d572727a9 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::KMS" + "cloudformation": "AWS::KMS", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-lakeformation/package.json b/packages/@aws-cdk/aws-lakeformation/package.json index d1078df39b644..461b0aff0c1b0 100644 --- a/packages/@aws-cdk/aws-lakeformation/package.json +++ b/packages/@aws-cdk/aws-lakeformation/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::LakeFormation", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index dc330db3f7ec9..3e22118644ac6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,7 +1,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IFunction, QualifiedFunctionBase } from './function-base'; import { extractQualifierFromArn, IVersion } from './lambda-version'; diff --git a/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts b/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts index ca2c148001d93..6c2419c6f8fd5 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, Resource } from '@aws-cdk/core'; +import { Duration, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { DestinationType, IDestination } from './destination'; import { IFunction } from './function-base'; import { CfnEventInvokeConfig } from './lambda.generated'; diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index a9fbc97bdea0e..3fdefc2b16e42 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IEventSourceDlq } from './dlq'; import { IFunction } from './function-base'; import { CfnEventSourceMapping } from './lambda.generated'; @@ -139,7 +140,7 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp /** * Import an event source into this stack from its event source id. */ - public static fromEventSourceMappingId(scope: cdk.Construct, id: string, eventSourceMappingId: string): IEventSourceMapping { + public static fromEventSourceMappingId(scope: Construct, id: string, eventSourceMappingId: string): IEventSourceMapping { class Import extends cdk.Resource implements IEventSourceMapping { public readonly eventSourceMappingId = eventSourceMappingId; } @@ -148,7 +149,7 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp public readonly eventSourceMappingId: string; - constructor(scope: cdk.Construct, id: string, props: EventSourceMappingProps) { + constructor(scope: Construct, id: string, props: EventSourceMappingProps) { super(scope, id); if (props.maxBatchingWindow && props.maxBatchingWindow.toSeconds() > 300) { diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index b7a6546077c26..dd16d89b54db8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -4,7 +4,8 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, CfnResource, Construct, Duration, Fn, Lazy, Stack } from '@aws-cdk/core'; +import { Annotations, CfnResource, Duration, Fn, Lazy, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Code, CodeConfig } from './code'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index b62523fd0ef45..2156b2d87cfb5 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -1,5 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import { Construct, Fn, Lazy, RemovalPolicy } from '@aws-cdk/core'; +import { Fn, Lazy, RemovalPolicy } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Alias, AliasOptions } from './alias'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { Function } from './function'; diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 03e120223deba..12a92140b511a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; import { Runtime } from './runtime'; diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts index fed62dc2563ba..e499764f96e20 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -1,5 +1,5 @@ import * as logs from '@aws-cdk/aws-logs'; -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; /** * Retry options for all AWS API calls. @@ -25,7 +25,7 @@ export interface LogRetentionProps extends logs.LogRetentionProps { * @deprecated use `LogRetention` from '@aws-cdk/aws-logs' instead */ export class LogRetention extends logs.LogRetention { - constructor(scope: cdk.Construct, id: string, props: LogRetentionProps) { + constructor(scope: Construct, id: string, props: LogRetentionProps) { super(scope, id, { ...props }); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 9a8e54472232a..170f1f3e37573 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Function as LambdaFunction, FunctionProps } from './function'; import { FunctionBase } from './function-base'; import { Version } from './lambda-version'; @@ -48,7 +49,7 @@ export class SingletonFunction extends FunctionBase { protected readonly canCreatePermissions: boolean; private lambdaFunction: LambdaFunction; - constructor(scope: cdk.Construct, id: string, props: SingletonFunctionProps) { + constructor(scope: Construct, id: string, props: SingletonFunctionProps) { super(scope, id); this.lambdaFunction = this.ensureLambda(props); diff --git a/packages/@aws-cdk/aws-lambda/lib/util.ts b/packages/@aws-cdk/aws-lambda/lib/util.ts index 16b56e524094c..fd5aa246e63cc 100644 --- a/packages/@aws-cdk/aws-lambda/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda/lib/util.ts @@ -1,4 +1,4 @@ -import { Construct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Alias, AliasOptions } from './alias'; import { IVersion } from './lambda-version'; diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index f1394d8950f0a..331234b9f47dc 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::Lambda" + "cloudformation": "AWS::Lambda", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "nyc": { "statements": 75, diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index b453561e6959f..0f428fb03bc77 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ILogGroup } from './log-group'; import { CfnDestination } from './logs.generated'; import { ILogSubscriptionDestination, LogSubscriptionDestinationConfig } from './subscription-filter'; @@ -64,7 +65,7 @@ export class CrossAccountDestination extends cdk.Resource implements ILogSubscri */ private readonly resource: CfnDestination; - constructor(scope: cdk.Construct, id: string, props: CrossAccountDestinationProps) { + constructor(scope: Construct, id: string, props: CrossAccountDestinationProps) { super(scope, id, { physicalName: props.destinationName || // In the underlying model, the name is not optional, but we make it so anyway. diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index 6a5ab9a8847a0..e041b16b29bbd 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -1,6 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; import { MetricFilter } from './metric-filter'; diff --git a/packages/@aws-cdk/aws-logs/lib/log-retention.ts b/packages/@aws-cdk/aws-logs/lib/log-retention.ts index 98e23a95d6199..97ea68276b00c 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-retention.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { RetentionDays } from './log-group'; /** @@ -71,7 +72,7 @@ export class LogRetention extends cdk.Construct { */ public readonly logGroupArn: string; - constructor(scope: cdk.Construct, id: string, props: LogRetentionProps) { + constructor(scope: Construct, id: string, props: LogRetentionProps) { super(scope, id); // Custom resource provider @@ -126,7 +127,7 @@ export class LogRetention extends cdk.Construct { class LogRetentionFunction extends cdk.Construct { public readonly functionArn: cdk.Reference; - constructor(scope: cdk.Construct, id: string, props: LogRetentionProps) { + constructor(scope: Construct, id: string, props: LogRetentionProps) { super(scope, id); // Code diff --git a/packages/@aws-cdk/aws-logs/lib/log-stream.ts b/packages/@aws-cdk/aws-logs/lib/log-stream.ts index 952f979577da5..d6e072da8c3d6 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-stream.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-stream.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ILogGroup } from './log-group'; import { CfnLogStream } from './logs.generated'; diff --git a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts index 3b8fe826d053e..8dda5f1728fd3 100644 --- a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts @@ -1,5 +1,6 @@ import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ILogGroup, MetricFilterOptions } from './log-group'; import { CfnMetricFilter } from './logs.generated'; diff --git a/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts b/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts index 8dd397f0fa76f..290534ea97f97 100644 --- a/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts @@ -1,8 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ILogGroup, SubscriptionFilterOptions } from './log-group'; import { CfnSubscriptionFilter } from './logs.generated'; +// v2 - keep this section separate to reduce merge conflicts when merging forwrad into v2 branch +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Interface for classes that can be the destination of a log Subscription */ @@ -17,7 +22,7 @@ export interface ILogSubscriptionDestination { * The destination may reconfigure its own permissions in response to this * function call. */ - bind(scope: Construct, sourceLogGroup: ILogGroup): LogSubscriptionDestinationConfig; + bind(scope: CoreConstruct, sourceLogGroup: ILogGroup): LogSubscriptionDestinationConfig; } /** diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index a4eadfdeba946..c2551e40f7b8e 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::Logs" + "cloudformation": "AWS::Logs", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-macie/package.json b/packages/@aws-cdk/aws-macie/package.json index 8bb07789a9ec1..88f4023ea790e 100644 --- a/packages/@aws-cdk/aws-macie/package.json +++ b/packages/@aws-cdk/aws-macie/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Macie", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-managedblockchain/package.json b/packages/@aws-cdk/aws-managedblockchain/package.json index 78450647f4a65..11963c04905d8 100644 --- a/packages/@aws-cdk/aws-managedblockchain/package.json +++ b/packages/@aws-cdk/aws-managedblockchain/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ManagedBlockchain", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-mediaconvert/package.json b/packages/@aws-cdk/aws-mediaconvert/package.json index df167a7059c03..90371cff4644f 100644 --- a/packages/@aws-cdk/aws-mediaconvert/package.json +++ b/packages/@aws-cdk/aws-mediaconvert/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::MediaConvert", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-medialive/package.json b/packages/@aws-cdk/aws-medialive/package.json index 523db6cdb0951..ce03aa8f8559d 100644 --- a/packages/@aws-cdk/aws-medialive/package.json +++ b/packages/@aws-cdk/aws-medialive/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::MediaLive", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-mediastore/package.json b/packages/@aws-cdk/aws-mediastore/package.json index 2fae56bae9d03..1d0e9516ac62f 100644 --- a/packages/@aws-cdk/aws-mediastore/package.json +++ b/packages/@aws-cdk/aws-mediastore/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::MediaStore", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-msk/package.json b/packages/@aws-cdk/aws-msk/package.json index 6ea82b33f9988..c2b82ed9b62d0 100644 --- a/packages/@aws-cdk/aws-msk/package.json +++ b/packages/@aws-cdk/aws-msk/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::MSK", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index f7293686b7aaa..4d99679cc9705 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Neptune", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-networkmanager/package.json b/packages/@aws-cdk/aws-networkmanager/package.json index 7112c40b8da86..472734536bacb 100644 --- a/packages/@aws-cdk/aws-networkmanager/package.json +++ b/packages/@aws-cdk/aws-networkmanager/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::NetworkManager", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-opsworks/package.json b/packages/@aws-cdk/aws-opsworks/package.json index 2962110e06cce..b33783b40cb45 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::OpsWorks", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-opsworkscm/package.json b/packages/@aws-cdk/aws-opsworkscm/package.json index 8ed560f9bc326..107aa2d9a4f47 100644 --- a/packages/@aws-cdk/aws-opsworkscm/package.json +++ b/packages/@aws-cdk/aws-opsworkscm/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::OpsWorksCM", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-pinpoint/package.json b/packages/@aws-cdk/aws-pinpoint/package.json index b2212b78cabf0..fd660c7d8e04c 100644 --- a/packages/@aws-cdk/aws-pinpoint/package.json +++ b/packages/@aws-cdk/aws-pinpoint/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Pinpoint", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-pinpointemail/package.json b/packages/@aws-cdk/aws-pinpointemail/package.json index b0f9756f2a08c..ec43537f13f82 100644 --- a/packages/@aws-cdk/aws-pinpointemail/package.json +++ b/packages/@aws-cdk/aws-pinpointemail/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::PinpointEmail", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-qldb/package.json b/packages/@aws-cdk/aws-qldb/package.json index 7fe121a6b482f..158598e00ac11 100644 --- a/packages/@aws-cdk/aws-qldb/package.json +++ b/packages/@aws-cdk/aws-qldb/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::QLDB", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-ram/package.json b/packages/@aws-cdk/aws-ram/package.json index fc5fd391233f6..aee469902f46c 100644 --- a/packages/@aws-cdk/aws-ram/package.json +++ b/packages/@aws-cdk/aws-ram/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::RAM", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index 98a3257924863..0856766b99fb9 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -3,7 +3,8 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Construct, Duration, IResource, RemovalPolicy, Resource, SecretValue, Token } from '@aws-cdk/core'; +import { Duration, IResource, RemovalPolicy, Resource, SecretValue, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IClusterParameterGroup } from './parameter-group'; diff --git a/packages/@aws-cdk/aws-redshift/lib/database-secret.ts b/packages/@aws-cdk/aws-redshift/lib/database-secret.ts index 7e7617be2be83..2476bfef3f7e0 100644 --- a/packages/@aws-cdk/aws-redshift/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-redshift/lib/database-secret.ts @@ -1,6 +1,6 @@ import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Construct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; /** * Construction properties for a DatabaseSecret. diff --git a/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts b/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts index 9c7f702395790..7fb570bc23ce6 100644 --- a/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts @@ -1,4 +1,5 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnClusterParameterGroup } from './redshift.generated'; /** diff --git a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts index b8ef551ad5834..1d4a6cbb25eca 100644 --- a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnClusterSubnetGroup } from './redshift.generated'; /** diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 77790ff7c4b93..77deed8ac0c8e 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::Redshift", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-resourcegroups/package.json b/packages/@aws-cdk/aws-resourcegroups/package.json index cde5efc080d7b..bc9d79393aeaf 100644 --- a/packages/@aws-cdk/aws-resourcegroups/package.json +++ b/packages/@aws-cdk/aws-resourcegroups/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::ResourceGroups", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-robomaker/package.json b/packages/@aws-cdk/aws-robomaker/package.json index 49a3ec2db3b28..5baeb85f6582e 100644 --- a/packages/@aws-cdk/aws-robomaker/package.json +++ b/packages/@aws-cdk/aws-robomaker/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::RoboMaker", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index 17dc716ff0f7b..b489ffdcb3584 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -1,6 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Construct, ContextProvider, Duration, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ContextProvider, Duration, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { HostedZoneProviderProps } from './hosted-zone-provider'; import { HostedZoneAttributes, IHostedZone } from './hosted-zone-ref'; import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set'; diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 5519d2a05ff8c..33aec19d9171b 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -1,4 +1,5 @@ -import { Construct, Duration, IResource, Resource, Token } from '@aws-cdk/core'; +import { Duration, IResource, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IAliasRecordTarget } from './alias-record-target'; import { IHostedZone } from './hosted-zone-ref'; import { CfnRecordSet } from './route53.generated'; diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 3ac3f2a68525c..4aa7c9084b782 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::Route53" + "cloudformation": "AWS::Route53", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index 25016c99ea9ad..e0003ac9de3fc 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Route53Resolver", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index 87c9c38cd3fc7..d1cb59520e0f0 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -6,6 +6,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; +import { Construct } from 'constructs'; import { toSymlinkFollow } from './compat'; const ARCHIVE_EXTENSIONS = ['.zip', '.jar']; @@ -110,7 +111,7 @@ export class Asset extends cdk.Construct implements cdk.IAsset { public readonly assetHash: string; - constructor(scope: cdk.Construct, id: string, props: AssetProps) { + constructor(scope: Construct, id: string, props: AssetProps) { super(scope, id); // stage the asset source (conditionally). diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 0d50839a6e9e7..389e186eae655 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -51,7 +51,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index cdd4e1413f766..ad6bc3851c8db 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -6,6 +6,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ISource, SourceConfig } from './source'; const handlerCodeBundle = path.join(__dirname, '..', 'lambda', 'bundle.zip'); @@ -163,7 +164,7 @@ export interface BucketDeploymentProps { } export class BucketDeployment extends cdk.Construct { - constructor(scope: cdk.Construct, id: string, props: BucketDeploymentProps) { + constructor(scope: Construct, id: string, props: BucketDeploymentProps) { super(scope, id); if (props.distributionPaths && !props.distribution) { diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 4639915267e90..429991c20e751 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -57,7 +57,10 @@ "test": [ "/bin/bash lambda/test.sh" ], - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index db851cbf96644..efca2898af564 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::SageMaker", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index 46e05e90bb0f0..0d39f842f7b10 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Serverless", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index 6287b044f5427..da9bc2cd9ab13 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::SDB", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/policy.ts b/packages/@aws-cdk/aws-secretsmanager/lib/policy.ts index 54d6b2af5bb03..67769ed403399 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/policy.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/policy.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ISecret } from './secret'; import { CfnResourcePolicy } from './secretsmanager.generated'; diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts index ada35925d8b3a..49d6170004e71 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts @@ -1,5 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Resource } from '@aws-cdk/core'; +import { Duration, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ISecret } from './secret'; import { CfnRotationSchedule } from './secretsmanager.generated'; diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index 6ef72114413cf..f4f24cd2b05e5 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -1,9 +1,14 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; import * as serverless from '@aws-cdk/aws-sam'; -import { Construct, Duration, Stack, Token } from '@aws-cdk/core'; +import { Duration, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ISecret } from './secret'; +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Options for a SecretRotationApplication */ @@ -205,7 +210,7 @@ export interface SecretRotationProps { /** * Secret rotation for a service or database */ -export class SecretRotation extends Construct { +export class SecretRotation extends CoreConstruct { constructor(scope: Construct, id: string, props: SecretRotationProps) { super(scope, id); diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 1e6bf8d2441a5..ad8e7d0fcf0ef 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Construct, IConstruct, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { IConstruct, Construct } from 'constructs'; import { ResourcePolicy } from './policy'; import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule'; import * as secretsmanager from './secretsmanager.generated'; diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index 5283f7cc04c5f..12af802e6694b 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -53,7 +53,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::SecretsManager" + "cloudformation": "AWS::SecretsManager", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-securityhub/package.json b/packages/@aws-cdk/aws-securityhub/package.json index 2465baa3de572..cd4ff6bbc426d 100644 --- a/packages/@aws-cdk/aws-securityhub/package.json +++ b/packages/@aws-cdk/aws-securityhub/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::SecurityHub", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index a4c3b37f8e1ec..1e93e64585ef2 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::ServiceCatalog", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts index 2e9eb90f105d6..a8302b5e1dbc9 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseInstanceProps, InstanceBase } from './instance'; import { NamespaceType } from './namespace'; import { DnsRecordType, IService, RoutingPolicy } from './service'; @@ -41,7 +41,7 @@ export class AliasTargetInstance extends InstanceBase { */ public readonly dnsName: string; - constructor(scope: cdk.Construct, id: string, props: AliasTargetInstanceProps) { + constructor(scope: Construct, id: string, props: AliasTargetInstanceProps) { super(scope, id); if (props.service.namespace.type === NamespaceType.HTTP) { diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts index 0810d4f6489c8..6d948119a4e73 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseInstanceProps, InstanceBase } from './instance'; import { NamespaceType } from './namespace'; import { DnsRecordType, IService } from './service'; @@ -46,7 +46,7 @@ export class CnameInstance extends InstanceBase { */ public readonly cname: string; - constructor(scope: cdk.Construct, id: string, props: CnameInstanceProps) { + constructor(scope: Construct, id: string, props: CnameInstanceProps) { super(scope, id); if (props.service.namespace.type === NamespaceType.HTTP) { diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts index bcb50a3961892..6b614f3f0bfa7 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseNamespaceProps, INamespace, NamespaceType } from './namespace'; import { BaseServiceProps, Service } from './service'; import { CfnHttpNamespace } from './servicediscovery.generated'; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts index 85bf49bc896f9..e635fca07cee4 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseInstanceProps, InstanceBase } from './instance'; import { DnsRecordType, IService } from './service'; import { CfnInstance } from './servicediscovery.generated'; @@ -74,7 +74,7 @@ export class IpInstance extends InstanceBase { */ public readonly port: number; - constructor(scope: cdk.Construct, id: string, props: IpInstanceProps) { + constructor(scope: Construct, id: string, props: IpInstanceProps) { super(scope, id); const dnsRecordType = props.service.dnsRecordType; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts index a59913200224a..7c51770155893 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseInstanceProps, InstanceBase } from './instance'; import { NamespaceType } from './namespace'; import { IService } from './service'; @@ -34,7 +34,7 @@ export class NonIpInstance extends InstanceBase { */ public readonly service: IService; - constructor(scope: cdk.Construct, id: string, props: NonIpInstanceProps) { + constructor(scope: Construct, id: string, props: NonIpInstanceProps) { super(scope, id); if (props.service.namespace.type !== NamespaceType.HTTP) { diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts index 1186bcda57e0e..c5b8332c3238c 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseNamespaceProps, INamespace, NamespaceType } from './namespace'; import { DnsServiceProps, Service } from './service'; import { CfnPrivateDnsNamespace } from './servicediscovery.generated'; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts index fdd9f1fdde570..431befda4c22d 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts @@ -1,4 +1,5 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BaseNamespaceProps, INamespace, NamespaceType } from './namespace'; import { DnsServiceProps, Service } from './service'; import { CfnPublicDnsNamespace } from './servicediscovery.generated'; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts index 9b7fa80466f8c..b0dce1b20c882 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts @@ -1,5 +1,6 @@ import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import { Construct, Duration, IResource, Resource } from '@aws-cdk/core'; +import { Duration, IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { AliasTargetInstance } from './alias-target-instance'; import { CnameInstance, CnameInstanceBaseProps } from './cname-instance'; import { IInstance } from './instance'; diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index f90041bcfb1c7..11be7c8fc3b3a 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::ServiceDiscovery" + "cloudformation": "AWS::ServiceDiscovery", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "nyc": { "statements": 75 diff --git a/packages/@aws-cdk/aws-sns/lib/policy.ts b/packages/@aws-cdk/aws-sns/lib/policy.ts index 60aec85b13b8c..7d93d863a75f0 100644 --- a/packages/@aws-cdk/aws-sns/lib/policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/policy.ts @@ -1,5 +1,6 @@ import { PolicyDocument } from '@aws-cdk/aws-iam'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnTopicPolicy } from './sns.generated'; import { ITopic } from './topic-base'; diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 9955260ef4797..88817b6db6fdd 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -1,6 +1,7 @@ import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; import { IQueue } from '@aws-cdk/aws-sqs'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnSubscription } from './sns.generated'; import { SubscriptionFilter } from './subscription-filter'; import { ITopic } from './topic-base'; diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index 5f31cb93a88fc..fbabed25ecb51 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,5 +1,6 @@ import { IKey } from '@aws-cdk/aws-kms'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnTopic } from './sns.generated'; import { ITopic, TopicBase } from './topic-base'; diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index fcf94d3403f0f..b5baef7a3135a 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -55,7 +55,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::SNS" + "cloudformation": "AWS::SNS", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-sqs/lib/policy.ts b/packages/@aws-cdk/aws-sqs/lib/policy.ts index 41aa3e8de7f7f..c5d323bf60c63 100644 --- a/packages/@aws-cdk/aws-sqs/lib/policy.ts +++ b/packages/@aws-cdk/aws-sqs/lib/policy.ts @@ -1,5 +1,6 @@ import { PolicyDocument } from '@aws-cdk/aws-iam'; -import { Construct, Resource } from '@aws-cdk/core'; +import { Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IQueue } from './queue-base'; import { CfnQueuePolicy } from './sqs.generated'; diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index d335da2c9120f..2d95f60f4748e 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -1,5 +1,6 @@ import * as kms from '@aws-cdk/aws-kms'; -import { Construct, Duration, Stack, Token } from '@aws-cdk/core'; +import { Duration, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IQueue, QueueAttributes, QueueBase } from './queue-base'; import { CfnQueue } from './sqs.generated'; import { validateProps } from './validate-props'; diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 996743744aa5f..68b8431b6a80a 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::SQS" + "cloudformation": "AWS::SQS", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index bc73278bfc267..416454c1ef8cc 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -3,8 +3,9 @@ import * as kms from '@aws-cdk/aws-kms'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, - Construct, ContextProvider, Fn, IResource, Resource, Stack, Token, + Construct as CompatConstruct, ContextProvider, Fn, IResource, Resource, Stack, Token, } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as ssm from './ssm.generated'; import { arnForParameterName, AUTOGEN_MARKER } from './util'; @@ -324,7 +325,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { const stringValue = attrs.version ? new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${attrs.version}`).toString() - : new CfnParameter(scope, `${id}.Parameter`, { type: `AWS::SSM::Parameter::Value<${type}>`, default: attrs.parameterName }).valueAsString; + : new CfnParameter(scope as CompatConstruct, `${id}.Parameter`, { type: `AWS::SSM::Parameter::Value<${type}>`, default: attrs.parameterName }).valueAsString; class Import extends ParameterBase { public readonly parameterName = attrs.parameterName; @@ -360,7 +361,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { * Requires that the stack this scope is defined in will have explicit * account/region information. Otherwise, it will fail during synthesis. */ - public static valueFromLookup(scope: Construct, parameterName: string): string { + public static valueFromLookup(scope: CompatConstruct, parameterName: string): string { const value = ContextProvider.getValue(scope, { provider: cxschema.ContextProvider.SSM_PARAMETER_PROVIDER, props: { parameterName }, diff --git a/packages/@aws-cdk/aws-ssm/lib/util.ts b/packages/@aws-cdk/aws-ssm/lib/util.ts index 05d42ada96690..d023ef902282d 100644 --- a/packages/@aws-cdk/aws-ssm/lib/util.ts +++ b/packages/@aws-cdk/aws-ssm/lib/util.ts @@ -1,4 +1,5 @@ -import { IConstruct, Stack, Token } from '@aws-cdk/core'; +import { Stack, Token } from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; export const AUTOGEN_MARKER = '$$autogen$$'; diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index d7021983ba7b3..3d06a2eedbf03 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -52,7 +52,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::SSM" + "cloudformation": "AWS::SSM", + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-sso/package.json b/packages/@aws-cdk/aws-sso/package.json index 97c4875ce58e8..c8960a4309e39 100644 --- a/packages/@aws-cdk/aws-sso/package.json +++ b/packages/@aws-cdk/aws-sso/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::SSO", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index ef97f45e6f8b3..e40673730b1cf 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,6 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnActivity } from './stepfunctions.generated'; /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 13a0b9ea5b7be..46ad90e48660d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -1,7 +1,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; -import { Arn, Construct, Duration, IResource, Resource, Stack } from '@aws-cdk/core'; +import { Arn, Duration, IResource, Resource, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { StateGraph } from './state-graph'; import { CfnStateMachine } from './stepfunctions.generated'; import { IChainable } from './types'; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts index df72fb16459ec..e2b3422d227be 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { Condition } from '../condition'; import { IChainable, INextable } from '../types'; @@ -46,7 +46,7 @@ export interface ChoiceProps { export class Choice extends State { public readonly endStates: INextable[] = []; - constructor(scope: cdk.Construct, id: string, props: ChoiceProps = {}) { + constructor(scope: Construct, id: string, props: ChoiceProps = {}) { super(scope, id, props); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts index 005945a4ad20b..eccfad6e9bcc1 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '..'; import { IChainable, INextable } from '../types'; import { State } from './state'; @@ -28,7 +28,7 @@ export class CustomState extends State implements IChainable, INextable { */ private readonly stateJson: { [key: string]: any}; - constructor(scope: cdk.Construct, id: string, props: CustomStateProps) { + constructor(scope: Construct, id: string, props: CustomStateProps) { super(scope, id, {}); this.endStates = [this]; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts index 2f900e935bb62..f7b8e6b10e609 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { INextable } from '../types'; import { StateType } from './private/state-type'; import { State } from './state'; @@ -40,7 +40,7 @@ export class Fail extends State { private readonly error?: string; private readonly cause?: string; - constructor(scope: cdk.Construct, id: string, props: FailProps = {}) { + constructor(scope: Construct, id: string, props: FailProps = {}) { super(scope, id, props); this.error = props.error; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts index 08c375a62bf0b..e3f6e4800991f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/map.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { FieldUtils } from '../fields'; import { StateGraph } from '../state-graph'; @@ -101,7 +101,7 @@ export class Map extends State implements INextable { private readonly maxConcurrency: number | undefined; private readonly itemsPath?: string; - constructor(scope: cdk.Construct, id: string, props: MapProps = {}) { + constructor(scope: Construct, id: string, props: MapProps = {}) { super(scope, id, props); this.endStates = [this]; this.maxConcurrency = props.maxConcurrency; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts index 4bce7313377cf..70ac5819a6888 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { StateGraph } from '../state-graph'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; @@ -58,7 +58,7 @@ export interface ParallelProps { export class Parallel extends State implements INextable { public readonly endStates: INextable[]; - constructor(scope: cdk.Construct, id: string, props: ParallelProps = {}) { + constructor(scope: Construct, id: string, props: ParallelProps = {}) { super(scope, id, props); this.endStates = [this]; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index 01168a125b90e..de9b4d16aab01 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { FieldUtils } from '../fields'; import { IChainable, INextable } from '../types'; @@ -123,7 +123,7 @@ export class Pass extends State implements INextable { private readonly result?: Result; - constructor(scope: cdk.Construct, id: string, props: PassProps = {}) { + constructor(scope: Construct, id: string, props: PassProps = {}) { super(scope, id, props); this.result = props.result; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index d586a3e472d98..00334a0934323 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { IConstruct, Construct, Node } from 'constructs'; import { Condition } from '../condition'; import { JsonPath } from '../fields'; import { StateGraph } from '../state-graph'; @@ -63,14 +64,14 @@ export abstract class State extends cdk.Construct implements IChainable { /** * Add a prefix to the stateId of all States found in a construct tree */ - public static prefixStates(root: cdk.IConstruct, prefix: string) { + public static prefixStates(root: IConstruct, prefix: string) { const queue = [root]; while (queue.length > 0) { const el = queue.splice(0, 1)[0]!; if (isPrefixable(el)) { el.addPrefix(prefix); } - queue.push(...el.node.children); + queue.push(...Node.of(el).children); } } @@ -173,7 +174,7 @@ export abstract class State extends cdk.Construct implements IChainable { */ private readonly incomingStates: State[] = []; - constructor(scope: cdk.Construct, id: string, props: StateProps) { + constructor(scope: Construct, id: string, props: StateProps) { super(scope, id); this.startState = this; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts index 75d51840e0234..0017c9111b3ce 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts @@ -1,4 +1,4 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { INextable } from '../types'; import { StateType } from './private/state-type'; import { State } from './state'; @@ -44,7 +44,7 @@ export interface SucceedProps { export class Succeed extends State { public readonly endStates: INextable[] = []; - constructor(scope: cdk.Construct, id: string, props: SucceedProps = {}) { + constructor(scope: Construct, id: string, props: SucceedProps = {}) { super(scope, id, props); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts index af076233f2747..f530113de2202 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts @@ -1,6 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { StateGraph } from '../state-graph'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; @@ -95,7 +96,7 @@ export abstract class TaskStateBase extends State implements INextable { private readonly timeout?: cdk.Duration; private readonly heartbeat?: cdk.Duration; - constructor(scope: cdk.Construct, id: string, props: TaskStateBaseProps) { + constructor(scope: Construct, id: string, props: TaskStateBaseProps) { super(scope, id, props); this.endStates = [this]; this.timeout = props.timeout; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 29a1d6845aeb9..3c9fc1dca3fd7 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -1,5 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { FieldUtils } from '../fields'; import { StateGraph } from '../state-graph'; @@ -108,7 +109,7 @@ export class Task extends State implements INextable { private readonly timeout?: cdk.Duration; private readonly taskProps: StepFunctionsTaskConfig; - constructor(scope: cdk.Construct, id: string, props: TaskProps) { + constructor(scope: Construct, id: string, props: TaskProps) { super(scope, id, props); this.timeout = props.timeout; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts index 014879e7cabde..8386b3ce334b1 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Chain } from '../chain'; import { IChainable, INextable } from '../types'; import { StateType } from './private/state-type'; @@ -72,7 +73,7 @@ export class Wait extends State implements INextable { private readonly time: WaitTime; - constructor(scope: cdk.Construct, id: string, props: WaitProps) { + constructor(scope: Construct, id: string, props: WaitProps) { super(scope, id, props); this.time = props.time; diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index 77d086a781c31..c37edc6fca586 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::StepFunctions", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-synthetics/lib/canary.ts b/packages/@aws-cdk/aws-synthetics/lib/canary.ts index d2adaf68a9335..d3f3d8ee16b43 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/canary.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/canary.ts @@ -3,6 +3,7 @@ import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { Code } from './code'; import { Schedule } from './schedule'; import { CfnCanary } from './synthetics.generated'; @@ -224,7 +225,7 @@ export class Canary extends cdk.Resource { */ public readonly artifactsBucket: s3.IBucket; - public constructor(scope: cdk.Construct, id: string, props: CanaryProps) { + public constructor(scope: Construct, id: string, props: CanaryProps) { if (props.canaryName && !cdk.Token.isUnresolved(props.canaryName)) { validateName(props.canaryName); } diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index 55611139e5af8..3c53ad9c2bfa8 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Synthetics", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-transfer/package.json b/packages/@aws-cdk/aws-transfer/package.json index e198102ef0d05..2b11c123c59cc 100644 --- a/packages/@aws-cdk/aws-transfer/package.json +++ b/packages/@aws-cdk/aws-transfer/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::Transfer", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 4a6c0f4fe0e18..03101648417b6 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::WAF", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index 2944c053d9d25..3f1b6333fcfc2 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::WAFRegional", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-wafv2/package.json b/packages/@aws-cdk/aws-wafv2/package.json index 6b4ffcf5a6f07..3469018e07c3b 100644 --- a/packages/@aws-cdk/aws-wafv2/package.json +++ b/packages/@aws-cdk/aws-wafv2/package.json @@ -54,7 +54,10 @@ }, "cdk-build": { "cloudformation": "AWS::WAFv2", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index 13ce6feb407ae..54198f3012d6f 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -53,7 +53,10 @@ }, "cdk-build": { "cloudformation": "AWS::WorkSpaces", - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index 96d958fa11710..09041b0634d19 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -156,6 +156,9 @@ async function main() { 'cdk-build': { cloudformation: namespace, jest: true, + env: { + AWSLINT_BASE_CONSTRUCT: 'true', + }, }, keywords: [ 'aws', diff --git a/packages/@aws-cdk/core/lib/annotations.ts b/packages/@aws-cdk/core/lib/annotations.ts index 8ddeedda108a2..f46c830c25757 100644 --- a/packages/@aws-cdk/core/lib/annotations.ts +++ b/packages/@aws-cdk/core/lib/annotations.ts @@ -1,5 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { IConstruct } from './construct-compat'; +import { IConstruct, Node } from 'constructs'; const DEPRECATIONS_SYMBOL = Symbol.for('@aws-cdk/core.deprecations'); @@ -70,7 +70,7 @@ export class Annotations { // throw if CDK_BLOCK_DEPRECATIONS is set if (process.env.CDK_BLOCK_DEPRECATIONS) { - throw new Error(`${this.scope.node.path}: ${text}`); + throw new Error(`${Node.of(this.scope).path}: ${text}`); } // de-dup based on api key @@ -89,7 +89,7 @@ export class Annotations { * @param message The message itself */ private addMessage(level: string, message: string) { - this.scope.node.addMetadata(level, message); + Node.of(this.scope).addMetadata(level, message); } /** diff --git a/packages/@aws-cdk/core/lib/context-provider.ts b/packages/@aws-cdk/core/lib/context-provider.ts index 6a01a7c5a6506..d0ebaa34705ab 100644 --- a/packages/@aws-cdk/core/lib/context-provider.ts +++ b/packages/@aws-cdk/core/lib/context-provider.ts @@ -1,7 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; +import { Construct, Node } from 'constructs'; import { Annotations } from './annotations'; -import { Construct } from './construct-compat'; import { Stack } from './stack'; import { Token } from './token'; @@ -95,7 +95,7 @@ export class ContextProvider { } const { key, props } = this.getKey(scope, options); - const value = scope.node.tryGetContext(key); + const value = Node.of(scope).tryGetContext(key); const providerError = extractProviderError(value); // if context is missing or an error occurred during context retrieval, diff --git a/packages/@aws-cdk/core/lib/private/resolve.ts b/packages/@aws-cdk/core/lib/private/resolve.ts index 43d3afda46830..7098dc4e3ec99 100644 --- a/packages/@aws-cdk/core/lib/private/resolve.ts +++ b/packages/@aws-cdk/core/lib/private/resolve.ts @@ -1,9 +1,13 @@ -import { IConstruct } from '../construct-compat'; +import { IConstruct } from 'constructs'; import { DefaultTokenResolver, IPostProcessor, IResolvable, IResolveContext, ITokenResolver, StringConcat } from '../resolvable'; import { TokenizedStringFragments } from '../string-fragments'; import { containsListTokenElement, TokenString, unresolved } from './encoding'; import { TokenMap } from './token-map'; +// v2 - leave this as a separate section so it reduces merge conflicts when compat is removed +// eslint-disable-next-line import/order +import { IConstruct as ICoreConstruct } from '../construct-compat'; + // This file should not be exported to consumers, resolving should happen through Construct.resolve() const tokenMap = TokenMap.instance(); @@ -44,7 +48,7 @@ export function resolve(obj: any, options: IResolveOptions): any { const context: IResolveContext = { preparing: options.preparing, - scope: options.scope, + scope: options.scope as ICoreConstruct, registerPostProcessor(pp) { postProcessor = pp; }, resolve(x: any) { return resolve(x, { ...options, prefix: newPrefix }); }, }; diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index c437ef98d5d42..6a810b8a971e8 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -1,6 +1,9 @@ +// v2 - leave this as a separate section so it reduces merge conflicts when compat is removed +// eslint-disable-next-line import/order +import { IConstruct, Construct as CoreConstruct } from './construct-compat'; + import { Construct } from 'constructs'; import { ArnComponents } from './arn'; -import { IConstruct, Construct as CoreConstruct } from './construct-compat'; import { Lazy } from './lazy'; import { generatePhysicalName, isGeneratedWhenNeededMarker } from './private/physical-name-generator'; import { IResolveContext } from './resolvable'; diff --git a/packages/@aws-cdk/core/lib/token.ts b/packages/@aws-cdk/core/lib/token.ts index 73a43c74f6e7f..35f2667f67365 100644 --- a/packages/@aws-cdk/core/lib/token.ts +++ b/packages/@aws-cdk/core/lib/token.ts @@ -1,4 +1,4 @@ -import { IConstruct } from './construct-compat'; +import { IConstruct } from 'constructs'; import { Lazy } from './lazy'; import { unresolved } from './private/encoding'; import { Intrinsic } from './private/intrinsic'; diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts index 5169a85839800..730260e3e3779 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts @@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { PHYSICAL_RESOURCE_ID_REFERENCE, flatten } from './runtime'; // don't use "require" since the typescript compiler emits errors since this @@ -303,7 +304,7 @@ export class AwsCustomResource extends cdk.Construct implements iam.IGrantable { // 'props' cannot be optional, even though all its properties are optional. // this is because at least one sdk call must be provided. - constructor(scope: cdk.Construct, id: string, props: AwsCustomResourceProps) { + constructor(scope: Construct, id: string, props: AwsCustomResourceProps) { super(scope, id); if (!props.onCreate && !props.onUpdate && !props.onDelete) { diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 01759f71b7e45..a61e7ab475939 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -2,7 +2,8 @@ import * as path from 'path'; import * as cfn from '@aws-cdk/aws-cloudformation'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; -import { Construct, Duration } from '@aws-cdk/core'; +import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as consts from './runtime/consts'; import { calculateRetryPolicy } from './util'; import { WaiterStateMachine } from './waiter-state-machine'; @@ -74,7 +75,7 @@ export interface ProviderProps { /** * Defines an AWS CloudFormation custom resource provider. */ -export class Provider extends Construct implements cfn.ICustomResourceProvider { +export class Provider extends CoreConstruct implements cfn.ICustomResourceProvider { /** * The user-defined AWS Lambda function which is invoked for all resource @@ -138,7 +139,7 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { * Called by `CustomResource` which uses this provider. * @deprecated use `provider.serviceToken` instead */ - public bind(_: Construct): cfn.CustomResourceProviderConfig { + public bind(_: CoreConstruct): cfn.CustomResourceProviderConfig { return { serviceToken: this.entrypoint.functionArn, }; diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 2f370ac8a5968..511c490dae19b 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -54,7 +54,10 @@ "pre": [ "cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/aws-custom-resource/sdk-api-metadata.json && rm -rf test/aws-custom-resource/cdk.out" ], - "jest": true + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } }, "keywords": [ "aws", diff --git a/packages/awslint/bin/awslint.ts b/packages/awslint/bin/awslint.ts index 1426ac60885d0..d8b26a253268e 100644 --- a/packages/awslint/bin/awslint.ts +++ b/packages/awslint/bin/awslint.ts @@ -21,7 +21,7 @@ async function main() { .option('exclude', { alias: 'x', type: 'array', desc: 'do not evaludate these rules (takes priority over --include)', default: [] }) .option('save', { type: 'boolean', desc: 'updates package.json with "exclude" statements for all failing rules', default: false }) .option('verbose', { alias: 'v', type: 'boolean', desc: 'verbose output (prints all assertions)', default: false }) - .option('quiet', { alias: 'q', type: 'boolean', desc: 'quiet mode - shows only errors', default: false }) + .option('quiet', { alias: 'q', type: 'boolean', desc: 'quiet mode - shows only errors', default: true }) .option('force', { type: 'boolean', desc: 'succeed silently if this is not a jsii module', default: true }) .option('config', { type: 'boolean', desc: 'reads options from the "awslint" section in package.json', default: true }) .option('debug', { type: 'boolean', desc: 'debug output', default: false }) diff --git a/packages/decdk/lib/cdk-schema.ts b/packages/decdk/lib/cdk-schema.ts index c653d174d66d6..7fde9d38ed7d8 100644 --- a/packages/decdk/lib/cdk-schema.ts +++ b/packages/decdk/lib/cdk-schema.ts @@ -22,7 +22,7 @@ export function renderFullSchema(typeSystem: jsiiReflect.TypeSystem, options: Re // Find all constructs for which the props interface // (transitively) only consists of JSON primitives or interfaces // that consist of JSON primitives - const constructType = typeSystem.findClass('@aws-cdk/core.Construct'); + const constructType = typeSystem.findClass('constructs.Construct'); const constructs = typeSystem.classes.filter(c => c.extends(constructType)); const deconstructs = constructs diff --git a/packages/decdk/lib/jsii2schema.ts b/packages/decdk/lib/jsii2schema.ts index 59d33ce675f7c..2b490d7df2125 100644 --- a/packages/decdk/lib/jsii2schema.ts +++ b/packages/decdk/lib/jsii2schema.ts @@ -557,15 +557,15 @@ export function isConstruct(typeOrTypeRef: jsiiReflect.TypeReference | jsiiRefle } } - // if it is an interface, it should extend cdk.IConstruct + // if it is an interface, it should extend constructs.IConstruct if (type instanceof jsiiReflect.InterfaceType) { - const constructIface = type.system.findFqn('@aws-cdk/core.IConstruct'); + const constructIface = type.system.findFqn('constructs.IConstruct'); return type.extends(constructIface); } - // if it is a class, it should extend cdk.Construct + // if it is a class, it should extend constructs.Construct if (type instanceof jsiiReflect.ClassType) { - const constructClass = type.system.findFqn('@aws-cdk/core.Construct'); + const constructClass = type.system.findFqn('constructs.Construct'); return type.extends(constructClass); } From a8d7282934f02c9e773a0091129fba35b494f2e3 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 29 Sep 2020 09:39:04 +0200 Subject: [PATCH 26/46] fix(stepfunctions): States.ALL doesn't appear last in Retry and Catch arrays (#10538) Validate that `States.ALL` is not combined with other errors in `Retry` and `Catch` arrays and sort those arrays so that `States.ALL` appears last. Closes #10530 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-stepfunctions/lib/states/state.ts | 38 ++++++- .../aws-stepfunctions/test/task-base.test.ts | 101 ++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 00334a0934323..c43a7bec159a8 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -247,6 +247,8 @@ export abstract class State extends cdk.Construct implements IChainable { * @internal */ protected _addRetry(props: RetryProps = {}) { + validateErrors(props.errors); + this.retries.push({ ...props, errors: props.errors ? props.errors : [Errors.ALL], @@ -258,6 +260,8 @@ export abstract class State extends cdk.Construct implements IChainable { * @internal */ protected _addCatch(handler: State, props: CatchProps = {}) { + validateErrors(props.errors); + this.catches.push({ next: handler, props: { @@ -386,8 +390,8 @@ export abstract class State extends cdk.Construct implements IChainable { */ protected renderRetryCatch(): any { return { - Retry: renderList(this.retries, renderRetry), - Catch: renderList(this.catches, renderCatch), + Retry: renderList(this.retries, renderRetry, (a, b) => compareErrors(a.errors, b.errors)), + Catch: renderList(this.catches, renderCatch, (a, b) => compareErrors(a.props.errors, b.props.errors)), }; } @@ -501,12 +505,38 @@ function renderCatch(c: CatchTransition) { }; } +/** + * Compares a list of Errors to move Errors.ALL last in a sort function + */ +function compareErrors(a?: string[], b?: string[]) { + if (a?.includes(Errors.ALL)) { + return 1; + } + if (b?.includes(Errors.ALL)) { + return -1; + } + return 0; +} + +/** + * Validates an errors list + */ +function validateErrors(errors?: string[]) { + if (errors?.includes(Errors.ALL) && errors.length > 1) { + throw new Error(`${Errors.ALL} must appear alone in an error list`); + } +} + /** * Render a list or return undefined for an empty list */ -export function renderList(xs: T[], fn: (x: T) => any): any { +export function renderList(xs: T[], mapFn: (x: T) => any, sortFn?: (a: T, b: T) => number): any { if (xs.length === 0) { return undefined; } - return xs.map(fn); + let list = xs; + if (sortFn) { + list = xs.sort(sortFn); + } + return list.map(mapFn); } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts index a57e78ae85c6d..eb14a414e20fc 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts @@ -76,6 +76,69 @@ describe('Task base', () => { }); }); + test('States.ALL catch appears at end of list', () => { + // GIVEN + const httpFailure = new sfn.Fail(stack, 'http', { error: 'HTTP' }); + const otherFailure = new sfn.Fail(stack, 'other', { error: 'Other' }); + const allFailure = new sfn.Fail(stack, 'all'); + + // WHEN + task + .addCatch(httpFailure, { errors: ['HTTPError'] }) + .addCatch(allFailure) + .addCatch(otherFailure, { errors: ['OtherError'] }); + + // THEN + expect(render(task)).toEqual({ + StartAt: 'my-task', + States: { + 'all': { + Type: 'Fail', + }, + 'http': { + Error: 'HTTP', + Type: 'Fail', + }, + 'my-task': { + End: true, + Catch: [ + { + ErrorEquals: ['HTTPError'], + Next: 'http', + }, + { + ErrorEquals: ['OtherError'], + Next: 'other', + }, + { + ErrorEquals: ['States.ALL'], + Next: 'all', + }, + ], + Type: 'Task', + Resource: 'my-resource', + Parameters: { MyParameter: 'myParameter' }, + }, + 'other': { + Error: 'Other', + Type: 'Fail', + }, + }, + }); + }); + + test('addCatch throws when errors are combined with States.ALL', () => { + // GIVEN + const failure = new sfn.Fail(stack, 'failed', { + error: 'DidNotWork', + cause: 'We got stuck', + }); + + expect(() => task.addCatch(failure, { + errors: ['States.ALL', 'HTTPError'], + })).toThrow(/must appear alone/); + }); + test('add retry configuration', () => { // WHEN task.addRetry({ errors: ['HTTPError'], maxAttempts: 2 }) @@ -104,6 +167,44 @@ describe('Task base', () => { }); }); + test('States.ALL retry appears at end of list', () => { + // WHEN + task + .addRetry({ errors: ['HTTPError'] }) + .addRetry() + .addRetry({ errors: ['OtherError'] }); + + // THEN + expect(render(task)).toEqual({ + StartAt: 'my-task', + States: { + 'my-task': { + End: true, + Retry: [ + { + ErrorEquals: ['HTTPError'], + }, + { + ErrorEquals: ['OtherError'], + }, + { + ErrorEquals: ['States.ALL'], + }, + ], + Type: 'Task', + Resource: 'my-resource', + Parameters: { MyParameter: 'myParameter' }, + }, + }, + }); + }); + + test('addRetry throws when errors are combined with States.ALL', () => { + expect(() => task.addRetry({ + errors: ['States.ALL', 'HTTPError'], + })).toThrow(/must appear alone/); + }); + test('add a next state to the task in the chain', () => { // WHEN task.next(new sfn.Pass(stack, 'passState')); From 84738ee8d6d47ec19846c0e4bc7d7096333f96d3 Mon Sep 17 00:00:00 2001 From: Ana Danilochkina Date: Tue, 29 Sep 2020 09:06:13 +0100 Subject: [PATCH 27/46] feat(stepfunctions-tasks): support for SageMaker APIs: CreateEndpoint, CreateEndpointConfig, CreateModel, and UpdateEndpoint (#10187) **Implementation** Update package `@aws-cdk/aws-stepfunctions-tasks` to include support for SageMaker **CreateEndpoint**, **CreateEndpointConfig**, **CreateModel**, **UpdateEndpoint** API as per documentation here: https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html Includes support for the following Amazon SageMaker API calls: * `CreateEndpoint` * `CreateEndpointConfig` * `CreateModel` * `UpdateEndpoint` Closes https://github.com/aws/aws-cdk/issues/6572 --- .../aws-stepfunctions-tasks/README.md | 57 ++ .../aws-stepfunctions-tasks/lib/index.ts | 4 + .../lib/sagemaker/base-types.ts | 212 ++++++ .../lib/sagemaker/create-endpoint-config.ts | 127 ++++ .../lib/sagemaker/create-endpoint.ts | 98 +++ .../lib/sagemaker/create-model.ts | 237 +++++++ .../lib/sagemaker/create-training-job.ts | 7 +- .../lib/sagemaker/create-transform-job.ts | 7 +- .../lib/sagemaker/private/utils.ts | 4 + .../lib/sagemaker/update-endpoint.ts | 79 +++ .../sagemaker/create-endpoint-config.test.ts | 149 ++++ .../test/sagemaker/create-endpoint.test.ts | 118 ++++ .../test/sagemaker/create-model.test.ts | 139 ++++ .../integ.call-sagemaker.expected.json | 637 ++++++++++++++++++ .../test/sagemaker/integ.call-sagemaker.ts | 123 ++++ .../test/sagemaker/update-endpoint.test.ts | 82 +++ 16 files changed, 2070 insertions(+), 10 deletions(-) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/private/utils.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 5faf997549ed6..a663faf7e5325 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -52,6 +52,10 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [SageMaker](#sagemaker) - [Create Training Job](#create-training-job) - [Create Transform Job](#create-transform-job) + - [Create Endpoint](#create-endpoint) + - [Create Endpoint Config](#create-endpoint-config) + - [Create Model](#create-model) + - [Update Endpoint](#update-endpoint) - [SNS](#sns) - [Step Functions](#step-functions) - [Start Execution](#start-execution) @@ -774,6 +778,59 @@ new sfn.SagemakerTransformTask(this, 'Batch Inference', { ``` +### Create Endpoint + +You can call the [`CreateEndpoint`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateEndpoint.html) API from a `Task` state. + +```ts +new sfn.SageMakerCreateEndpoint(this, 'SagemakerEndpoint', { + endpointName: sfn.JsonPath.stringAt('$.EndpointName'), + endpointConfigName: sfn.JsonPath.stringAt('$.EndpointConfigName'), +}); +``` + +### Create Endpoint Config + +You can call the [`CreateEndpointConfig`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateEndpointConfig.html) API from a `Task` state. + +```ts +new sfn.SageMakerCreateEndpointConfig(this, 'SagemakerEndpointConfig', { + endpointConfigName: 'MyEndpointConfig', + productionVariants: [{ + initialInstanceCount: 2, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + modelName: 'MyModel', + variantName: 'awesome-variant', + }], +}); +``` + +### Create Model + +You can call the [`CreateModel`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateModel.html) API from a `Task` state. + +```ts +new sfn.SageMakerCreateModel(this, 'Sagemaker', { + modelName: 'MyModel', + primaryContainer: new tasks.ContainerDefinition({ + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), + mode: tasks.Mode.SINGLE_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + }), +}); +``` + +### Update Endpoint + +You can call the [`UpdateEndpoint`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_UpdateEndpoint.html) API from a `Task` state. + +```ts +new sfn.SageMakerUpdateEndpoint(this, 'SagemakerEndpoint', { + endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.EndpointConfig'), + }); +``` + ## SNS Step Functions supports [Amazon SNS](https://docs.aws.amazon.com/step-functions/latest/dg/connect-sns.html) through the service integration pattern. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index a0e97e9df77ca..98768ce538697 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -14,6 +14,10 @@ export * from './ecs/run-task'; export * from './sagemaker/base-types'; export * from './sagemaker/create-training-job'; export * from './sagemaker/create-transform-job'; +export * from './sagemaker/create-endpoint'; +export * from './sagemaker/create-endpoint-config'; +export * from './sagemaker/create-model'; +export * from './sagemaker/update-endpoint'; export * from './start-execution'; export * from './stepfunctions/start-execution'; export * from './stepfunctions/invoke-activity'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index f701f9e20d6e9..e5a23cce356c6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -625,6 +625,218 @@ export interface TransformResources { readonly volumeEncryptionKey?: kms.IKey; } +/** + * Properties to define a ContainerDefinition + * + * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html + * @experimental + */ +export interface ContainerDefinitionOptions { + /** + * The Amazon EC2 Container Registry (Amazon ECR) path where inference code is stored. + * + * @default - None + */ + readonly image?: DockerImage; + /** + * The environment variables to set in the Docker container + * + * @default - No variables + */ + readonly environmentVariables?: sfn.TaskInput; + /** + * The name or Amazon Resource Name (ARN) of the model package to use to create the model. + * + * @default - None + */ + readonly modelPackageName?: string; + /** + * Defines how many models the container hosts + * + * @default - Mode.SINGLE_MODEL + */ + readonly mode?: Mode; + /** + * This parameter is ignored for models that contain only a PrimaryContainer. + * When a ContainerDefinition is part of an inference pipeline, + * the value of the parameter uniquely identifies the container for the purposes of logging and metrics. + * + * @default - None + */ + readonly containerHostName?: string; + /** + * The S3 path where the model artifacts, which result from model training, are stored. + * This path must point to a single gzip compressed tar archive (.tar.gz suffix). + * The S3 path is required for Amazon SageMaker built-in algorithms, but not if you use your own algorithms. + * + * @default - None + */ + readonly modelS3Location?: S3Location; +} + +/** + * Describes the container, as part of model definition. + * + * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html + * @experimental + */ +export class ContainerDefinition implements IContainerDefinition { + + constructor(private readonly options: ContainerDefinitionOptions) {} + + /** + * Called when the ContainerDefinition type configured on Sagemaker Task + */ + public bind(task: ISageMakerTask): ContainerDefinitionConfig { + return { + parameters: { + ContainerHostname: this.options.containerHostName, + Image: this.options.image?.bind(task).imageUri, + Mode: this.options.mode, + ModelDataUrl: this.options.modelS3Location?.bind(task, { forReading: true }).uri, + ModelPackageName: this.options.modelPackageName, + Environment: this.options.environmentVariables?.value, + }, + }; + } +} + +/** + * Configuration of the container used to host the model + * + * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html + * @experimental + */ +export interface IContainerDefinition { + /** + * Called when the ContainerDefinition is used by a SageMaker task. + */ + bind(task: ISageMakerTask): ContainerDefinitionConfig; +} + +/** + * Configuration options for the ContainerDefinition + */ +export interface ContainerDefinitionConfig { + /** + * Additional parameters to pass to the base task + * + * @default - No additional parameters passed + */ + readonly parameters?: { [key: string]: any }; +} + +/** + * Specifies how many models the container hosts + * + * @experimental + */ +export enum Mode { + /** + * Container hosts a single model + */ + SINGLE_MODEL = 'SingleModel', + /** + * Container hosts multiple models + * + * @see https://docs.aws.amazon.com/sagemaker/latest/dg/multi-model-endpoints.html + */ + MULTI_MODEL = 'MultiModel', +} + +/** + * Identifies a model that you want to host and the resources to deploy for hosting it. + * + * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ProductionVariant.html + * @experimental + */ +export interface ProductionVariant { + /** + * The size of the Elastic Inference (EI) instance to use for the production variant. + * + * @default - None + */ + readonly acceleratorType?: AcceleratorType; + /** + * Number of instances to launch initially. + * + * @default - 1 + */ + readonly initialInstanceCount?: number; + /** + * Determines initial traffic distribution among all of the models that you specify in the endpoint configuration. + * + * @default - 1.0 + */ + readonly initialVariantWeight?: number; + /** + * The ML compute instance type + */ + readonly instanceType: ec2.InstanceType; + /** + * The name of the production variant. + */ + readonly variantName: string; + /** + * The name of the model that you want to host. This is the name that you specified when creating the model. + */ + readonly modelName: string; +} + +/** + * The generation of Elastic Inference (EI) instance + * + * @see https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html + * @experimental + */ +export class AcceleratorClass { + /** + * Elastic Inference accelerator 1st generation + */ + public static readonly EIA1 = AcceleratorClass.of('eia1'); + /** + * Elastic Inference accelerator 2nd generation + */ + public static readonly EIA2 = AcceleratorClass.of('eia2'); + /** + * Custom AcceleratorType + * @param version - Elastic Inference accelerator generation + */ + public static of(version: string) { return new AcceleratorClass(version); } + /** + * @param version - Elastic Inference accelerator generation + */ + private constructor(public readonly version: string) { } +} + +/** + * The size of the Elastic Inference (EI) instance to use for the production variant. + * EI instances provide on-demand GPU computing for inference + * + * @see https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html + * @experimental + */ +export class AcceleratorType { + /** + * AcceleratorType + * + * This class takes a combination of a class and size. + */ + public static of(acceleratorClass: AcceleratorClass, instanceSize: ec2.InstanceSize) { + return new AcceleratorType(`ml.${acceleratorClass}.${instanceSize}`); + } + + constructor(private readonly instanceTypeIdentifier: string) { + } + + /** + * Return the accelerator type as a dotted string + */ + public toString(): string { + return this.instanceTypeIdentifier; + } +} + /** * Specifies the number of records to include in a mini-batch for an HTTP inference request. * diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts new file mode 100644 index 0000000000000..4b3729614c6ea --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts @@ -0,0 +1,127 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; +import { ProductionVariant } from './base-types'; + +/** + * Properties for creating an Amazon SageMaker endpoint configuration + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export interface SageMakerCreateEndpointConfigProps extends sfn.TaskStateBaseProps { + /** + * The name of the endpoint configuration. + */ + readonly endpointConfigName: string; + /** + * AWS Key Management Service key that Amazon SageMaker + * uses to encrypt data on the storage volume attached to the ML compute instance that hosts the endpoint. + * + * @default - None + */ + readonly kmsKey?: kms.IKey; + + /** + * An list of ProductionVariant objects, one for each model that you want to host at this endpoint. + * Identifies a model that you want to host and the resources to deploy for hosting it. + * If you are deploying multiple models, tell Amazon SageMaker how to distribute traffic among the models by specifying variant weights. + */ + readonly productionVariants: ProductionVariant[]; + + /** + * Tags to be applied to the endpoint configuration. + * + * @default - No tags + */ + readonly tags?: sfn.TaskInput; +} + +/** + * A Step Functions Task to create a SageMaker endpoint configuration + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export class SageMakerCreateEndpointConfig extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + ]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + private readonly integrationPattern: sfn.IntegrationPattern; + + constructor(scope: cdk.Construct, id: string, private readonly props: SageMakerCreateEndpointConfigProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.REQUEST_RESPONSE; + validatePatternSupported(this.integrationPattern, SageMakerCreateEndpointConfig.SUPPORTED_INTEGRATION_PATTERNS); + + this.validateProductionVariants(); + this.taskPolicies = this.makePolicyStatements(); + } + + /** + * @internal + */ + protected _renderTask(): any { + return { + Resource: integrationResourceArn('sagemaker', 'createEndpointConfig', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + }; + } + + private renderParameters(): { [key: string]: any } { + return { + EndpointConfigName: this.props.endpointConfigName, + Tags: this.props.tags?.value, + KmsKeyId: this.props.kmsKey?.keyId, + ProductionVariants: this.props.productionVariants.map((variant) => ({ + InitialInstanceCount: variant.initialInstanceCount ? variant.initialInstanceCount : 1, + InstanceType: `ml.${variant.instanceType}`, + ModelName: variant.modelName, + VariantName: variant.variantName, + AcceleratorType: variant.acceleratorType, + InitialVariantWeight: variant.initialVariantWeight, + }), + ), + }; + } + + private makePolicyStatements(): iam.PolicyStatement[] { + const stack = cdk.Stack.of(this); + // https://docs.aws.amazon.com/sagemaker/latest/dg/api-permissions-reference.html + return [ + new iam.PolicyStatement({ + actions: ['sagemaker:CreateEndpointConfig'], + resources: [ + stack.formatArn({ + service: 'sagemaker', + resource: 'endpoint-config', + // If the endpoint configuration name comes from input, we cannot target the policy to a particular ARN prefix reliably. + // SageMaker uses lowercase for resource name in the arn + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.endpointConfigName) ? '*' : `${this.props.endpointConfigName.toLowerCase()}`, + }), + ], + }), + new iam.PolicyStatement({ + actions: ['sagemaker:ListTags'], + // https://docs.aws.amazon.com/step-functions/latest/dg/sagemaker-iam.html + resources: ['*'], + }), + ]; + } + + private validateProductionVariants() { + if (this.props.productionVariants.length < 1 || this.props.productionVariants.length > 10) { + throw new Error('Must specify from 1 to 10 production variants per endpoint configuration'); + } + this.props.productionVariants.forEach((variant) => { + if (variant.initialInstanceCount && variant.initialInstanceCount < 1) throw new Error('Must define at least one instance'); + if ( variant.initialVariantWeight && variant.initialVariantWeight <= 0) { + throw new Error('InitialVariantWeight has minimum value of 0'); + } + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts new file mode 100644 index 0000000000000..5aa97a96c9b38 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts @@ -0,0 +1,98 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; + +/** + * Properties for creating an Amazon SageMaker endpoint + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export interface SageMakerCreateEndpointProps extends sfn.TaskStateBaseProps { + /** + * The name of an endpoint configuration. + */ + readonly endpointConfigName: string; + /** + * The name of the endpoint. The name must be unique within an AWS Region in your AWS account. + */ + readonly endpointName: string; + /** + * Tags to be applied to the endpoint. + * + * @default - No tags + */ + readonly tags?: sfn.TaskInput; +} + +/** + * A Step Functions Task to create a SageMaker endpoint + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export class SageMakerCreateEndpoint extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + ]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + private readonly integrationPattern: sfn.IntegrationPattern; + + constructor(scope: cdk.Construct, id: string, private readonly props: SageMakerCreateEndpointProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.REQUEST_RESPONSE; + validatePatternSupported(this.integrationPattern, SageMakerCreateEndpoint.SUPPORTED_INTEGRATION_PATTERNS); + this.taskPolicies = this.makePolicyStatements(); + } + + /** + * @internal + */ + protected _renderTask(): any { + return { + Resource: integrationResourceArn('sagemaker', 'createEndpoint', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + }; + } + + private renderParameters(): { [key: string]: any } { + return { + EndpointConfigName: this.props.endpointConfigName, + EndpointName: this.props.endpointName, + Tags: this.props.tags?.value, + }; + } + + private makePolicyStatements(): iam.PolicyStatement[] { + const stack = cdk.Stack.of(this); + // https://docs.aws.amazon.com/sagemaker/latest/dg/api-permissions-reference.html + return [ + new iam.PolicyStatement({ + actions: ['sagemaker:createEndpoint'], + resources: [ + stack.formatArn({ + service: 'sagemaker', + resource: 'endpoint', + // If the endpoint name comes from input, we cannot target the policy to a particular ARN prefix reliably. + // SageMaker uses lowercase for resource name in the arn + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.endpointName) ? '*' : `${this.props.endpointName.toLowerCase()}`, + }), + stack.formatArn({ + service: 'sagemaker', + resource: 'endpoint-config', + // If the endpoint config name comes from input, we cannot target the policy to a particular ARN prefix reliably. + // SageMaker uses lowercase for resource name in the arn + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.endpointConfigName) ? '*' : `${this.props.endpointConfigName.toLowerCase()}`, + }), + ], + }), + new iam.PolicyStatement({ + actions: ['sagemaker:ListTags'], + // https://docs.aws.amazon.com/step-functions/latest/dg/sagemaker-iam.html + resources: ['*'], + }), + ]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts new file mode 100644 index 0000000000000..220617ee57b22 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts @@ -0,0 +1,237 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; +import { IContainerDefinition } from './base-types'; + +/** + * Properties for creating an Amazon SageMaker model + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export interface SageMakerCreateModelProps extends sfn.TaskStateBaseProps { + /** + * An execution role that you can pass in a CreateModel API request + * + * @default - a role will be created. + */ + readonly role?: iam.IRole; + /** + * The name of the new model. + */ + readonly modelName: string; + /** + * The definition of the primary docker image containing inference code, associated artifacts, + * and custom environment map that the inference code uses when the model is deployed for predictions. + */ + readonly primaryContainer: IContainerDefinition; + /** + * Specifies the containers in the inference pipeline. + * + * @default - None + */ + readonly containers?: IContainerDefinition[]; + + /** + * Isolates the model container. No inbound or outbound network calls can be made to or from the model container. + * + * @default false + */ + readonly enableNetworkIsolation?: boolean; + + /** + * The VPC that is accessible by the hosted model + * + * @default - None + */ + readonly vpc?: ec2.IVpc; + + /** + * The subnets of the VPC to which the hosted model is connected + * (Note this parameter is only used when VPC is provided) + * + * @default - Private Subnets are selected + */ + readonly subnetSelection?: ec2.SubnetSelection; + + /** + * Tags to be applied to the model. + * + * @default - No tags + */ + readonly tags?: sfn.TaskInput; +} + +/** + * A Step Functions Task to create a SageMaker model + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export class SageMakerCreateModel extends sfn.TaskStateBase implements iam.IGrantable, ec2.IConnectable { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + ]; + /** + * Allows specify security group connections for instances of this fleet. + */ + public readonly connections: ec2.Connections = new ec2.Connections(); + /** + * The execution role for the Sagemaker Create Model API. + */ + public readonly role: iam.IRole; + public readonly grantPrincipal: iam.IPrincipal; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + private readonly vpc?: ec2.IVpc; + private securityGroup?: ec2.ISecurityGroup; + private readonly securityGroups: ec2.ISecurityGroup[] = []; + private readonly subnets?: string[]; + private readonly integrationPattern: sfn.IntegrationPattern; + + constructor(scope: cdk.Construct, id: string, private readonly props: SageMakerCreateModelProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.REQUEST_RESPONSE; + validatePatternSupported(this.integrationPattern, SageMakerCreateModel.SUPPORTED_INTEGRATION_PATTERNS); + + // add the security groups to the connections object + if (props.vpc) { + this.vpc = props.vpc; + this.subnets = props.subnetSelection ? this.vpc.selectSubnets(props.subnetSelection).subnetIds : this.vpc.selectSubnets().subnetIds; + } + + this.role = this.props.role || this.createSagemakerRole(); + this.grantPrincipal = this.role; + this.taskPolicies = this.makePolicyStatements(); + } + + /** + * Add the security group to all instances via the launch configuration + * security groups array. + * + * @param securityGroup: The security group to add + */ + public addSecurityGroup(securityGroup: ec2.ISecurityGroup): void { + this.securityGroups.push(securityGroup); + } + + /** + * @internal + */ + protected _renderTask(): any { + return { + Resource: integrationResourceArn('sagemaker', 'createModel', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + }; + } + + private renderParameters(): { [key: string]: any } { + return { + EnableNetworkIsolation: this.props.enableNetworkIsolation, + ExecutionRoleArn: this.role.roleArn, + ModelName: this.props.modelName, + Tags: this.props.tags?.value, + PrimaryContainer: this.props.primaryContainer.bind(this).parameters, + Containers: this.props.containers?.map(container => (container.bind(this))), + ...this.renderVpcConfig(), + }; + } + + private makePolicyStatements(): iam.PolicyStatement[] { + const stack = cdk.Stack.of(this); + return [ + new iam.PolicyStatement({ + actions: ['sagemaker:CreateModel'], + resources: [ + stack.formatArn({ + service: 'sagemaker', + resource: 'model', + // If the model name comes from input, we cannot target the policy to a particular ARN prefix reliably. + // SageMaker uses lowercase for resource name in the arn + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.modelName) ? '*' : `${this.props.modelName.toLowerCase()}*`, + }), + ], + }), + new iam.PolicyStatement({ + actions: ['sagemaker:ListTags'], + // https://docs.aws.amazon.com/step-functions/latest/dg/sagemaker-iam.html + resources: ['*'], + }), + new iam.PolicyStatement({ + actions: ['iam:PassRole'], + resources: [this.role.roleArn], + conditions: { + StringEquals: { 'iam:PassedToService': 'sagemaker.amazonaws.com' }, + }, + }), + ]; + } + + private createSagemakerRole() { + // https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-roles.html + const role = new iam.Role(this, 'SagemakerRole', { + assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'), + inlinePolicies: { + CreateModel: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: [ + // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncloudwatch.html + 'cloudwatch:PutMetricData', + // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncloudwatchlogs.html + 'logs:CreateLogStream', + 'logs:CreateLogGroup', + 'logs:PutLogEvents', + 'logs:DescribeLogStreams', + // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonelasticcontainerregistry.html + 'ecr:GetAuthorizationToken', + ], + resources: ['*'], + }), + ], + }), + }, + }); + if (this.props.vpc) { + role.addToPrincipalPolicy( + // https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonec2.html + new iam.PolicyStatement({ + actions: [ + 'ec2:CreateNetworkInterface', + 'ec2:CreateNetworkInterfacePermission', + 'ec2:DeleteNetworkInterface', + 'ec2:DeleteNetworkInterfacePermission', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DescribeVpcs', + 'ec2:DescribeDhcpOptions', + 'ec2:DescribeSubnets', + 'ec2:DescribeSecurityGroups', + ], + resources: ['*'], + }), + ); + } + return role; + } + + private renderVpcConfig(): { [key: string]: any } { + // create a security group if not defined + if (this.vpc && this.securityGroup === undefined) { + this.securityGroup = new ec2.SecurityGroup(this, 'ModelSecurityGroup', { + vpc: this.vpc, + }); + this.connections.addSecurityGroup(this.securityGroup); + this.securityGroups.push(this.securityGroup); + } + return this.vpc + ? { + VpcConfig: { + SecurityGroupIds: cdk.Lazy.listValue({ produce: () => this.securityGroups.map((sg) => sg.securityGroupId) }), + Subnets: this.subnets, + }, + } + : {}; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts index 16a74559cf98f..146a78405b4c8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts @@ -4,6 +4,7 @@ import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Construct, Duration, Lazy, Size, Stack } from '@aws-cdk/core'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; import { AlgorithmSpecification, Channel, InputMode, OutputDataConfig, ResourceConfig, S3DataType, StoppingCondition, VpcConfig } from './base-types'; +import { renderTags } from './private/utils'; /** * Properties for creating an Amazon SageMaker training job @@ -224,7 +225,7 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam ...this.renderResourceConfig(this.resourceConfig), ...this.renderStoppingCondition(this.stoppingCondition), ...this.renderHyperparameters(this.props.hyperparameters), - ...this.renderTags(this.props.tags), + ...renderTags(this.props.tags), ...this.renderVpcConfig(this.props.vpcConfig), }; } @@ -296,10 +297,6 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam return params ? { HyperParameters: params } : {}; } - private renderTags(tags: { [key: string]: any } | undefined): { [key: string]: any } { - return tags ? { Tags: Object.keys(tags).map((key) => ({ Key: key, Value: tags[key] })) } : {}; - } - private renderVpcConfig(config: VpcConfig | undefined): { [key: string]: any } { return config ? { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts index b9bdcbf940d3d..d1461de3de7cc 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts @@ -4,6 +4,7 @@ import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Construct, Size, Stack } from '@aws-cdk/core'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; import { BatchStrategy, S3DataType, TransformInput, TransformOutput, TransformResources } from './base-types'; +import { renderTags } from './private/utils'; /** * Properties for creating an Amazon SageMaker training job task @@ -163,7 +164,7 @@ export class SageMakerCreateTransformJob extends sfn.TaskStateBase { ...(this.props.maxConcurrentTransforms ? { MaxConcurrentTransforms: this.props.maxConcurrentTransforms } : {}), ...(this.props.maxPayload ? { MaxPayloadInMB: this.props.maxPayload.toMebibytes() } : {}), ModelName: this.props.modelName, - ...this.renderTags(this.props.tags), + ...renderTags(this.props.tags), ...this.renderTransformInput(this.transformInput), TransformJobName: this.props.transformJobName, ...this.renderTransformOutput(this.props.transformOutput), @@ -212,10 +213,6 @@ export class SageMakerCreateTransformJob extends sfn.TaskStateBase { return environment ? { Environment: environment } : {}; } - private renderTags(tags: { [key: string]: any } | undefined): { [key: string]: any } { - return tags ? { Tags: Object.keys(tags).map((key) => ({ Key: key, Value: tags[key] })) } : {}; - } - private makePolicyStatements(): iam.PolicyStatement[] { const stack = Stack.of(this); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/private/utils.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/private/utils.ts new file mode 100644 index 0000000000000..e308fd890864c --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/private/utils.ts @@ -0,0 +1,4 @@ + +export function renderTags(tags: { [key: string]: any } | undefined): { [key: string]: any } { + return tags ? { Tags: Object.keys(tags).map((key) => ({ Key: key, Value: tags[key] })) } : {}; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts new file mode 100644 index 0000000000000..6e998fd522d15 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts @@ -0,0 +1,79 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; + +/** + * Properties for updating Amazon SageMaker endpoint + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export interface SageMakerUpdateEndpointProps extends sfn.TaskStateBaseProps { + /** + * The name of the new endpoint configuration + */ + readonly endpointConfigName: string; + /** + * The name of the endpoint whose configuration you want to update. + */ + readonly endpointName: string; +} + +/** + * A Step Functions Task to update a SageMaker endpoint + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html + * @experimental + */ +export class SageMakerUpdateEndpoint extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + ]; + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + private readonly integrationPattern: sfn.IntegrationPattern; + + constructor(scope: cdk.Construct, id: string, private readonly props: SageMakerUpdateEndpointProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.REQUEST_RESPONSE; + validatePatternSupported(this.integrationPattern, SageMakerUpdateEndpoint.SUPPORTED_INTEGRATION_PATTERNS); + this.taskPolicies = this.makePolicyStatements(); + } + + /** + * @internal + */ + protected _renderTask(): any { + return { + Resource: integrationResourceArn('sagemaker', 'updateEndpoint', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + }; + } + + private renderParameters(): { [key: string]: any } { + return { + EndpointConfigName: this.props.endpointConfigName, + EndpointName: this.props.endpointName, + }; + } + + private makePolicyStatements(): iam.PolicyStatement[] { + const stack = cdk.Stack.of(this); + // https://docs.aws.amazon.com/sagemaker/latest/dg/api-permissions-reference.html + return [ + new iam.PolicyStatement({ + actions: ['sagemaker:updateEndpoint'], + resources: [ + stack.formatArn({ + service: 'sagemaker', + resource: 'endpoint', + // If the endpoint name comes from input, we cannot target the policy to a particular ARN prefix reliably. + // SageMaker uses lowercase for resource name in the arn + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.endpointName) ? '*' : `${this.props.endpointName.toLowerCase()}`, + }), + ], + }), + ]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts new file mode 100644 index 0000000000000..f2c24889d1d0e --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts @@ -0,0 +1,149 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +let stack: cdk.Stack; + +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); +}); + +test('create basic endpoint config', () => { + // WHEN + const task = new tasks.SageMakerCreateEndpointConfig(stack, 'SagemakerEndpointConfig', { + endpointConfigName: 'MyEndpointConfig', + productionVariants: [{ + initialInstanceCount: 2, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + modelName: 'MyModel', + variantName: 'awesome-variant', + }], + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createEndpointConfig', + ], + ], + }, + End: true, + Parameters: { + EndpointConfigName: 'MyEndpointConfig', + ProductionVariants: [{ + InitialInstanceCount: 2, + InstanceType: 'ml.m5.xlarge', + ModelName: 'MyModel', + VariantName: 'awesome-variant', + }], + }, + }); +}); + +test('create complex endpoint config', () => { + // WHEN + const key = new kms.Key(stack, 'Key'); + + const task = new tasks.SageMakerCreateEndpointConfig(stack, 'SagemakerEndpointConfig', { + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.EndpointConfig'), + kmsKey: key, + productionVariants: [{ + initialInstanceCount: 1, + initialVariantWeight: 0.8, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + modelName: 'MyModel', + variantName: 'awesome-variant', + }, + { + initialInstanceCount: 1, + initialVariantWeight: 0.2, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.XLARGE), + modelName: sfn.JsonPath.stringAt('$.Endpoint.Model'), + variantName: 'awesome-variant-2', + }], + tags: sfn.TaskInput.fromObject([{ + Key: 'Project', + Value: 'ML', + }]), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createEndpointConfig', + ], + ], + }, + End: true, + Parameters: { + 'EndpointConfigName.$': '$.Endpoint.EndpointConfig', + 'KmsKeyId': { + Ref: 'Key961B73FD', + }, + 'ProductionVariants': [{ + InitialInstanceCount: 1, + InitialVariantWeight: 0.8, + InstanceType: 'ml.m5.xlarge', + ModelName: 'MyModel', + VariantName: 'awesome-variant', + }, + { + 'InitialInstanceCount': 1, + 'InitialVariantWeight': 0.2, + 'InstanceType': 'ml.m4.xlarge', + 'ModelName.$': '$.Endpoint.Model', + 'VariantName': 'awesome-variant-2', + }], + 'Tags': [ + { + Key: 'Project', + Value: 'ML', + }, + ], + }, + }); +}); + +test('Cannot create a SageMaker create enpoint config task with empty production variant', () => { + expect(() => new tasks.SageMakerCreateEndpointConfig(stack, 'EndpointConfig', { + endpointConfigName: 'MyEndpointConfig', + productionVariants: [], + })) + .toThrowError(/Must specify from 1 to 10 production variants per endpoint configuration/); +}); + +test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => new tasks.SageMakerCreateEndpointConfig(stack, 'EndpointConfig', { + endpointConfigName: 'MyEndpointConfig', + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + productionVariants: [{ + initialInstanceCount: 2, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + modelName: 'MyModel', + variantName: 'awesome-variant', + }], + })) + .toThrowError(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE. Received: WAIT_FOR_TASK_TOKEN/i); +}); + + diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts new file mode 100644 index 0000000000000..4e7f67148b731 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts @@ -0,0 +1,118 @@ +import '@aws-cdk/assert/jest'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +let stack: cdk.Stack; + +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); +}); + +test('create endpoint', () => { + // WHEN + const task = new tasks.SageMakerCreateEndpoint(stack, 'SagemakerEndpoint', { + endpointName: 'MyEndpoint', + endpointConfigName: 'MyEndpointConfig', + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createEndpoint', + ], + ], + }, + End: true, + Parameters: { + EndpointConfigName: 'MyEndpointConfig', + EndpointName: 'MyEndpoint', + }, + }); +}); + +test('create endpoint with tags', () => { + // WHEN + const task = new tasks.SageMakerCreateEndpoint(stack, 'SagemakerEndpoint', { + endpointName: 'MyEndpoint', + endpointConfigName: 'MyEndpointConfig', + tags: sfn.TaskInput.fromObject([{ + Key: 'Label', + Value: 'ML', + }]), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createEndpoint', + ], + ], + }, + End: true, + Parameters: { + EndpointConfigName: 'MyEndpointConfig', + EndpointName: 'MyEndpoint', + Tags: [ + { Key: 'Label', Value: 'ML' }, + ], + }, + }); +}); + +test('create endpoint with input from task', () => { + // WHEN + const task = new tasks.SageMakerCreateEndpoint(stack, 'SagemakerEndpoint', { + endpointName: sfn.JsonPath.stringAt('$.EndpointName'), + endpointConfigName: sfn.JsonPath.stringAt('$.EndpointConfig'), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createEndpoint', + ], + ], + }, + End: true, + Parameters: { + 'EndpointConfigName.$': '$.EndpointConfig', + 'EndpointName.$': '$.EndpointName', + }, + }); +}); + +test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new tasks.SageMakerCreateEndpoint(stack, 'TrainSagemaker', { + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + endpointConfigName: 'MyEndpointConfig', + endpointName: 'MyEndpoint', + }); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE. Received: WAIT_FOR_TASK_TOKEN/i); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts new file mode 100644 index 0000000000000..4a1dedeac1f21 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts @@ -0,0 +1,139 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +let stack: cdk.Stack; + +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); +}); + +test('create basic model', () => { + // WHEN + const task = new tasks.SageMakerCreateModel(stack, 'SagemakerModel', { + modelName: 'MyModel', + primaryContainer: new tasks.ContainerDefinition({ + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), + mode: tasks.Mode.SINGLE_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + }), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createModel', + ], + ], + }, + End: true, + Parameters: { + ExecutionRoleArn: { 'Fn::GetAtt': ['SagemakerModelSagemakerRoleF5035084', 'Arn'] }, + ModelName: 'MyModel', + PrimaryContainer: { + 'Image.$': '$.Model.imageName', + 'ModelDataUrl.$': '$.TrainingJob.ModelArtifacts.S3ModelArtifacts', + 'Mode': 'SingleModel', + }, + }, + }, + ); +}); + +test('create complex model', () => { + // WHEN + const vpc = new ec2.Vpc(stack, 'VPC'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc, description: 'My SG' }); + securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world'); + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSageMakerFullAccess'), + ], + }); + + const task = new tasks.SageMakerCreateModel(stack, 'SagemakerModel', { + modelName: sfn.JsonPath.stringAt('$.ModelName'), + primaryContainer: new tasks.ContainerDefinition({ + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), + mode: tasks.Mode.MULTI_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + }), + enableNetworkIsolation: true, + role, + tags: sfn.TaskInput.fromObject([{ + Key: 'Project', + Value: 'ML', + }]), + vpc, + }); + task.addSecurityGroup(securityGroup); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:createModel', + ], + ], + }, + End: true, + Parameters: { + 'ExecutionRoleArn': { 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'] }, + 'ModelName.$': '$.ModelName', + 'PrimaryContainer': { + 'Image.$': '$.Model.imageName', + 'ModelDataUrl.$': '$.TrainingJob.ModelArtifacts.S3ModelArtifacts', + 'Mode': 'MultiModel', + }, + 'EnableNetworkIsolation': true, + 'Tags': [ + { Key: 'Project', Value: 'ML' }, + ], + 'VpcConfig': { + SecurityGroupIds: [ + { 'Fn::GetAtt': ['SecurityGroupDD263621', 'GroupId'] }, + { 'Fn::GetAtt': ['SagemakerModelModelSecurityGroup4D2A2C36', 'GroupId'] }, + ], + Subnets: [ + { Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' }, + { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, + ], + }, + }, + }, + ); +}); + +test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new tasks.SageMakerCreateModel(stack, 'Sagemaker', { + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + modelName: 'MyModel', + primaryContainer: new tasks.ContainerDefinition({ + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), + mode: tasks.Mode.SINGLE_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + }), + }); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE. Received: WAIT_FOR_TASK_TOKEN/i); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json new file mode 100644 index 0000000000000..57244344cbaed --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json @@ -0,0 +1,637 @@ +{ + "Resources": { + "EncryptionKey1B843E66": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "TrainTaskSagemakerRoleD5A6F967", + "Arn" + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "TrainTaskSagemakerRoleD5A6F967", + "Arn" + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "TrainingData3FDB6D34": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "TrainTaskSagemakerRoleD5A6F967": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "sagemaker.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudwatch:PutMetricData", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:CreateLogGroup", + "logs:DescribeLogStreams", + "ecr:GetAuthorizationToken" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CreateTrainingJob" + } + ] + } + }, + "TrainTaskSagemakerRoleDefaultPolicy163CCC72": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TrainingData3FDB6D34", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TrainingData3FDB6D34", + "Arn" + ] + }, + "/data/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TrainingData3FDB6D34", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TrainingData3FDB6D34", + "Arn" + ] + }, + "/result/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TrainTaskSagemakerRoleDefaultPolicy163CCC72", + "Roles": [ + { + "Ref": "TrainTaskSagemakerRoleD5A6F967" + } + ] + } + }, + "CreateModelSagemakerRoleC2E07FC0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "sagemaker.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudwatch:PutMetricData", + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "ecr:GetAuthorizationToken" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CreateModel" + } + ] + } + }, + "CreateModelSagemakerRoleDefaultPolicyD7EAA79E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CreateModelSagemakerRoleDefaultPolicyD7EAA79E", + "Roles": [ + { + "Ref": "CreateModelSagemakerRoleC2E07FC0" + } + ] + } + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sagemaker:CreateTrainingJob", + "sagemaker:DescribeTrainingJob", + "sagemaker:StopTrainingJob" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":training-job/mytrainingjob*" + ] + ] + } + }, + { + "Action": "sagemaker:ListTags", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "sagemaker.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TrainTaskSagemakerRoleD5A6F967", + "Arn" + ] + } + }, + { + "Action": "sagemaker:CreateModel", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":model/*" + ] + ] + } + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "sagemaker.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CreateModelSagemakerRoleC2E07FC0", + "Arn" + ] + } + }, + { + "Action": "sagemaker:CreateEndpointConfig", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":endpoint-config/*" + ] + ] + } + }, + { + "Action": "sagemaker:createEndpoint", + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":endpoint/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":endpoint-config/*" + ] + ] + } + ] + }, + { + "Action": "sagemaker:updateEndpoint", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sagemaker:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":endpoint/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"Endpoint\":{\"Image\":\"ImageArn\",\"Config\":\"MyEndpointConfig\",\"Name\":\"MyEndpointName\",\"Model\":\"MyEndpointModelName\"}},\"Next\":\"Train Task\"},\"Train Task\":{\"Next\":\"Create Model\",\"Type\":\"Task\",\"ResultPath\":\"$.TrainingJob\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::sagemaker:createTrainingJob\",\"Parameters\":{\"TrainingJobName\":\"mytrainingjob\",\"RoleArn\":\"", + { + "Fn::GetAtt": [ + "TrainTaskSagemakerRoleD5A6F967", + "Arn" + ] + }, + "\",\"AlgorithmSpecification\":{\"TrainingInputMode\":\"File\",\"AlgorithmName\":\"arn:aws:sagemaker:us-east-1:865070037744:algorithm/scikit-decision-trees-15423055-57b73412d2e93e9239e4e16f83298b8f\"},\"InputDataConfig\":[{\"ChannelName\":\"InputData\",\"DataSource\":{\"S3DataSource\":{\"S3Uri\":\"https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "TrainingData3FDB6D34" + }, + "/data/\",\"S3DataType\":\"S3Prefix\"}}}],\"OutputDataConfig\":{\"S3OutputPath\":\"https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "TrainingData3FDB6D34" + }, + "/result/\"},\"ResourceConfig\":{\"InstanceCount\":1,\"InstanceType\":\"ml.m4.xlarge\",\"VolumeSizeInGB\":10},\"StoppingCondition\":{\"MaxRuntimeInSeconds\":3600}}},\"Create Model\":{\"Next\":\"Create enpoint config\",\"Type\":\"Task\",\"ResultPath\":\"$.Model\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::sagemaker:createModel\",\"Parameters\":{\"ExecutionRoleArn\":\"", + { + "Fn::GetAtt": [ + "CreateModelSagemakerRoleC2E07FC0", + "Arn" + ] + }, + "\",\"ModelName.$\":\"$.Endpoint.Model\",\"PrimaryContainer\":{\"Image.$\":\"$.Endpoint.Image\",\"Mode\":\"SingleModel\",\"ModelDataUrl.$\":\"$.TrainingJob.ModelArtifacts.S3ModelArtifacts\"}}},\"Create enpoint config\":{\"Next\":\"Create endpoint\",\"Type\":\"Task\",\"ResultPath\":\"$.EndpointConfig\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::sagemaker:createEndpointConfig\",\"Parameters\":{\"EndpointConfigName.$\":\"$.Endpoint.Config\",\"ProductionVariants\":[{\"InitialInstanceCount\":1,\"InstanceType\":\"ml.m5.xlarge\",\"ModelName.$\":\"$.Endpoint.Model\",\"VariantName\":\"awesome-variant\"}]}},\"Create endpoint\":{\"End\":true,\"Catch\":[{\"ErrorEquals\":[\"States.TaskFailed\"],\"ResultPath\":\"$.EndpointDeployed\",\"Next\":\"Update endpoint\"}],\"Type\":\"Task\",\"ResultPath\":\"$.EndpointDeployed\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::sagemaker:createEndpoint\",\"Parameters\":{\"EndpointConfigName.$\":\"$.Endpoint.Config\",\"EndpointName.$\":\"$.Endpoint.Name\",\"Tags\":[{\"Key\":\"Endpoint\",\"Value\":\"New\"}]}},\"Update endpoint\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::sagemaker:updateEndpoint\",\"Parameters\":{\"EndpointConfigName.$\":\"$.Endpoint.Config\",\"EndpointName.$\":\"$.Endpoint.Name\"}}}}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "StateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.ts new file mode 100644 index 0000000000000..6ede459130439 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.ts @@ -0,0 +1,123 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + + +/* + * Creates a state machine with a task states needed to deploy the SageMaker Endpoint + * + * SageMaker jobs need training algorithms. These can be found in the AWS marketplace + * or created. + * + * Stack verification steps: + * The generated State Machine can be executed from the CLI (or Step Functions console) + * and runs with an execution status of `Succeeded`. + * + * -- aws stepfunctions start-execution --state-machine-arn provides execution arn + * -- aws stepfunctions describe-execution --execution-arn returns a status of `Succeeded` + */ + + +class CallSageMakerStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) { + super(scope, id, props); + + const encryptionKey = new kms.Key(this, 'EncryptionKey', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const trainingData = new s3.Bucket(this, 'TrainingData', { + encryption: s3.BucketEncryption.KMS, + encryptionKey, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + const trainingJob = new tasks.SageMakerCreateTrainingJob(this, 'Train Task', { + algorithmSpecification: { + algorithmName: 'arn:aws:sagemaker:us-east-1:865070037744:algorithm/scikit-decision-trees-15423055-57b73412d2e93e9239e4e16f83298b8f', + }, + inputDataConfig: [{ + channelName: 'InputData', + dataSource: { + s3DataSource: { + s3Location: tasks.S3Location.fromBucket(trainingData, 'data/'), + }, + }, + }], + outputDataConfig: { s3OutputLocation: tasks.S3Location.fromBucket(trainingData, 'result/') }, + trainingJobName: 'mytrainingjob', + resultPath: '$.TrainingJob', + }); + const createModelTask = new tasks.SageMakerCreateModel(this, 'Create Model', { + modelName: sfn.JsonPath.stringAt('$.Endpoint.Model'), + primaryContainer: new tasks.ContainerDefinition({ + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Endpoint.Image')), + mode: tasks.Mode.SINGLE_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + }), + resultPath: '$.Model', + }); + + const createEndpointConfigTask = new tasks.SageMakerCreateEndpointConfig(this, 'Create enpoint config', { + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.Config'), + productionVariants: [{ + initialInstanceCount: 1, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), + variantName: 'awesome-variant', + modelName: sfn.JsonPath.stringAt('$.Endpoint.Model'), + }], + resultPath: '$.EndpointConfig', + }); + + const createEndpointTask = new tasks.SageMakerCreateEndpoint(this, 'Create endpoint', { + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.Config'), + endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), + tags: sfn.TaskInput.fromObject([{ + Key: 'Endpoint', + Value: 'New', + }]), + resultPath: '$.EndpointDeployed', + }); + + const updateEndpointTask = new tasks.SageMakerUpdateEndpoint(this, 'Update endpoint', { + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.Config'), + endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), + }); + + createEndpointTask.addCatch(updateEndpointTask, { + errors: ['States.TaskFailed'], + resultPath: '$.EndpointDeployed', + }); + const definition = new sfn.Pass(this, 'Start', { + result: sfn.Result.fromObject( + { + Endpoint: { + // Change to real parameters for the actual run & put the testing data in the training bucket + Image: 'ImageArn', + Config: 'MyEndpointConfig', + Name: 'MyEndpointName', + Model: 'MyEndpointModelName', + }, + }), + }) + .next(trainingJob) + .next(createModelTask) + .next(createEndpointConfigTask) + .next(createEndpointTask); + + const stateMachine = new sfn.StateMachine(this, 'StateMachine', { + definition, + }); + + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + }); + } +} + +const app = new cdk.App(); +new CallSageMakerStack(app, 'aws-stepfunctions-integ-sagemaker'); +app.synth(); + diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts new file mode 100644 index 0000000000000..f628ddbe9ed14 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts @@ -0,0 +1,82 @@ +import '@aws-cdk/assert/jest'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +let stack: cdk.Stack; + +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); +}); + +test('update endpoint', () => { + // WHEN + const task = new tasks.SageMakerUpdateEndpoint(stack, 'SagemakerEndpoint', { + endpointName: 'MyEndpoint', + endpointConfigName: 'MyEndpointConfig', + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:updateEndpoint', + ], + ], + }, + End: true, + Parameters: { + EndpointConfigName: 'MyEndpointConfig', + EndpointName: 'MyEndpoint', + }, + }); +}); + +test('pass parameters to update endpoint', () => { + // WHEN + const task = new tasks.SageMakerUpdateEndpoint(stack, 'SagemakerEndpoint', { + endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.Config'), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sagemaker:updateEndpoint', + ], + ], + }, + End: true, + Parameters: { + 'EndpointConfigName.$': '$.Endpoint.Config', + 'EndpointName.$': '$.Endpoint.Name', + }, + }); +}); + + +test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new tasks.SageMakerUpdateEndpoint(stack, 'UpdateSagemaker', { + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + endpointConfigName: 'MyEndpointConfig', + endpointName: 'MyEndpoint', + }); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE. Received: WAIT_FOR_TASK_TOKEN/i); +}); From 56ef837a7831869d2165a0c846f4aa6e43d120b9 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 29 Sep 2020 10:31:56 +0200 Subject: [PATCH 28/46] chore(cli): undo rename 'versionReporting' => 'analyticsReporting' (#10587) The switch `versionReporting` was renamed to `analyticsReporting` in the framework in order to be more generic. However, this rename was carried through in parts of the CLI as well, especially in parts of the settings handling. This part is however not strongly typed but *stringly* typed (remember that settings are also read from `cdk.json`) and it's rather tricky to change there. Undo this change as it's breaking the integration tests; for now, the switch will still be called `versionReporting` in the CLI. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index b091ba493c6dd..af9193d9a6456 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -54,7 +54,7 @@ export class Configuration { public context = new Context(); public readonly defaultConfig = new Settings({ - analyticsReporting: true, + versionReporting: true, pathMetadata: true, output: 'cdk.out', }); @@ -239,7 +239,7 @@ export class Settings { bucketName: argv.bootstrapBucketName, kmsKeyId: argv.bootstrapKmsKeyId, }, - analyticsReporting: argv.versionReporting, + versionReporting: argv.versionReporting, staging: argv.staging, output: argv.output, progress: argv.progress, From 07d14aeb6a18e61be19047442e2058e2c7e5578a Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Tue, 29 Sep 2020 14:28:04 +0530 Subject: [PATCH 29/46] fix(stepfunctions): JsonPath does not support path with array (#10553) Fix JsonPath not supporting array paths closes #9403 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-stepfunctions/lib/fields.ts | 9 +++++++-- .../aws-stepfunctions/test/fields.test.ts | 16 ++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts index 407f93cd3d162..9064fb93d2b6e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts @@ -214,8 +214,13 @@ export class FieldUtils { } function validateJsonPath(path: string) { - if (path !== '$' && !path.startsWith('$.') && path !== '$$' && !path.startsWith('$$.')) { - throw new Error(`JSON path values must be exactly '$', '$$', start with '$.' or start with '$$.' Received: ${path}`); + if (path !== '$' + && !path.startsWith('$.') + && path !== '$$' + && !path.startsWith('$$.') + && !path.startsWith('$[') + ) { + throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.' or start with '$[' Received: ${path}`); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index ef6d15fd86519..1235dc9e5d526 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -2,6 +2,8 @@ import '@aws-cdk/assert/jest'; import { FieldUtils, JsonPath } from '../lib'; describe('Fields', () => { + const jsonPathValidationErrorMsg = /exactly '\$', '\$\$', start with '\$.', start with '\$\$.' or start with '\$\['/; + test('deep replace correctly handles fields in arrays', () => { expect( FieldUtils.renderObject({ @@ -70,16 +72,18 @@ describe('Fields', () => { test('datafield path must be correct', () => { expect(JsonPath.stringAt('$')).toBeDefined(); - expect(() => JsonPath.stringAt('$hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); - - expect(() => JsonPath.stringAt('hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); + expect(() => JsonPath.stringAt('$hello')).toThrowError(jsonPathValidationErrorMsg); + expect(() => JsonPath.stringAt('hello')).toThrowError(jsonPathValidationErrorMsg); }), test('context path must be correct', () => { expect(JsonPath.stringAt('$$')).toBeDefined(); - expect(() => JsonPath.stringAt('$$hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); - - expect(() => JsonPath.stringAt('hello')).toThrowError(/exactly '\$', '\$\$', start with '\$.' or start with '\$\$.'/); + expect(() => JsonPath.stringAt('$$hello')).toThrowError(jsonPathValidationErrorMsg); + expect(() => JsonPath.stringAt('hello')).toThrowError(jsonPathValidationErrorMsg); + }), + test('datafield path with array must be correct', () => { + expect(JsonPath.stringAt('$[0]')).toBeDefined(); + expect(JsonPath.stringAt("$['abc']")).toBeDefined(); }), test('test contains task token', () => { expect(true).toEqual( From 1f7311f56457556a6f229e745cd24e3f1e5fe1d3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 29 Sep 2020 14:17:59 +0200 Subject: [PATCH 30/46] feat(pipelines): allow disabling of KMS keys (#10396) KMS keys for cross-account actions used to be created automatically, but incur a $1/month charge for every region, adding a charge you don't need if you don't plan to deploy in to cross-account destinations. Add the option `crossAccountKeys: false` to allow users to switch off the KMS keys and avoid the charge if they don't need it. Relates to #10115. Must not be merged before #10474. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-codepipeline/README.md | 135 +++++++++--- .../@aws-cdk/aws-codepipeline/lib/action.ts | 3 + .../@aws-cdk/aws-codepipeline/lib/artifact.ts | 2 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 193 ++++++++++-------- .../cross-region-support-stack.ts | 51 ++++- .../{ => private}/full-action-descriptor.ts | 4 +- .../lib/private/rich-action.ts | 116 +++++++++++ .../lib/{ => private}/stage.ts | 8 +- .../lib/{ => private}/validation.ts | 4 +- .../@aws-cdk/aws-codepipeline/package.json | 1 - .../aws-codepipeline/test/action.test.ts | 2 +- .../aws-codepipeline/test/cross-env.test.ts | 135 ++++++++++++ .../test/fake-build-action.ts | 4 +- .../test/general-validation.test.ts | 2 +- .../aws-codepipeline/test/stages.test.ts | 2 +- packages/@aws-cdk/pipelines/README.md | 20 ++ packages/@aws-cdk/pipelines/lib/pipeline.ts | 24 +++ 17 files changed, 568 insertions(+), 138 deletions(-) rename packages/@aws-cdk/aws-codepipeline/lib/{ => private}/cross-region-support-stack.ts (71%) rename packages/@aws-cdk/aws-codepipeline/lib/{ => private}/full-action-descriptor.ts (97%) create mode 100644 packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts rename packages/@aws-cdk/aws-codepipeline/lib/{ => private}/stage.ts (96%) rename packages/@aws-cdk/aws-codepipeline/lib/{ => private}/validation.ts (96%) create mode 100644 packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 373caa6017b35..e32f07f486c0f 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -27,6 +27,22 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { }); ``` +Be aware that in the default configuration, the `Pipeline` construct creates +an AWS Key Management Service (AWS KMS) Customer Master Key (CMK) for you to +encrypt the artifacts in the artifact bucket, which incurs a cost of +**$1/month**. This default configuration is necessary to allow cross-account +actions. + +If you do not intend to perform cross-account deployments, you can disable +the creation of the Customer Master Keys by passing `crossAccountKeys: false` +when defining the Pipeline: + +```ts +const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { + crossAccountKeys: false, +}); +``` + ### Stages You can provide Stages when creating the Pipeline: @@ -80,16 +96,101 @@ or you can use the `IStage.addAction()` method to mutate an existing Stage: sourceStage.addAction(someAction); ``` +### Cross-account CodePipelines + +> Cross-account Pipeline actions require that the Pipeline has *not* been +> created with `crossAccountKeys: false`. + +Most pipeline Actions accept an AWS resource object to operate on. For example: + +* `S3DeployAction` accepts an `s3.IBucket`. +* `CodeBuildAction` accepts a `codebuild.IProject`. +* etc. + +These resources can be either newly defined (`new s3.Bucket(...)`) or imported +(`s3.Bucket.fromBucketAttributes(...)`) and identify the resource that should +be changed. + +These resources can be in different accounts than the pipeline itself. For +example, the following action deploys to an imported S3 bucket from a +different account: + +```typescript +stage.addAction(new codepipeline_actions.S3DeployAction({ + bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { + account: '123456789012', + // ... + }), + // ... +})); +``` + +Actions that don't accept a resource object accept an explicit `account` parameter: + +```typescript +stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + account: '123456789012', + // ... +})); +``` + +The `Pipeline` construct automatically defines an **IAM Role** for you in the +target account which the pipeline will assume to perform that action. This +Role will be defined in a **support stack** named +`-support-`, that will automatically be deployed +before the stack containing the pipeline. + +If you do not want to use the generated role, you can also explicitly pass a +`role` when creating the action. In that case, the action will operate in the +account the role belongs to: + +```ts +stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + // ... + role: iam.Role.fromRoleArn(this, 'ActionRole', '...'), +})); +``` + ### Cross-region CodePipelines -You can also use the cross-region feature to deploy resources -into a different region than your Pipeline is in. +Similar to how you set up a cross-account Action, the AWS resource object you +pass to actions can also be in different *Regions*. For example, the +following Action deploys to an imported S3 bucket from a different Region: + +```typescript +stage.addAction(new codepipeline_actions.S3DeployAction({ + bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { + region: 'us-west-1', + // ... + }), + // ... +})); +``` -It works like this: +Actions that don't take an AWS resource will accept an explicit `region` +parameter: ```typescript -const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { +stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ // ... + region: 'us-west-1', +})); +``` + +The `Pipeline` construct automatically defines a **replication bucket** for +you in the target region, which the pipeline will replicate artifacts to and +from. This Bucket will be defined in a **support stack** named +`-support-`, that will automatically be deployed +before the stack containing the pipeline. + +If you don't want to use these support stacks, and already have buckets in +place to serve as replication buckets, you can supply these at Pipeline definition +time using the `crossRegionReplicationBuckets` parameter. Example: + +```ts +const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { /* ... */ }); + // ... + crossRegionReplicationBuckets: { // note that a physical name of the replication Bucket must be known at synthesis time 'us-west-1': s3.Bucket.fromBucketAttributes(this, 'UsWest1ReplicationBucket', { @@ -101,32 +202,6 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { }), }, }); - -// later in the code... -new codepipeline_actions.CloudFormationCreateUpdateStackAction({ - actionName: 'CFN_US_West_1', - // ... - region: 'us-west-1', -}); -``` - -This way, the `CFN_US_West_1` Action will operate in the `us-west-1` region, -regardless of which region your Pipeline is in. - -If you don't provide a bucket for a region (other than the Pipeline's region) -that you're using for an Action, -there will be a new Stack, called `-support-`, -defined for you, containing a replication Bucket. -This new Stack will depend on your Pipeline Stack, -so deploying the Pipeline Stack will deploy the support Stack(s) first. -Example: - -```bash -$ cdk ls -MyMainStack -MyMainStack-support-us-west-1 -$ cdk deploy MyMainStack -# output of cdk deploy here... ``` See [the AWS docs here](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-create-cross-region.html) diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index d417195622a46..b2b6e79154699 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -114,6 +114,9 @@ export interface ActionConfig { readonly configuration?: any; } +/** + * A Pipeline Action + */ export interface IAction { readonly actionProperties: ActionProperties; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts index fab9b46edcfe6..487cc7c422189 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts @@ -1,6 +1,6 @@ import * as s3 from '@aws-cdk/aws-s3'; import { Lazy, Token } from '@aws-cdk/core'; -import * as validation from './validation'; +import * as validation from './private/validation'; /** * An output artifact of an action. Artifacts can be used as input by some actions. diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 4aacc589b3689..b3d18091322b1 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -4,15 +4,16 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { App, BootstraplessSynthesizer, Construct as CoreConstruct, DefaultStackSynthesizer, - IStackSynthesizer, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token, TokenComparison, + IStackSynthesizer, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ActionCategory, IAction, IPipeline, IStage } from './action'; import { CfnPipeline } from './codepipeline.generated'; -import { CrossRegionSupportConstruct, CrossRegionSupportStack } from './cross-region-support-stack'; -import { FullActionDescriptor } from './full-action-descriptor'; -import { Stage } from './stage'; -import { validateName, validateNamespaceName, validateSourceAction } from './validation'; +import { CrossRegionSupportConstruct, CrossRegionSupportStack } from './private/cross-region-support-stack'; +import { FullActionDescriptor } from './private/full-action-descriptor'; +import { RichAction } from './private/rich-action'; +import { Stage } from './private/stage'; +import { validateName, validateNamespaceName, validateSourceAction } from './private/validation'; /** * Allows you to control where to place a new Stage when it's added to the Pipeline. @@ -104,6 +105,23 @@ export interface PipelineProps { * @default - None. */ readonly stages?: StageProps[]; + + /** + * Create KMS keys for cross-account deployments + * + * This controls whether the pipeline is enabled for cross-account deployments. + * + * By default cross-account deployments are enabled, but this feature requires + * that KMS Customer Master Keys are created which have a cost of $1/month. + * + * If you do not need cross-account deployments, you can set this to `false` to + * not create those keys and save on that cost (the artifact bucket will be + * encrypted with an AWS-managed key). However, cross-account deployments will + * no longer be possible. + * + * @default true + */ + readonly crossAccountKeys?: boolean; } abstract class PipelineBase extends Resource implements IPipeline { @@ -211,6 +229,7 @@ export class Pipeline extends PipelineBase { private readonly crossRegionBucketsPassed: boolean; private readonly _crossRegionSupport: { [region: string]: CrossRegionSupport } = {}; private readonly _crossAccountSupport: { [account: string]: Stack } = {}; + private readonly crossAccountKeys: boolean; constructor(scope: Construct, id: string, props: PipelineProps = {}) { super(scope, id, { @@ -224,27 +243,37 @@ export class Pipeline extends PipelineBase { throw new Error('Only one of artifactBucket and crossRegionReplicationBuckets can be specified!'); } + + // @deprecated(v2): switch to default false + this.crossAccountKeys = props.crossAccountKeys ?? true; + // If a bucket has been provided, use it - otherwise, create a bucket. let propsBucket = this.getArtifactBucketFromProps(props); + if (!propsBucket) { - const encryptionKey = new kms.Key(this, 'ArtifactsBucketEncryptionKey', { - // remove the key - there is a grace period of a few days before it's gone for good, - // that should be enough for any emergency access to the bucket artifacts - removalPolicy: RemovalPolicy.DESTROY, - }); + let encryptionKey; + + if (this.crossAccountKeys) { + encryptionKey = new kms.Key(this, 'ArtifactsBucketEncryptionKey', { + // remove the key - there is a grace period of a few days before it's gone for good, + // that should be enough for any emergency access to the bucket artifacts + removalPolicy: RemovalPolicy.DESTROY, + }); + // add an alias to make finding the key in the console easier + new kms.Alias(this, 'ArtifactsBucketEncryptionKeyAlias', { + aliasName: this.generateNameForDefaultBucketKeyAlias(), + targetKey: encryptionKey, + removalPolicy: RemovalPolicy.DESTROY, // destroy the alias along with the key + }); + } + propsBucket = new s3.Bucket(this, 'ArtifactsBucket', { bucketName: PhysicalName.GENERATE_IF_NEEDED, encryptionKey, - encryption: s3.BucketEncryption.KMS, + encryption: encryptionKey ? s3.BucketEncryption.KMS : s3.BucketEncryption.KMS_MANAGED, blockPublicAccess: new s3.BlockPublicAccess(s3.BlockPublicAccess.BLOCK_ALL), removalPolicy: RemovalPolicy.RETAIN, }); - // add an alias to make finding the key in the console easier - new kms.Alias(this, 'ArtifactsBucketEncryptionKeyAlias', { - aliasName: this.generateNameForDefaultBucketKeyAlias(), - targetKey: encryptionKey, - removalPolicy: RemovalPolicy.DESTROY, // destroy the alias along with the key - }); } this.artifactBucket = propsBucket; @@ -365,22 +394,28 @@ export class Pipeline extends PipelineBase { /** @internal */ public _attachActionToPipeline(stage: Stage, action: IAction, actionScope: CoreConstruct): FullActionDescriptor { + const richAction = new RichAction(action, this); + // handle cross-region actions here - const crossRegionInfo = this.ensureReplicationResourcesExistFor(action); + const crossRegionInfo = this.ensureReplicationResourcesExistFor(richAction); - // get the role for the given action - const actionRole = this.getRoleForAction(stage, action, actionScope); + // get the role for the given action, handling if it's cross-account + const actionRole = this.getRoleForAction(stage, richAction, actionScope); // // CodePipeline Variables - validateNamespaceName(action.actionProperties.variablesNamespace); + validateNamespaceName(richAction.actionProperties.variablesNamespace); // bind the Action - const actionConfig = action.bind(actionScope, stage, { + const actionConfig = richAction.bind(actionScope, stage, { role: actionRole ? actionRole : this.role, bucket: crossRegionInfo.artifactBucket, }); return new FullActionDescriptor({ + // must be 'action', not 'richAction', + // as those are returned by the IStage.actions property, + // and it's important customers of Pipeline get the same instance + // back as they added to the pipeline action, actionConfig, actionRole, @@ -405,54 +440,17 @@ export class Pipeline extends PipelineBase { ]; } - private ensureReplicationResourcesExistFor(action: IAction): CrossRegionInfo { - const pipelineStack = Stack.of(this); - - let actionRegion: string | undefined; - let otherStack: Stack | undefined; - - const actionResource = action.actionProperties.resource; - if (actionResource) { - const pipelineAndActionRegionComparison = Token.compareStrings(this.env.region, actionResource.env.region); - const pipelineAndActionInDifferentRegions = pipelineAndActionRegionComparison === TokenComparison.ONE_UNRESOLVED || - pipelineAndActionRegionComparison === TokenComparison.DIFFERENT; - if (pipelineAndActionInDifferentRegions) { - actionRegion = actionResource.env.region; - - // if the resource is from a different stack in another region but the same account, - // use that stack as home for the cross-region support resources - const actionResourceStack = Stack.of(actionResource); - const actionResourceAndItsStackRegionComparison = Token.compareStrings(actionResource.env.region, actionResourceStack.region); - const actionResourceInSameRegionAsItsStack = actionResourceAndItsStackRegionComparison === TokenComparison.SAME || - actionResourceAndItsStackRegionComparison === TokenComparison.BOTH_UNRESOLVED; - const pipelineAndActionResourceStackAccountComparison = Token.compareStrings(this.env.account, actionResourceStack.account); - const pipelineAndActionResourceStackInSameAccount = pipelineAndActionResourceStackAccountComparison === TokenComparison.SAME || - pipelineAndActionResourceStackAccountComparison === TokenComparison.BOTH_UNRESOLVED; - if (pipelineAndActionResourceStackInSameAccount && actionResourceInSameRegionAsItsStack) { - otherStack = actionResourceStack; - } - } - } else { - actionRegion = action.actionProperties.region; - } - - // if actionRegion is undefined, - // it means the action is in the same region as the pipeline - - // so, just return the artifactBucket - if (!actionRegion) { - return { - artifactBucket: this.artifactBucket, - }; - } - // get the region the Pipeline itself is in - const pipelineRegion = this.requireRegion(); - // if the action is in the same region as the pipeline, nothing to do - if (actionRegion === pipelineRegion) { + private ensureReplicationResourcesExistFor(action: RichAction): CrossRegionInfo { + if (!action.isCrossRegion) { return { artifactBucket: this.artifactBucket, }; } + // The action has a specific region, + // require the pipeline to have a known region as well. + this.requireRegion(); + // source actions have to be in the same region as the pipeline if (action.actionProperties.category === ActionCategory.SOURCE) { throw new Error(`Source action '${action.actionProperties.actionName}' must be in the same region as the pipeline`); @@ -460,23 +458,35 @@ export class Pipeline extends PipelineBase { // check whether we already have a bucket in that region, // either passed from the outside or previously created - let crossRegionSupport = this._crossRegionSupport[actionRegion]; - if (!crossRegionSupport) { - // we need to create scaffolding resources for this region - crossRegionSupport = this.createSupportResourcesForRegion(otherStack, actionRegion); - this._crossRegionSupport[actionRegion] = crossRegionSupport; - } + const crossRegionSupport = this.obtainCrossRegionSupportFor(action); // the stack containing the replication bucket must be deployed before the pipeline - pipelineStack.addDependency(crossRegionSupport.stack); + Stack.of(this).addDependency(crossRegionSupport.stack); + // The Pipeline role must be able to replicate to that bucket crossRegionSupport.replicationBucket.grantReadWrite(this.role); return { artifactBucket: crossRegionSupport.replicationBucket, - region: actionRegion, + region: action.effectiveRegion, }; } + /** + * Get or create the cross-region support construct for the given action + */ + private obtainCrossRegionSupportFor(action: RichAction) { + // this method is never called for non cross-region actions + const actionRegion = action.effectiveRegion!; + let crossRegionSupport = this._crossRegionSupport[actionRegion]; + if (!crossRegionSupport) { + // we need to create scaffolding resources for this region + const otherStack = action.resourceStack; + crossRegionSupport = this.createSupportResourcesForRegion(otherStack, actionRegion); + this._crossRegionSupport[actionRegion] = crossRegionSupport; + } + return crossRegionSupport; + } + private createSupportResourcesForRegion(otherStack: Stack | undefined, actionRegion: string): CrossRegionSupport { // if we have a stack from the resource passed - use that! @@ -485,7 +495,9 @@ export class Pipeline extends PipelineBase { const id = `CrossRegionReplicationSupport-d823f1d8-a990-4e5c-be18-4ac698532e65-${actionRegion}`; let crossRegionSupportConstruct = otherStack.node.tryFindChild(id) as CrossRegionSupportConstruct; if (!crossRegionSupportConstruct) { - crossRegionSupportConstruct = new CrossRegionSupportConstruct(otherStack, id); + crossRegionSupportConstruct = new CrossRegionSupportConstruct(otherStack, id, { + createKmsKey: this.crossAccountKeys, + }); } return { @@ -510,6 +522,7 @@ export class Pipeline extends PipelineBase { region: actionRegion, account: pipelineAccount, synthesizer: this.getCrossRegionSupportSynthesizer(), + createKmsKey: this.crossAccountKeys, }); } @@ -553,7 +566,7 @@ export class Pipeline extends PipelineBase { * @param action the action to return/create a role for * @param actionScope the scope, unique to the action, to create new resources in */ - private getRoleForAction(stage: Stage, action: IAction, actionScope: Construct): iam.IRole | undefined { + private getRoleForAction(stage: Stage, action: RichAction, actionScope: Construct): iam.IRole | undefined { const pipelineStack = Stack.of(this); let actionRole = this.getRoleFromActionPropsOrGenerateIfCrossAccount(stage, action); @@ -576,9 +589,22 @@ export class Pipeline extends PipelineBase { return actionRole; } - private getRoleFromActionPropsOrGenerateIfCrossAccount(stage: Stage, action: IAction): iam.IRole | undefined { + private getRoleFromActionPropsOrGenerateIfCrossAccount(stage: Stage, action: RichAction): iam.IRole | undefined { const pipelineStack = Stack.of(this); + // if we have a cross-account action, the pipeline's bucket must have a KMS key + // (otherwise we can't configure cross-account trust policies) + if (action.isCrossAccount) { + const artifactBucket = this.ensureReplicationResourcesExistFor(action).artifactBucket; + if (!artifactBucket.encryptionKey) { + throw new Error( + `Artifact Bucket must have a KMS Key to add cross-account action '${action.actionProperties.actionName}' ` + + `(pipeline account: '${renderEnvDimension(this.env.account)}', action account: '${renderEnvDimension(action.effectiveAccount)}'). ` + + 'Create Pipeline with \'crossAccountKeys: true\' (or pass an existing Bucket with a key)', + ); + } + } + // if a Role has been passed explicitly, always use it // (even if the backing resource is from a different account - // this is how the user can override our default support logic) @@ -611,14 +637,6 @@ export class Pipeline extends PipelineBase { return undefined; } - // if we have a cross-account action, the pipeline's bucket must have a KMS key - if (!this.artifactBucket.encryptionKey) { - throw new Error('The Pipeline is being used in a cross-account manner, ' + - 'but its artifact bucket does not have a KMS key defined. ' + - 'A KMS key is required for a cross-account Pipeline. ' + - 'Make sure to pass a Bucket with a Key when creating the Pipeline'); - } - // generate a role in the other stack, that the Pipeline will assume for executing this action const ret = new iam.Role(otherAccountStack, `${this.node.uniqueId}-${stage.stageName}-${action.actionProperties.actionName}-ActionRole`, { @@ -956,3 +974,10 @@ class PipelineLocation { return `Stage ${this.stageIndex + 1} Action ${this.action.runOrder} ('${this.stageName}'/'${this.actionName}')`; } } + +/** + * Render an env dimension without showing the ugly stringified tokens + */ +function renderEnvDimension(s: string | undefined) { + return Token.isUnresolved(s) ? '(current)' : s; +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/cross-region-support-stack.ts similarity index 71% rename from packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts rename to packages/@aws-cdk/aws-codepipeline/lib/private/cross-region-support-stack.ts index 00d0c5ca29493..33a81467a23d9 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/cross-region-support-stack.ts @@ -28,22 +28,42 @@ function lastNCharacters(str: string, n: number) { return str.substring(startIndex); } +/** + * Props for the support stack + */ +export interface CrossRegionSupportConstructProps { + /** + * Whether to create the KMS CMK + * + * (Required for cross-account deployments) + * + * @default true + */ + readonly createKmsKey?: boolean; +} + export class CrossRegionSupportConstruct extends cdk.Construct { public readonly replicationBucket: s3.IBucket; - constructor(scope: cdk.Construct, id: string) { + constructor(scope: cdk.Construct, id: string, props: CrossRegionSupportConstructProps = {}) { super(scope, id); - const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey', { - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - const encryptionAlias = new AliasWithShorterGeneratedName(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', { - targetKey: encryptionKey, - aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); + const createKmsKey = props.createKmsKey ?? true; + + let encryptionAlias; + if (createKmsKey) { + const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + encryptionAlias = new AliasWithShorterGeneratedName(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', { + targetKey: encryptionKey, + aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + } this.replicationBucket = new s3.Bucket(this, 'CrossRegionCodePipelineReplicationBucket', { bucketName: cdk.PhysicalName.GENERATE_IF_NEEDED, + encryption: encryptionAlias ? s3.BucketEncryption.KMS : s3.BucketEncryption.KMS_MANAGED, encryptionKey: encryptionAlias, }); } @@ -73,6 +93,15 @@ export interface CrossRegionSupportStackProps { readonly account: string; readonly synthesizer: cdk.IStackSynthesizer | undefined; + + /** + * Whether or not to create a KMS key in the support stack + * + * (Required for cross-account deployments) + * + * @default true + */ + readonly createKmsKey?: boolean; } /** @@ -95,7 +124,9 @@ export class CrossRegionSupportStack extends cdk.Stack { synthesizer: props.synthesizer, }); - const crossRegionSupportConstruct = new CrossRegionSupportConstruct(this, 'Default'); + const crossRegionSupportConstruct = new CrossRegionSupportConstruct(this, 'Default', { + createKmsKey: props.createKmsKey, + }); this.replicationBucket = crossRegionSupportConstruct.replicationBucket; } } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/full-action-descriptor.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts similarity index 97% rename from packages/@aws-cdk/aws-codepipeline/lib/full-action-descriptor.ts rename to packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts index b2759ff353b85..6d58bf815d3db 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/full-action-descriptor.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; -import { ActionArtifactBounds, ActionCategory, ActionConfig, IAction } from './action'; -import { Artifact } from './artifact'; +import { ActionArtifactBounds, ActionCategory, ActionConfig, IAction } from '../action'; +import { Artifact } from '../artifact'; export interface FullActionDescriptorProps { readonly action: IAction; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts new file mode 100644 index 0000000000000..f218344da61fa --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts @@ -0,0 +1,116 @@ +import * as events from '@aws-cdk/aws-events'; +import { Construct, ResourceEnvironment, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { ActionBindOptions, ActionConfig, ActionProperties, IAction, IPipeline, IStage } from '../action'; + +/** + * Helper routines to work with Actions + * + * Can't put these on Action themselves since we only have an interface + * and every library would need to reimplement everything (there is no + * `ActionBase`). + * + * So here go the members that should have gone onto the Action class + * but can't. + * + * It was probably my own idea but I don't want it anymore: + * https://github.com/aws/aws-cdk/issues/10393 + */ +export class RichAction implements IAction { + public readonly actionProperties: ActionProperties; + + constructor(private readonly action: IAction, private readonly pipeline: IPipeline) { + this.actionProperties = action.actionProperties; + } + + public bind(scope: Construct, stage: IStage, options: ActionBindOptions): ActionConfig { + return this.action.bind(scope, stage, options); + } + + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { + return this.action.onStateChange(name, target, options); + } + + public get isCrossRegion(): boolean { + return !actionDimensionSameAsPipelineDimension(this.effectiveRegion, this.pipeline.env.region); + } + + public get isCrossAccount(): boolean { + return !actionDimensionSameAsPipelineDimension(this.effectiveAccount, this.pipeline.env.account); + } + + /** + * Returns the Stack of the resource backing this action + * if they belong to the same environment. + * Returns `undefined` if either this action is not backed by a resource, + * or if the resource does not belong to the same env as its Stack + * (which can happen for imported resources). + */ + public get resourceStack(): Stack | undefined { + const actionResource = this.actionProperties.resource; + if (!actionResource) { + return undefined; + } + + const actionResourceStack = Stack.of(actionResource); + const actionResourceStackEnv: ResourceEnvironment = { + region: actionResourceStack.region, + account: actionResourceStack.account, + }; + + return sameEnv(actionResource.env, actionResourceStackEnv) ? actionResourceStack : undefined; + } + + /** + * The region this action wants to execute in. + * `undefined` means it wants to execute in the same region as the pipeline. + */ + public get effectiveRegion(): string | undefined { + return this.action.actionProperties.resource?.env.region + ?? this.action.actionProperties.region; + } + + /** + * The account this action wants to execute in. + * `undefined` means it wants to execute in the same account as the pipeline. + */ + public get effectiveAccount(): string | undefined { + return this.action.actionProperties.role?.env.account + ?? this.action.actionProperties?.resource?.env.account + ?? this.action.actionProperties.account; + } +} + +function actionDimensionSameAsPipelineDimension(actionDim: string | undefined, pipelineDim: string) { + // if the action's dimension is `undefined`, + // it means it is in the same region/account as the pipeline + if (!actionDim) { + return true; + } + // if the action's region/account is AWS::Region/AWS::AccountId, + // we assume it's also in the same region/account as the pipeline + if (Token.isUnresolved(actionDim)) { + return true; + } + // here, we know the action's dimension is explicitly set; + // in this case, it must be equal to the pipeline's dimension + // for the action to be considered in the same region/account + return Token.compareStrings(actionDim, pipelineDim) === TokenComparison.SAME; +} + +/** + * Whether the two envs represent the same environment + */ +function sameEnv(env1: ResourceEnvironment, env2: ResourceEnvironment) { + return sameEnvDimension(env1.region, env2.region) + && sameEnvDimension(env1.account, env2.account); +} + +/** + * Whether two string probably contain the same environment dimension (region or account) + * + * Used to compare either accounts or regions, and also returns true if both + * are unresolved (in which case both are expted to be "current region" or "current account"). + */ +function sameEnvDimension(dim1: string, dim2: string) { + return [TokenComparison.SAME, TokenComparison.BOTH_UNRESOLVED].includes(Token.compareStrings(dim1, dim2)); +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/stage.ts similarity index 96% rename from packages/@aws-cdk/aws-codepipeline/lib/stage.ts rename to packages/@aws-cdk/aws-codepipeline/lib/private/stage.ts index 2b1975e147c1a..a0f16972e1575 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/stage.ts @@ -1,10 +1,10 @@ import * as events from '@aws-cdk/aws-events'; import * as cdk from '@aws-cdk/core'; -import { IAction, IPipeline, IStage } from './action'; -import { Artifact } from './artifact'; -import { CfnPipeline } from './codepipeline.generated'; +import { IAction, IPipeline, IStage } from '../action'; +import { Artifact } from '../artifact'; +import { CfnPipeline } from '../codepipeline.generated'; +import { Pipeline, StageProps } from '../pipeline'; import { FullActionDescriptor } from './full-action-descriptor'; -import { Pipeline, StageProps } from './pipeline'; import * as validation from './validation'; /** diff --git a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/validation.ts similarity index 96% rename from packages/@aws-cdk/aws-codepipeline/lib/validation.ts rename to packages/@aws-cdk/aws-codepipeline/lib/private/validation.ts index 6bff1eda49e0d..d44ea7b745fef 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/validation.ts @@ -1,6 +1,6 @@ import * as cdk from '@aws-cdk/core'; -import { ActionCategory } from './action'; -import { Artifact } from './artifact'; +import { ActionCategory } from '../action'; +import { Artifact } from '../artifact'; /** * Validation function that checks if the number of artifacts is within the given bounds diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 69abb3b50581d..5e9054460f74d 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -154,7 +154,6 @@ "props-default-doc:@aws-cdk/aws-codepipeline.ActionProperties.runOrder", "docs-public-apis:@aws-cdk/aws-codepipeline.ActionProperties.version", "props-default-doc:@aws-cdk/aws-codepipeline.ActionProperties.version", - "docs-public-apis:@aws-cdk/aws-codepipeline.IAction", "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.actionProperties", "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.bind", "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.onStateChange", diff --git a/packages/@aws-cdk/aws-codepipeline/test/action.test.ts b/packages/@aws-cdk/aws-codepipeline/test/action.test.ts index 9c573a188a4f3..381eed2a1b7c5 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/action.test.ts @@ -3,7 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as codepipeline from '../lib'; -import * as validations from '../lib/validation'; +import * as validations from '../lib/private/validation'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; diff --git a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts new file mode 100644 index 0000000000000..917f4c833858f --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts @@ -0,0 +1,135 @@ +import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import { Stack, App } from '@aws-cdk/core'; +import * as codepipeline from '../lib'; +import { FakeBuildAction } from './fake-build-action'; +import { FakeSourceAction } from './fake-source-action'; + +let app: App; +let stack: Stack; +let sourceArtifact: codepipeline.Artifact; +let initialStages: codepipeline.StageProps[]; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'PipelineStack', { env: { account: '2222', region: 'us-east-1' } }); + sourceArtifact = new codepipeline.Artifact(); + initialStages = [ + { + stageName: 'Source', + actions: [new FakeSourceAction({ + actionName: 'Source', + output: sourceArtifact, + })], + }, + { + stageName: 'Build', + actions: [new FakeBuildAction({ + actionName: 'Build', + input: sourceArtifact, + })], + }, + ]; +}); + +describe('crossAccountKeys=false', () => { + let pipeline: codepipeline.Pipeline; + beforeEach(() => { + pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + crossAccountKeys: false, + stages: initialStages, + }); + }); + + test('creates a bucket but no keys', () => { + // THEN + expect(stack).not.toHaveResource('AWS::KMS::Key'); + expect(stack).toHaveResource('AWS::S3::Bucket'); + }); + + describe('prevents adding a cross-account action', () => { + const expectedError = 'crossAccountKeys: true'; + + let stage: codepipeline.IStage; + beforeEach(() => { + stage = pipeline.addStage({ stageName: 'Deploy' }); + }); + + test('by role', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + role: iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::1111:role/some-role'), + })); + }).toThrow(expectedError); + }); + + test('by resource', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + resource: s3.Bucket.fromBucketAttributes(stack, 'Bucket', { + bucketName: 'foo', + account: '1111', + }), + })); + }).toThrow(expectedError); + }); + + test('by declared account', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + account: '1111', + })); + }).toThrow(expectedError); + }); + }); + + describe('also affects cross-region support stacks', () => { + let stage: codepipeline.IStage; + beforeEach(() => { + stage = pipeline.addStage({ stageName: 'Deploy' }); + }); + + test('when making a support stack', () => { + // WHEN + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + // No resource to grab onto forces creating a fresh support stack + region: 'eu-west-1', + })); + + // THEN + const asm = app.synth(); + const supportStack = asm.getStack(`${stack.stackName}-support-eu-west-1`); + + // THEN + expect(supportStack).not.toHaveResource('AWS::KMS::Key'); + expect(supportStack).toHaveResource('AWS::S3::Bucket'); + }); + + test('when twiddling another stack', () => { + const stack2 = new Stack(app, 'Stack2', { env: { account: '2222', region: 'eu-west-1' } }); + + // WHEN + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + resource: new iam.User(stack2, 'DoesntMatterWhatThisIs'), + })); + + // THEN + expect(stack2).not.toHaveResource('AWS::KMS::Key'); + expect(stack2).toHaveResource('AWS::S3::Bucket'); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts index 63a3ef4d551c5..741fd2d188dec 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts @@ -1,6 +1,6 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; +import { Construct, IResource } from '@aws-cdk/core'; import * as codepipeline from '../lib'; export interface FakeBuildActionProps extends codepipeline.CommonActionProps { @@ -19,6 +19,8 @@ export interface FakeBuildActionProps extends codepipeline.CommonActionProps { region?: string; customConfigKey?: string; + + resource?: IResource; } export class FakeBuildAction implements codepipeline.IAction { diff --git a/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts b/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts index c51ed25c97755..76cd9b71a6941 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts @@ -3,7 +3,7 @@ import { nodeunitShim, Test } from 'nodeunit-shim'; import { IStage } from '../lib/action'; import { Artifact } from '../lib/artifact'; import { Pipeline } from '../lib/pipeline'; -import { validateName } from '../lib/validation'; +import { validateName } from '../lib/private/validation'; import { FakeSourceAction } from './fake-source-action'; interface NameValidationTestCase { diff --git a/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts b/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts index 9adf193b66fb7..58b6ff1b45510 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts @@ -2,7 +2,7 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as codepipeline from '../lib'; -import { Stage } from '../lib/stage'; +import { Stage } from '../lib/private/stage'; /* eslint-disable quote-props */ diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 350c99386d429..a2c72b4c03bb9 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -102,6 +102,26 @@ following to `cdk.json`: } ``` +## A note on cost + +By default, the `CdkPipeline` construct creates an AWS Key Management Service +(AWS KMS) Customer Master Key (CMK) for you to encrypt the artifacts in the +artifact bucket, which incurs a cost of +**$1/month**. This default configuration is necessary to allow cross-account +deployments. + +If you do not intend to perform cross-account deployments, you can disable +the creation of the Customer Master Keys by passing `crossAccountKeys: false` +when defining the Pipeline: + +```ts +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { + crossAccountKeys: false, + + // ... +}); +``` + ## Defining the Pipeline (Source and Synth) The pipeline is defined by instantiating `CdkPipeline` in a Stack. This defines the diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index 9643b413eef08..7e96e4c39bab3 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -55,6 +55,26 @@ export interface CdkPipelineProps { */ readonly pipelineName?: string; + /** + * Create KMS keys for cross-account deployments + * + * This controls whether the pipeline is enabled for cross-account deployments. + * + * Can only be set if `codePipeline` is not set. + * + * By default cross-account deployments are enabled, but this feature requires + * that KMS Customer Master Keys are created which have a cost of $1/month. + * + * If you do not need cross-account deployments, you can set this to `false` to + * not create those keys and save on that cost (the artifact bucket will be + * encrypted with an AWS-managed key). However, cross-account deployments will + * no longer be possible. + * + * @default true + */ + readonly crossAccountKeys?: boolean; + // @deprecated(v2): switch to default false + /** * CDK CLI version to use in pipeline * @@ -115,11 +135,15 @@ export class CdkPipeline extends Construct { if (props.pipelineName) { throw new Error('Cannot set \'pipelineName\' if an existing CodePipeline is given using \'codePipeline\''); } + if (props.crossAccountKeys !== undefined) { + throw new Error('Cannot set \'crossAccountKeys\' if an existing CodePipeline is given using \'codePipeline\''); + } this._pipeline = props.codePipeline; } else { this._pipeline = new codepipeline.Pipeline(this, 'Pipeline', { pipelineName: props.pipelineName, + crossAccountKeys: props.crossAccountKeys, restartExecutionOnUpdate: true, }); } From d95af009a8d59393d019620aa8878098ce0aed1d Mon Sep 17 00:00:00 2001 From: Nathan Peck Date: Tue, 29 Sep 2020 13:16:32 -0400 Subject: [PATCH 31/46] feat(aws-ecs-builder): RFC 219 - An extendable service class for AWS ECS (#10129) This PR implements [RFC 219](https://github.com/nathanpeck/aws-cdk-rfcs/blob/master/text/0219-ecs-service-extensions.md) It adds a new module called "@aws-containers/aws-ecs-builder" (Name proposed but not final) with following new constructs: - `Environment` - A deploy environment for a service which by default supplies its own VPC, and ECS cluster with Fargate capacity - `Service` - An ECS service And supporting classes: - `ServiceDescription` - Defines the application to run as a service, and any features it needs - `ServiceExtension` - Defines an optional extension that may be added to a service to enhance it with new capabilities or connect it to other ECS adjacent features This PR comes with a `ServiceExtension` for each of the following ECS adjacent features: - AppMesh - X-Ray - CloudWatch Agent - Application Load Balancer - FireLens Developers can use this new extendable `Service` class to build a `ServiceDescription` that defines their application and as many optional service extensions as they want. The `ServiceDescription` is used to build out a `Service` inside an `Environment` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .gitallowed | 6 + CONTRIBUTING.md | 2 +- lerna.json | 3 +- package.json | 3 +- .../ecs-service-extensions/.eslintrc.js | 3 + .../ecs-service-extensions/.gitignore | 17 + .../ecs-service-extensions/.npmignore | 24 + .../ecs-service-extensions/LICENSE | 201 + .../ecs-service-extensions/NOTICE | 2 + .../ecs-service-extensions/README.md | 284 ++ .../ecs-service-extensions/lib/environment.ts | 84 + .../lib/extensions/appmesh.ts | 334 ++ .../lib/extensions/cloudwatch-agent.ts | 73 + .../lib/extensions/container.ts | 145 + .../lib/extensions/extension-interfaces.ts | 224 ++ .../lib/extensions/firelens.ts | 126 + .../lib/extensions/http-load-balancer.ts | 60 + .../lib/extensions/index.ts | 7 + .../extensions/scale-on-cpu-utilization.ts | 134 + .../lib/extensions/xray.ts | 66 + .../ecs-service-extensions/lib/index.ts | 5 + .../lib/service-description.ts | 40 + .../ecs-service-extensions/lib/service.ts | 248 ++ .../ecs-service-extensions/package.json | 98 + .../integ.all-service-addons.expected.json | 3515 +++++++++++++++++ .../test/integ.all-service-addons.ts | 100 + ...teg.custom-service-extension.expected.json | 704 ++++ .../test/integ.custom-service-extension.ts | 65 + .../integ.multiple-environments.expected.json | 2252 +++++++++++ .../test/integ.multiple-environments.ts | 54 + .../test/test.appmesh.ts | 591 +++ .../test/test.cloudwatch-agent.ts | 111 + .../test/test.environment.ts | 220 ++ .../test/test.firelens.ts | 122 + .../test/test.http-load-balancer.ts | 76 + .../test/test.scale-on-cpu-utilization.ts | 153 + .../test/test.service.ts | 101 + .../ecs-service-extensions/test/test.xray.ts | 122 + .../ecs-service-extensions/tsconfig.json | 108 + .../region-info/build-tools/fact-tables.ts | 25 + .../build-tools/generate-static-data.ts | 4 +- packages/@aws-cdk/region-info/lib/fact.ts | 6 + .../@aws-cdk/region-info/lib/region-info.ts | 8 + yarn.lock | 679 +++- 44 files changed, 11189 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/.eslintrc.js create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/.gitignore create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/.npmignore create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/LICENSE create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/NOTICE create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/README.md create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/cloudwatch-agent.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/container.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/firelens.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/scale-on-cpu-utilization.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/xray.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/index.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/service-description.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/package.json create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.expected.json create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.appmesh.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.cloudwatch-agent.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.firelens.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.http-load-balancer.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.scale-on-cpu-utilization.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.service.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/test/test.xray.ts create mode 100644 packages/@aws-cdk-containers/ecs-service-extensions/tsconfig.json diff --git a/.gitallowed b/.gitallowed index ed8c612466b3d..7542be9216573 100644 --- a/.gitallowed +++ b/.gitallowed @@ -15,3 +15,9 @@ account: '012345678913' # Account patterns used in the CHANGELOG account: '123456789012' 123456789012 + +# The account ID's of public facing ECR images for App Mesh Envoy +# https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html +account: '772975370895' +account: '856666278305' +account: '840364872350' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef73dba94c39d..3333987762d80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -181,7 +181,7 @@ Integration tests perform a few functions in the CDK code base - 1. Acts as a regression detector. It does this by running `cdk synth` on the integration test and comparing it against the `*.expected.json` file. This highlights how a change affects the synthesized stacks. 2. Allows for a way to verify if the stacks are still valid CloudFormation templates, as part of an intrusive change. - This is done by running `yarn integ` which will run `cdk deploy` across all of the integration tests in that package. + This is done by running `yarn integ` which will run `cdk deploy` across all of the integration tests in that package. If you are developing a new integration test or for some other reason want to work on a single integration test over and over again without running through all the integration tests you can do so using `yarn integ integ.test-name.js` Remember to set up AWS credentials before doing this. 3. (Optionally) Acts as a way to validate that constructs set up the CloudFormation resources as expected. A successful CloudFormation deployment does not mean that the resources are set up correctly. diff --git a/lerna.json b/lerna.json index dab86c14f2229..8fe4a483846ac 100644 --- a/lerna.json +++ b/lerna.json @@ -5,10 +5,11 @@ "packages": [ "packages/*", "packages/@aws-cdk/*", + "packages/@aws-cdk-containers/*", "packages/@monocdk-experiment/*", "packages/@aws-cdk/*/lambda-packages/*", "tools/*" ], "rejectCycles": "true", "version": "1.64.1" -} +} \ No newline at end of file diff --git a/package.json b/package.json index 330289aef2e5d..bd0b765867870 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "packages": [ "packages/*", "packages/@aws-cdk/*", + "packages/@aws-cdk-containers/*", "packages/@monocdk-experiment/*", "packages/@aws-cdk/*/lambda-packages/*", "tools/*" @@ -95,4 +96,4 @@ "monocdk-experiment/yaml/**" ] } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/.eslintrc.js b/packages/@aws-cdk-containers/ecs-service-extensions/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore b/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore new file mode 100644 index 0000000000000..937533073b465 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore @@ -0,0 +1,17 @@ +*.js +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk +!.eslintrc.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore b/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore new file mode 100644 index 0000000000000..913c96cd9ae15 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore @@ -0,0 +1,24 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js + +**/cdk.out +junit.xml diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/LICENSE b/packages/@aws-cdk-containers/ecs-service-extensions/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/NOTICE b/packages/@aws-cdk-containers/ecs-service-extensions/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md new file mode 100644 index 0000000000000..7df503cf6a139 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -0,0 +1,284 @@ +# CDK Construct library for building ECS services + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +This library provides a high level, extensible pattern for constructing services +deployed using Amazon ECS. + +The `Service` construct provided by this module can be extended with optional `ServiceExtension` classes that add supplemental ECS features including: + +- [AWS X-Ray](https://aws.amazon.com/xray/) for tracing your application +- [Amazon CloudWatch Agent](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html) for capturing per task stats +- [AWS AppMesh f](https://aws.amazon.com/app-mesh/)or adding your application to a service mesh +- [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), for exposing your service to the public +- [AWS FireLens](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html), for filtering and routing application logs + +The `ServiceExtension` class is an abstract class which you can also implement in +order to build your own custom service extensions for modifying your service, or +attaching your own custom resources or sidecars. + +## Example + +```ts +import { AppMeshExtension, CloudwatchAgentExtension, Container, Environment, FireLensExtension, HttpLoadBalancerExtension, Service, ServiceDescription, XRayExtension } from 'ecs-service-builder'; + +// Create an environment to deploy a service in. +const environment = new Environment(stack, 'production'); + +// Build out the service description +const nameDescription = new ServiceDescription(); +nameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +nameDescription.add(new AppMeshExtension({ mesh })); +nameDescription.add(new FireLensExtension()); +nameDescription.add(new XRayExtension()); +nameDescription.add(new CloudwatchAgentExtension()); +nameDescription.add(new HttpLoadBalancerExtension()); + +// Implement the service description as a real service inside +// an environment. +const nameService = new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, +}); +``` + +## Creating an `Environment` + +An `Environment` is a place to deploy your services. You can have multiple environments +on a single AWS account. For example you could create a `test` environment as well +as a `production` environment so you have a place to verify that you application +works as intended before you deploy it to a live environment. + +Each environment is isolated from other environments. In specific +by default when you create an environment the construct supplies its own VPC, +ECS Cluster, and any other required resources for the environment: + +```ts +const environment = new Environment(stack, 'production'); +``` + +However, you can also choose to build an environment out of a pre-existing VPC, +or ECS Cluster: + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + +const environment = new Environment(stack, 'production', { + vpc, + cluster, +}); +``` + +## Defining your `ServiceDescription` + +The `ServiceDescription` defines what application you want the service to run and +what optional extensions you want to add to the service. The most basic form of a `ServiceExtension` looks like this: + +```ts +const nameDescription = new ServiceDescription(); +nameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +``` + +Every `ServiceDescription` requires at minimum that you add a `Container` extension +which defines the main application container to run for the service. + +After that you can optionally enable additional features for the service using the `ServiceDescription.add()` method: + +```ts +nameDescription.add(new AppMeshExtension({ mesh })); +nameDescription.add(new FireLensExtension()); +nameDescription.add(new XRayExtension()); +nameDescription.add(new CloudwatchAgentExtension()); +nameDescription.add(new HttpLoadBalancerExtension()); +``` + +## Launching the `ServiceDescription` as a `Service` + +Once the service description is defined, you can launch it as a service: + +```ts +const nameService = new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, +}); +``` + +At this point, all the service resources will be created. This includes the ECS Task +Definition, Service, as well as any other attached resources, such as App Mesh Virtual +Node or an Application Load Balancer. + +## Creating your own custom `ServiceExtension` + +In addition to using the default service extensions that come with this module, you +can choose to implement your own custom service extensions. The `ServiceExtension` +class is an abstract class you can implement yourself. The following example +implements a custom service extension that could be added to a service in order to +autoscale it based on CPU: + +```ts +export class MyCustomAutoscaling extends ServiceExtension { + constructor() { + super('my-custom-autoscaling'); + } + + // This function modifies properties of the service prior + // to construct creation. + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + + // Initially launch 10 copies of the service + desiredCount: 10 + } as ServiceBuild; + } + + // This hook utilizes the resulting service construct + // once it is created + public useService(service: ecs.Ec2Service | ecs.FargateService) { + const scalingTarget = service.autoScaleTaskCount({ + minCapacity: 5, // Min 5 tasks + maxCapacity: 20 // Max 20 tasks + }); + + scalingTarget.scaleOnCpuUtilization('TargetCpuUtilization50', { + targetUtilizationPercent: 50, + scaleInCooldown: cdk.Duration.seconds(60), + scaleOutCooldown: cdk.Duration.seconds(60), + }); + } +} +``` + +This `ServiceExtension` can now be reused and added to any number of different +service descriptions. This allows you to develop reusable bits of configuration, +attach them to many different services, and centrally manage them. Updating the +`ServiceExtension` in one place would update all services that use it, instead of +requiring decentralized updates to many different services. + +Every `ServiceExtension` can implement the following hooks to modify the properties +of constructs, or make use of the resulting constructs: + +* `addHooks()` - This hook is called after all the extensions are added to a + ServiceDescription, but before any of the other extension hooks have been run. + It gives each extension a chance to do some inspection of the overall ServiceDescription + and see what other extensions have been added. Some extensions may want to register + hooks on the other extensions to modify them. For example, the Firelens extension + wants to be able to modify the settings of the application container to route logs + through Firelens. +* `modifyTaskDefinitionProps()` - This is hook is passed the proposed + ecs.TaskDefinitionProps for a TaskDefinition that is about to be created. + This allows the extension to make modifications to the task definition props + before the TaskDefinition is created. For example, the App Mesh extension modifies + the proxy settings for the task. +* `useTaskDefinition()` - After the TaskDefinition is created, this hook is + passed the actual TaskDefinition construct that was created. This allows the + extension to add containers to the task, modify the task definition's IAM role, + etc. +* `resolveContainerDependencies()` - Once all extensions have added their containers, + each extension is given a chance to modify its container's `dependsOn` settings. + Extensions need to check and see what other extensions were enabled and decide + whether their container needs to wait on another container to start first. +* `modifyServiceProps()` - Before an Ec2Service or FargateService is created, this + hook is passed a draft version of the service props to change. Each extension adds + its own modifications to the service properties. For example, the App Mesh extension + needs to modify the service settings to enable CloudMap service discovery. +* `useService()` - After the service is created, this hook is given a chance to + utilize that service. This is used by extensions like the load balancer or App Mesh + extension, which create and link other AWS resources to the ECS extension. +* `connectToService()` - This hook is called when a user wants to connect one service + to another service. It allows an extension to implement logic about how to allow + connections from one service to another. For example, the App Mesh extension implements + this method in order to easily connect one service mesh service to another, which + allows the service's Envoy proxy sidecars to route traffic to each other. + +## Connecting services + +One of the hooks that a `ServiceExtension` can implement is a hook for connection +logic. This is utilized when connecting one service to another service, e.g. +connecting a user facing web service with a backend API. Usage looks like this: + +```ts +const frontend = new Service(stack, 'frontend', { + environment, + serviceDescription: frontendDescription +}); +const backend = new Service(stack, 'backend', { + environment, + serviceDescription: backendDescription +}); + +frontend.connectTo(backend); +``` + +The address that a service will use to talk to another service depends on the +type of ingress that has been created by the extension that did the connecting. +For example if an App Mesh extension has been used then the service is accessible +at a DNS address of `.`. For example: + +```ts +const environment = new Environment(stack, 'production'); + +// Define the frontend tier +const frontendDescription = new ServiceDescription(); +frontendDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('my-frontend-service'), + environment: { + BACKEND_URL: 'http://backend.production' + }, +})); +const frontend = new Service(stack, 'frontend', { + environment, + serviceDescription: frontendDescription +}); + +// Define the backend tier +const backendDescription = new ServiceDescription(); +backendDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('my-backend-service'), + environment: { + FRONTEND_URL: 'http://frontend.production' + }, +})); +const backend = new Service(stack, 'backend', { + environment, + serviceDescription: backendDescription +}); + +// Connect the two tiers to each other +frontend.connectTo(backend); +``` + +The above code uses the well known service discovery name for each +service, and passes it as an environment variable to the container so +that the container knows what address to use when communicating to +the other service. diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts new file mode 100644 index 0000000000000..2a5e215d7571e --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -0,0 +1,84 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { EnvironmentCapacityType } from './extensions/extension-interfaces'; + +/** + * Settings for the environment you want to deploy. + * services within. + */ +export interface EnvironmentProps { + /** + * The VPC used by the service for networking + * @default - Create a new VPC + */ + readonly vpc?: ec2.IVpc, + + /** + * The ECS cluster which provides compute capacity to this service. + * [disable-awslint:ref-via-interface] + * @default - Create a new cluster + */ + readonly cluster?: ecs.Cluster + + /** + * The type of capacity to use for this environment. + * @default - EnvironmentCapacityType.FARGATE + */ + readonly capacityType?: EnvironmentCapacityType +} + +/** + * An environment into which to deploy a service. This environment + * can either be instantiated with a preexisting AWS VPC and ECS cluster, + * or it can create it's own VPC and cluster. By default it will create + * a cluster with Fargate capacity. + */ +export class Environment extends cdk.Construct { + /** + * The name of this environment. + */ + public readonly id: string; + + /** + * The VPC into which environment services should be placed. + */ + public readonly vpc: ec2.IVpc; + + /** + * The cluster that is providing capacity for this service. + */ + public readonly cluster: ecs.Cluster; + + /** + * The capacity type used by the service's cluster. + */ + public readonly capacityType: EnvironmentCapacityType; + + private readonly scope: cdk.Construct; + + constructor(scope: cdk.Construct, id: string, props?: EnvironmentProps) { + super(scope, id); + + this.scope = scope; + this.id = id; + + if (props && props.vpc) { + this.vpc = props.vpc; + } else { + this.vpc = new ec2.Vpc(this.scope, `${this.id}-environment-vpc`); + } + + if (props && props.cluster) { + this.cluster = props.cluster; + } else { + this.cluster = new ecs.Cluster(this.scope, `${this.id}-environment-cluster`, { vpc: this.vpc }); + } + + if (props && props.capacityType) { + this.capacityType = props.capacityType; + } else { + this.capacityType = EnvironmentCapacityType.FARGATE; + } + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts new file mode 100644 index 0000000000000..dcf3f7ac73e56 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -0,0 +1,334 @@ +import * as appmesh from '@aws-cdk/aws-appmesh'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecr from '@aws-cdk/aws-ecr'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import * as regionInfo from '@aws-cdk/region-info'; +import { Service } from '../service'; +import { Container } from './container'; +import { ServiceExtension, ServiceBuild } from './extension-interfaces'; + +// The version of the App Mesh envoy sidecar to add to the task. +const APP_MESH_ENVOY_SIDECAR_VERSION = 'v1.15.0.0-prod'; + +/** + * The settings for the App Mesh extension. + */ +export interface MeshProps { + /** + * The service mesh into which to register the service + */ + readonly mesh: appmesh.Mesh; + + /** + * The protocol of the service. + * Valid values are Protocol.HTTP, Protocol.HTTP2, Protocol.TCP, Protocol.GRPC + * @default - Protocol.HTTP + */ + readonly protocol?: appmesh.Protocol; +} + +/** + * This extension adds an Envoy sidecar to the task definition and + * creates the App Mesh resources required to route network traffic + * to the container in a service mesh. + * + * The service will then be available to other App Mesh services at the + * address `.`. For example a service called + * `orders` deploying in an environment called `production` would be accessible + * to other App Mesh enabled services at the address `http://orders.production` + */ +export class AppMeshExtension extends ServiceExtension { + protected virtualNode!: appmesh.VirtualNode; + protected virtualService!: appmesh.VirtualService; + protected virtualRouter!: appmesh.VirtualRouter; + protected route!: appmesh.Route; + private mesh: appmesh.Mesh; + + /** + * The protocol used for AppMesh routing. + * default - Protocol.HTTP + */ + public readonly protocol: appmesh.Protocol; + + constructor(props: MeshProps) { + super('appmesh'); + this.mesh = props.mesh; + + if (props.protocol) { + this.protocol = props.protocol; + } else { + this.protocol = appmesh.Protocol.HTTP; + } + } + + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + this.scope = scope; + + // Make sure that the parent cluster for this service has + // a namespace attached. + if (!this.parentService.cluster.defaultCloudMapNamespace) { + this.parentService.cluster.addDefaultCloudMapNamespace({ + // Name the namespace after the environment name. + // Service DNS will be like . + name: this.parentService.environment.id, + }); + } + } + + public modifyTaskDefinitionProps(props: ecs.TaskDefinitionProps) { + // Find the app extension, to get its port + const containerextension = this.parentService.serviceDescription.get('service-container') as Container; + + if (!containerextension) { + throw new Error('Appmesh extension requires an application extension'); + } + + return { + ...props, + + // App Mesh requires AWS VPC networking mode so that each + // task can have its own IP address + networkMode: ecs.NetworkMode.AWS_VPC, + + // This configures the envoy container as a proxy for all + // traffic going into and out of the task, with a few exceptions + // for metadata endpoints or other ports that need direct + // communication + proxyConfiguration: new ecs.AppMeshProxyConfiguration({ + containerName: 'envoy', + properties: { + appPorts: [containerextension.trafficPort], + proxyEgressPort: 15001, + proxyIngressPort: 15000, + + // The App Mesh proxy runs with this user ID, and this keeps its + // own outbound connections from recursively attempting to infinitely proxy. + ignoredUID: 1337, + + // This GID is ignored and any outbound traffic originating from containers that + // use this group ID will be ignored by the proxy. This is primarily utilized by + // the FireLens extension, so that outbound application logs don't have to go through Envoy + // and therefore add extra burden to the proxy sidecar. Instead the logs can go directly + // to CloudWatch + ignoredGID: 1338, + + egressIgnoredIPs: [ + '169.254.170.2', // Allow services to talk directly to ECS metadata endpoints + '169.254.169.254', // and EC2 instance endpoint + ], + + // If there is outbound traffic to specific ports that you want to + // ignore the proxy those ports can be added here. + egressIgnoredPorts: [], + }, + }), + } as ecs.TaskDefinitionProps; + } + + private accountIdForRegion(region: string) { + return { ecrRepo: regionInfo.RegionInfo.get(region).appMeshRepositoryAccount }; + } + + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + var region = cdk.Stack.of(this.scope).region; + var appMeshRepo; + + // This is currently necessary because App Mesh has different images in each region, + // and some regions have their images in a different account. See: + // https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html + const mapping = new cdk.CfnMapping(this.scope, `${this.parentService.id}-envoy-image-account-mapping`, { + mapping: { + 'ap-northeast-1': this.accountIdForRegion('ap-northeast-1'), + 'ap-northeast-2': this.accountIdForRegion('ap-northeast-2'), + 'ap-south-1': this.accountIdForRegion('ap-south-1'), + 'ap-southeast-1': this.accountIdForRegion('ap-southeast-1'), + 'ap-southeast-2': this.accountIdForRegion('ap-southeast-1'), + 'ca-central-1': this.accountIdForRegion('ca-central-1'), + 'eu-central-1': this.accountIdForRegion('eu-central-1'), + 'eu-north-1': this.accountIdForRegion('eu-north-1'), + 'eu-south-1': this.accountIdForRegion('eu-south-1'), + 'eu-west-1': this.accountIdForRegion('eu-west-1'), + 'eu-west-2': this.accountIdForRegion('eu-west-2'), + 'eu-west-3': this.accountIdForRegion('eu-west-3'), + 'sa-east-1': this.accountIdForRegion('sa-east-1'), + 'us-east-1': this.accountIdForRegion('us-east-1'), + 'us-east-2': this.accountIdForRegion('us-east-2'), + 'us-west-1': this.accountIdForRegion('us-west-1'), + 'us-west-2': this.accountIdForRegion('us-west-2'), + + 'me-south-1': this.accountIdForRegion('me-south-1'), + 'ap-east-1': this.accountIdForRegion('ap-east-1'), + }, + }); + + // WHEN + const ownerAccount = mapping.findInMap(region, 'ecrRepo'); + + appMeshRepo = ecr.Repository.fromRepositoryAttributes( + this.scope, + `${this.parentService.id}-envoy-repo`, + { + repositoryName: 'aws-appmesh-envoy', + repositoryArn: `arn:aws:ecr:${region}:${ownerAccount}:repository/aws-appmesh-envoy`, + }, + ); + + this.container = taskDefinition.addContainer('envoy', { + image: ecs.ContainerImage.fromEcrRepository(appMeshRepo, APP_MESH_ENVOY_SIDECAR_VERSION), + essential: true, + environment: { + APPMESH_VIRTUAL_NODE_NAME: `mesh/${this.mesh.meshName}/virtualNode/${this.parentService.id}`, + AWS_REGION: cdk.Stack.of(this.parentService).region, + ENABLE_ENVOY_STATS_TAGS: '1', + ENABLE_ENVOY_DOG_STATSD: '1', + }, + healthCheck: { + command: [ + 'CMD-SHELL', + 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE', + ], + startPeriod: cdk.Duration.seconds(10), + interval: cdk.Duration.seconds(5), + timeout: cdk.Duration.seconds(2), + }, + memoryReservationMiB: 128, + user: '1337', + logging: new ecs.AwsLogDriver({ streamPrefix: 'envoy' }), + }); + + // Modify the task definition role to allow the Envoy sidecar to get + // configuration from the Envoy control plane, for this particular + // mesh only. + new iam.Policy(this.scope, `${this.parentService.id}-envoy-to-appmesh`, { + roles: [taskDefinition.taskRole], + statements: [ + new iam.PolicyStatement({ + resources: [this.mesh.meshArn], + actions: ['appmesh:StreamAggregatedResources'], + }), + ], + }); + + // Raise the number of open file descriptors allowed. This is + // necessary when the Envoy proxy is handling large amounts of + // traffic. + this.container.addUlimits({ + softLimit: 1024000, + hardLimit: 1024000, + name: ecs.UlimitName.NOFILE, + }); + } + + // Enable CloudMap for the service. + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + + // Ensure that service tasks are registered into + // CloudMap so that the App Mesh proxy can find them. + cloudMapOptions: { + dnsRecordType: 'A', + dnsTtl: cdk.Duration.seconds(10), + failureThreshold: 2, + name: this.parentService.id, + }, + + // These specific deployment settings are currently required in order to + // maintain availability during a rolling deploy of the service with App Mesh + // https://docs.aws.amazon.com/app-mesh/latest/userguide/best-practices.html#reduce-deployment-velocity + minHealthyPercent: 100, + maxHealthyPercent: 125, // Note that at low task count the Service will boost this setting higher + } as ServiceBuild; + } + + // Now that the service is defined we can create the AppMesh virtual service + // and virtual node for the real service + public useService(service: ecs.Ec2Service | ecs.FargateService) { + const containerextension = this.parentService.serviceDescription.get('service-container') as Container; + + if (!containerextension) { + throw new Error('Firelens extension requires an application extension'); + } + + const cloudmapNamespace = this.parentService.cluster.defaultCloudMapNamespace; + + if (!cloudmapNamespace) { + throw new Error('You must add a CloudMap namespace to the ECS cluster in order to use the AppMesh extension'); + } + + // Create a virtual node for the name service + this.virtualNode = new appmesh.VirtualNode(this.scope, `${this.parentService.id}-virtual-node`, { + mesh: this.mesh, + virtualNodeName: this.parentService.id, + cloudMapService: service.cloudMapService, + listener: { + portMapping: { + port: containerextension.trafficPort, + protocol: this.protocol, + }, + }, + }); + + // Create a virtual router for this service. This allows for retries + // and other similar behaviors. + this.virtualRouter = new appmesh.VirtualRouter(this.scope, `${this.parentService.id}-virtual-router`, { + mesh: this.mesh, + listener: { + portMapping: { + port: containerextension.trafficPort, + protocol: this.protocol, + }, + }, + virtualRouterName: `${this.parentService.id}`, + }); + + // Now add the virtual node as a route in the virtual router + this.route = this.virtualRouter.addRoute(`${this.parentService.id}-route`, { + routeTargets: [{ + virtualNode: this.virtualNode, + weight: 1, + }], + // Ensure that the route type matches the protocol type. + routeType: this.protocol == appmesh.Protocol.HTTP ? appmesh.RouteType.HTTP : appmesh.RouteType.TCP, + }); + + // Now create a virtual service. Relationship goes like this: + // virtual service -> virtual router -> virtual node + this.virtualService = new appmesh.VirtualService(this.scope, `${this.parentService.id}-virtual-service`, { + mesh: this.mesh, + virtualRouter: this.virtualRouter, + virtualServiceName: `${this.parentService.id}.${cloudmapNamespace.namespaceName}`, + }); + } + + // Connect the app mesh extension for this service to an app mesh + // extension on another service. + public connectToService(otherService: Service) { + const otherAppMesh = otherService.serviceDescription.get('appmesh') as AppMeshExtension; + const otherContainer = otherService.serviceDescription.get('service-container') as Container; + + // Do a check to ensure that these services are in the same environment. + // Currently this extension only supports connecting services within + // the same VPC, same App Mesh service mesh, and same Cloud Map namespace + if (otherAppMesh.parentService.environment.id !== this.parentService.environment.id) { + throw new Error(`Unable to connect service '${this.parentService.id}' in environment '${this.parentService.environment.id}' to service '${otherService.id}' in environment '${otherAppMesh.parentService.environment.id}' because services can not be connected across environment boundaries`); + } + + // First allow this service to talk to the other service + // at a network level. This opens the security groups so that + // the security groups of these two services to each other + this.parentService.ecsService.connections.allowTo( + otherService.ecsService, + ec2.Port.tcp(otherContainer.trafficPort), + `Accept inbound traffic from ${this.parentService.id}`, + ); + + // Next update the app mesh config so that the local Envoy + // proxy on this service knows how to route traffic to + // nodes from the other service. + this.virtualNode.addBackends(otherAppMesh.virtualService); + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/cloudwatch-agent.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/cloudwatch-agent.ts new file mode 100644 index 0000000000000..cd59b24f27860 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/cloudwatch-agent.ts @@ -0,0 +1,73 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; +import { ServiceExtension } from './extension-interfaces'; + +const CLOUDWATCH_AGENT_IMAGE = 'amazon/cloudwatch-agent:latest'; + +/** + * This extension adds a CloudWatch agent to the task definition and + * configures the task to be able to publish metrics to CloudWatch + */ +export class CloudwatchAgentExtension extends ServiceExtension { + private CW_CONFIG_CONTENT = { + logs: { + metrics_collected: { + emf: {}, + }, + }, + metrics: { + metrics_collected: { + statsd: {}, + }, + }, + }; + + constructor() { + super('cloudwatchAgent'); + } + + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + this.scope = scope; + } + + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + // Add the CloudWatch Agent to this task + this.container = taskDefinition.addContainer('cloudwatch-agent', { + image: ecs.ContainerImage.fromRegistry(CLOUDWATCH_AGENT_IMAGE), + environment: { + CW_CONFIG_CONTENT: JSON.stringify(this.CW_CONFIG_CONTENT), + }, + logging: new ecs.AwsLogDriver({ streamPrefix: 'cloudwatch-agent' }), + user: '0:1338', // Ensure that CloudWatch agent outbound traffic doesn't go through proxy + memoryReservationMiB: 50, + }); + + // Add permissions that allow the cloudwatch agent to publish metrics + new iam.Policy(this.scope, `${this.parentService.id}-publish-metrics`, { + roles: [taskDefinition.taskRole], + statements: [ + new iam.PolicyStatement({ + resources: ['*'], + actions: ['cloudwatch:PutMetricData'], + }), + ], + }); + } + + public resolveContainerDependencies() { + if (!this.container) { + throw new Error('The container dependency hook was called before the container was created'); + } + + const appmeshextension = this.parentService.serviceDescription.get('appmesh'); + if (appmeshextension && appmeshextension.container) { + this.container.addContainerDependencies({ + container: appmeshextension.container, + condition: ecs.ContainerDependencyCondition.HEALTHY, + }); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/container.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/container.ts new file mode 100644 index 0000000000000..98c047cc0b2ec --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/container.ts @@ -0,0 +1,145 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; +import { ServiceExtension } from './extension-interfaces'; + +/** + * Setting for the main application container of a service + */ +export interface ContainerExtensionProps { + /** + * How much CPU the container requires + */ + readonly cpu: number, + + /** + * How much memory in megabytes the container requires + */ + readonly memoryMiB: number, + + /** + * The image to run + */ + readonly image: ecs.ContainerImage, + + /** + * What port the image listen for traffic on + */ + readonly trafficPort: number, + + /** + * Environment variables to pass into the container + * @default - No environment variables + */ + readonly environment?: { + [key: string]: string, + } +} + +/** + * The main container of a service. This is generally the container + * which runs your application business logic. Other extensions will attach + * sidecars alongside this main container. + */ +export class Container extends ServiceExtension { + /** + * The port on which the container expects to receive network traffic + */ + public readonly trafficPort: number; + + /** + * The settings for the container + */ + private props: ContainerExtensionProps; + + constructor(props: ContainerExtensionProps) { + super('service-container'); + this.props = props; + this.trafficPort = props.trafficPort; + } + + // @ts-ignore - Ignore unused params that are required for abstract class extend + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + } + + // This hook sets the overall task resource requirements to the + // resource requirements of the application itself. + public modifyTaskDefinitionProps(props: ecs.TaskDefinitionProps) { + return { + ...props, + cpu: this.props.cpu.toString(), + memoryMiB: this.props.memoryMiB.toString(), + } as ecs.TaskDefinitionProps; + } + + // This hook adds the application container to the task definition. + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + let containerProps = { + image: this.props.image, + cpu: Number(this.props.cpu), + memoryLimitMiB: Number(this.props.memoryMiB), + environment: this.props.environment, + } as ecs.ContainerDefinitionOptions; + + // Let other extensions mutate the container definition. This is + // used by extensions which want to add environment variables, modify + // logging parameters, etc. + this.containerMutatingHooks.forEach((hookProvider) => { + containerProps = hookProvider.mutateContainerDefinition(containerProps); + }); + + this.container = taskDefinition.addContainer('app', containerProps); + + // Create a port mapping for the container + this.container.addPortMappings({ + containerPort: this.trafficPort, + }); + + // Raise the ulimits for this main application container + // so that it can handle more concurrent requests + this.container.addUlimits({ + softLimit: 1024000, + hardLimit: 1024000, + name: ecs.UlimitName.NOFILE, + }); + } + + public resolveContainerDependencies() { + if (!this.container) { + throw new Error('The container dependency hook was called before the container was created'); + } + + const firelens = this.parentService.serviceDescription.get('firelens'); + if (firelens && firelens.container) { + this.container.addContainerDependencies({ + container: firelens.container, + condition: ecs.ContainerDependencyCondition.START, + }); + } + + const appmeshextension = this.parentService.serviceDescription.get('appmesh'); + if (appmeshextension && appmeshextension.container) { + this.container.addContainerDependencies({ + container: appmeshextension.container, + condition: ecs.ContainerDependencyCondition.HEALTHY, + }); + } + + const cloudwatchextension = this.parentService.serviceDescription.get('cloudwatchAgent'); + if (cloudwatchextension && cloudwatchextension.container) { + this.container.addContainerDependencies({ + container: cloudwatchextension.container, + condition: ecs.ContainerDependencyCondition.START, + }); + } + + const xrayextension = this.parentService.serviceDescription.get('xray'); + if (xrayextension && xrayextension.container) { + this.container.addContainerDependencies({ + container: xrayextension.container, + condition: ecs.ContainerDependencyCondition.HEALTHY, + }); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts new file mode 100644 index 0000000000000..cdd25613f0fef --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts @@ -0,0 +1,224 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; + +/** + * A list of the capacity types that are supported. These + * capacity types may change the behavior of an extension. + */ +export enum EnvironmentCapacityType { + /** + * Specify that the environment should use AWS Fargate for + * hosting containers + */ + FARGATE = 'fargate', + + /** + * Specify that the environment should launch containers onto + * EC2 instances + */ + EC2 = 'ec2' +} + +/** + * A service props that is in the process of being assembled + * Eventually to be assigned to a ecs.Ec2ServiceProps + * or ecs.FargateServiceProps depending on the environment's + * capacity type. + */ +export interface ServiceBuild { + /** + * The cluster in which to add the service + */ + readonly cluster: ecs.ICluster, + + /** + * The task definition registered to this service + */ + readonly taskDefinition: ecs.TaskDefinition, + + /** + * Configuration for how to register the service in service discovery + * @default - No Cloud Map configured + */ + readonly cloudMapOptions?: ecs.CloudMapOptions + + /** + * During initial task startup how long the healthcheck can fail before + * the task is considered unhealthy. This is used to give the task more + * time to start passing healthchecks + * @default - No grace period + */ + readonly healthCheckGracePeriod?: cdk.Duration, + + /** + * How many tasks to run + * @default - 1 + */ + readonly desiredCount?: number; + + /** + * Minimum healthy task percentage + * @default - 100 + */ + readonly minHealthyPercent?: number; + + /** + * Maximum percentage of tasks that can be launched + * @default - 200 + */ + readonly maxHealthyPercent?: number; +} + +/** + * The shape of a service extension. This abstract class is implemented + * by other extensions which extend the hooks to implement their own + * logic that they want to run during each step of preparing the service + */ +export abstract class ServiceExtension { + /** + * The name of the extension + */ + public name: string; + + /** + * The container of this extension. Most extensions have a container, but not + * every extension is required to have a container, some extensions may just + * modify the properties of the service, or create external resources + * connected to the service + */ + public container?: ecs.ContainerDefinition; + + /** + * The service which this extension is applying itself to. + * Initially extensions are added to a ServiceDescription, but no service + * exists yet. Later when the ServiceDescription is used to create a service, + * the extension is told what Service it is now working on. + */ + protected parentService!: Service; + protected scope!: cdk.Construct; + + // A list of other extensions which want to mutate the + // container definition for this extension. + protected containerMutatingHooks: ContainerMutatingHook[] = []; + + constructor(name: string) { + this.name = name; + } + + /** + * A hook that allows the extension to add hooks to other + * extensions that are registered + */ + public addHooks() { } // tslint:disable-line + + /** + * This hook allows another service extension to register a mutating hook for + * changing the primary container of this extension. This is primarily used + * for the application extension. For example the Firelens extension wants to + * be able to modify the settings of the application container to + * route logs through Firelens. + * @param hook + */ + public addContainerMutatingHook(hook: ContainerMutatingHook) { + this.containerMutatingHooks.push(hook); + } + + /** + * This is a hook which allows extensions to modify the settings of the + * task definition prior to it being created. For example App Mesh + * extension needs to configure an Envoy proxy in the task definition, + * or the application extension wants to set the overall resource for + * the task. + * @param props - Properties of the task definition to be created + */ + public modifyTaskDefinitionProps(props: ecs.TaskDefinitionProps) { + return { + ...props, + } as ecs.TaskDefinitionProps; + } + + /** + * A hook that is called for each extension adhead of time to + * let it do any initial setup, such as creating resources in + * advance. + * @param parent - The parent service which this extension has been added to + * @param scope - The scope that this extension should create resources in + */ + public prehook(parent: Service, scope: cdk.Construct) { + this.parentService = parent; + this.scope = scope; + } + + /** + * Once the task definition is created, this hook is called for each + * extension to give it a chance to add containers to the task definition, + * change the task definition's role to add permissions, etc + * @param taskDefinition - The created task definition to add containers to + */ + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + taskDefinition = taskDefinition; + } + + /** + * Once all containers are added to the task definition this hook is + * called for each extension to give it a chance to resolve its dependency + * graph so that its container starts in the right order based on the + * other extensions that were enabled + */ + public resolveContainerDependencies() { + return; + } + + /** + * Prior to launching the task definition as a service this hook + * is called on each extension to give it a chance to mutate the properties + * of the service to be created + * @param props - The service properties to mutate + */ + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + } as ServiceBuild; + } + + /** + * When this hook is implemented by extension it allows the extension + * to use the service which has been created. It is generally used to + * create any final resources which might depend on the service itself + * @param service - The generated service + */ + public useService(service: ecs.Ec2Service | ecs.FargateService) { + service = service; + } + + /** + * This hook allows the extension to establish a connection to + * extensions from another service. Usually used for things like + * allowing one service to talk to the load balancer or service mesh + * proxy for another service. + * @param service - The other service to connect to + */ + public connectToService(service: Service) { + service = service; + } +} + +/** + * This is an abstract class wrapper for a mutating hook. It is + * extended by any extension which wants to mutate other extension's containers. + */ +export abstract class ContainerMutatingHook { + /** + * This is a hook for modifying the container definition of any upstream + * containers. This is primarily used for the application extension. + * For example the Firelens extension wants to be able to modify the logging + * settings of the application container. + * @param props - The container definition to mutate + */ + public mutateContainerDefinition(props: ecs.ContainerDefinitionOptions) { + return { + ...props, + } as ecs.ContainerDefinitionOptions; + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/firelens.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/firelens.ts new file mode 100644 index 0000000000000..c96ce40e99fe6 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/firelens.ts @@ -0,0 +1,126 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as awslogs from '@aws-cdk/aws-logs'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; +import { Container } from './container'; +import { ContainerMutatingHook, ServiceExtension } from './extension-interfaces'; + +/** + * Settings for the hook which mutates the application container + * to route logs through FireLens + */ +export interface FirelensProps { + /** + * The parent service that is being mutated + */ + readonly parentService: Service; + + /** + * The log group into which logs should be routed + */ + readonly logGroup: awslogs.LogGroup; +} + +/** + * This hook modifies the application container's settings so that + * it routes logs using FireLens + */ +export class FirelensMutatingHook extends ContainerMutatingHook { + private parentService: Service; + private logGroup: awslogs.LogGroup; + + constructor(props: FirelensProps) { + super(); + this.parentService = props.parentService; + this.logGroup = props.logGroup; + } + + public mutateContainerDefinition(props: ecs.ContainerDefinitionOptions) { + return { + ...props, + + logging: ecs.LogDrivers.firelens({ + options: { + Name: 'cloudwatch', + region: cdk.Stack.of(this.parentService).region, + log_group_name: this.logGroup.logGroupName, + log_stream_prefix: `${this.parentService.id}/`, + }, + }), + } as ecs.ContainerDefinitionOptions; + } +} + +/** + * This extension adds a FluentBit log router to the task definition + * and does all the configuration necessarily to enable log routing + * for the task using FireLens + */ +export class FireLensExtension extends ServiceExtension { + private logGroup!: awslogs.LogGroup; + + constructor() { + super('firelens'); + } + + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + + // Create a log group for the service, into which FireLens + // will route the service's logs + this.logGroup = new awslogs.LogGroup(scope, `${service.id}-logs`, { + logGroupName: `${service.id}-logs`, + removalPolicy: cdk.RemovalPolicy.DESTROY, + retention: awslogs.RetentionDays.ONE_WEEK, + }); + } + + // Add hooks to the main application extension so that it is modified to + // have logging properties that enable sending logs via the + // Firelens log router container + public addHooks() { + const container = this.parentService.serviceDescription.get('service-container') as Container; + + if (!container) { + throw new Error('Firelens extension requires an application extension'); + } + + container.addContainerMutatingHook(new FirelensMutatingHook({ + parentService: this.parentService, + logGroup: this.logGroup, + })); + } + + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + // Manually add a firelens log router, so that we can manually manage the dependencies + // to ensure that the Firelens log router depends on the Envoy proxy + this.container = taskDefinition.addFirelensLogRouter('firelens', { + image: ecs.obtainDefaultFluentBitECRImage(taskDefinition, { + logDriver: 'awsfirelens', + options: { + Name: 'cloudwatch', + }, + }), + firelensConfig: { + type: ecs.FirelensLogRouterType.FLUENTBIT, + }, + logging: new ecs.AwsLogDriver({ streamPrefix: 'firelens' }), + memoryReservationMiB: 50, + user: '0:1338', // Give Firelens a group ID that allows its outbound logs to bypass Envoy + }); + } + + public resolveContainerDependencies() { + if (!this.container) { + throw new Error('The container dependency hook was called before the container was created'); + } + + const appmeshextension = this.parentService.serviceDescription.get('appmesh'); + if (appmeshextension && appmeshextension.container) { + this.container.addContainerDependencies({ + container: appmeshextension.container, + condition: ecs.ContainerDependencyCondition.HEALTHY, + }); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts new file mode 100644 index 0000000000000..114aa1350249b --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts @@ -0,0 +1,60 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as alb from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; +import { ServiceExtension, ServiceBuild } from './extension-interfaces'; + +/** + * This extension add a public facing load balancer for sending traffic + * to one or more replicas of the application container + */ +export class HttpLoadBalancerExtension extends ServiceExtension { + private loadBalancer!: alb.IApplicationLoadBalancer; + private listener!: alb.IApplicationListener; + + constructor() { + super('load-balancer'); + } + + // Before the service is created go ahead and create the load balancer itself. + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + + this.loadBalancer = new alb.ApplicationLoadBalancer(scope, `${this.parentService.id}-load-balancer`, { + vpc: this.parentService.vpc, + internetFacing: true, + }); + + this.listener = this.loadBalancer.addListener(`${this.parentService.id}-listener`, { + port: 80, + open: true, + }); + + // Automatically create an output + new cdk.CfnOutput(scope, `${this.parentService.id}-load-balancer-dns-output`, { + value: this.loadBalancer.loadBalancerDnsName, + }); + } + + // Minor service configuration tweaks to work better with a load balancer + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + + // Give the task a little bit of grace time to start passing + // healthchecks. Without this it is possible for a slow starting task + // to cause the ALB to consider the task unhealthy, causing ECS to stop + // the task before it actually has a chance to finish starting up + healthCheckGracePeriod: cdk.Duration.minutes(1), + } as ServiceBuild; + } + + // After the service is created add the service to the load balancer's listener + public useService(service: ecs.Ec2Service | ecs.FargateService) { + this.listener.addTargets(this.parentService.id, { + deregistrationDelay: cdk.Duration.seconds(10), + port: 80, + targets: [service], + }); + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts new file mode 100644 index 0000000000000..e43005cf15bc6 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts @@ -0,0 +1,7 @@ +export * from './container'; +export * from './firelens'; +export * from './appmesh'; +export * from './http-load-balancer'; +export * from './cloudwatch-agent'; +export * from './scale-on-cpu-utilization'; +export * from './xray'; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/scale-on-cpu-utilization.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/scale-on-cpu-utilization.ts new file mode 100644 index 0000000000000..1fa993fae3ecc --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/scale-on-cpu-utilization.ts @@ -0,0 +1,134 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { ServiceExtension, ServiceBuild } from './extension-interfaces'; + +/** + * The autoscaling settings + */ +export interface CpuScalingProps { + /** + * How many tasks to launch initially + * @default - 2 + */ + readonly initialTaskCount?: number; + + /** + * The minimum number of tasks when scaling in + * @default - 2 + */ + readonly minTaskCount?: number; + + /** + * The maximum number of tasks when scaling out + * @default - 8 + */ + readonly maxTaskCount?: number; + + /** + * The CPU utilization to try ot maintain + * @default - 50% + */ + readonly targetCpuUtilization?: number; + + /** + * How long to wait between scale out actions + * @default - 60 seconds + */ + readonly scaleOutCooldown?: cdk.Duration; + + /** + * How long to wait between scale in actions + * @default - 60 seconds + */ + readonly scaleInCooldown?: cdk.Duration; +} + +// The default autoscaling settings +const cpuScalingPropsDefault = { + initialTaskCount: 2, + minTaskCount: 2, + maxTaskCount: 8, + targetCpuUtilization: 50, + scaleOutCooldown: cdk.Duration.seconds(60), + scaleInCooldown: cdk.Duration.seconds(60), +}; + +/** + * This extension helps you scale your service according to CPU utilization + */ +export class ScaleOnCpuUtilization extends ServiceExtension { + /** + * How many tasks to launch initially + */ + public readonly initialTaskCount: number; + + /** + * The minimum number of tasks when scaling in + */ + public readonly minTaskCount: number; + + /** + * The maximum number of tasks when scaling out + */ + public readonly maxTaskCount: number; + + /** + * The CPU utilization to try ot maintain + */ + public readonly targetCpuUtilization: number; + + /** + * How long to wait between scale out actions + */ + public readonly scaleOutCooldown: cdk.Duration; + + /** + * How long to wait between scale in actions + */ + public readonly scaleInCooldown: cdk.Duration; + + constructor(props?: CpuScalingProps) { + super('scale-on-cpu-utilization'); + + let combinedProps = { + ...cpuScalingPropsDefault, + ...props, + }; + + this.initialTaskCount = combinedProps.initialTaskCount; + this.minTaskCount = combinedProps.minTaskCount; + this.maxTaskCount = combinedProps.maxTaskCount; + this.targetCpuUtilization = combinedProps.targetCpuUtilization; + this.scaleOutCooldown = combinedProps.scaleOutCooldown; + this.scaleInCooldown = combinedProps.scaleInCooldown; + } + + // This service modifies properties of the service prior + // to construct creation. + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + + // Launch an initial number of tasks + // In the future we should change this to use a custom resource + // to read the current task count set by autoscaling, so that the task + // count doesn't rollback to the initial level on each deploy. + desiredCount: this.initialTaskCount, + } as ServiceBuild; + } + + // This hook utilizes the resulting service construct + // once it is created + public useService(service: ecs.Ec2Service | ecs.FargateService) { + const scalingTarget = service.autoScaleTaskCount({ + minCapacity: this.minTaskCount, + maxCapacity: this.maxTaskCount, + }); + + scalingTarget.scaleOnCpuUtilization(`${this.parentService.id}-target-cpu-utilization-${this.targetCpuUtilization}`, { + targetUtilizationPercent: this.targetCpuUtilization, + scaleInCooldown: this.scaleInCooldown, + scaleOutCooldown: this.scaleOutCooldown, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/xray.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/xray.ts new file mode 100644 index 0000000000000..3ba344f4133d8 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/xray.ts @@ -0,0 +1,66 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../service'; +import { ServiceExtension } from './extension-interfaces'; + +const XRAY_DAEMON_IMAGE = 'amazon/aws-xray-daemon:latest'; + +/** + * This extension adds an X-Ray daemon inside the task definition, for + * capturing application trace spans and submitting them to the AWS + * X-Ray service. + */ +export class XRayExtension extends ServiceExtension { + constructor() { + super('xray'); + } + + // @ts-ignore - Ignore unused params that are required for abstract class extend + public prehook(service: Service, scope: cdk.Construct) { + this.parentService = service; + } + + public useTaskDefinition(taskDefinition: ecs.TaskDefinition) { + // Add the XRay Daemon to the task + this.container = taskDefinition.addContainer('xray', { + image: ecs.ContainerImage.fromRegistry(XRAY_DAEMON_IMAGE), + essential: true, + memoryReservationMiB: 256, + environment: { + AWS_REGION: cdk.Stack.of(this.parentService).region, + }, + healthCheck: { + command: [ + 'CMD-SHELL', + 'curl -s http://localhost:2000', + ], + startPeriod: cdk.Duration.seconds(10), + interval: cdk.Duration.seconds(5), + timeout: cdk.Duration.seconds(2), + retries: 3, + }, + logging: new ecs.AwsLogDriver({ streamPrefix: 'xray' }), + user: '1337', // X-Ray traffic should not go through Envoy proxy + }); + + // Add permissions to this task to allow it to talk to X-Ray + taskDefinition.taskRole.addManagedPolicy( + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), + ); + } + + public resolveContainerDependencies() { + if (!this.container) { + throw new Error('The container dependency hook was called before the container was created'); + } + + const appmeshextension = this.parentService.serviceDescription.get('appmesh'); + if (appmeshextension && appmeshextension.container) { + this.container.addContainerDependencies({ + container: appmeshextension.container, + condition: ecs.ContainerDependencyCondition.HEALTHY, + }); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/index.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/index.ts new file mode 100644 index 0000000000000..04a891f556dd7 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/index.ts @@ -0,0 +1,5 @@ +export * from './service'; +export * from './service-description'; +export * from './environment'; +export * from './extensions'; +export * from './extensions/extension-interfaces'; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service-description.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service-description.ts new file mode 100644 index 0000000000000..597b6ac6fe5d0 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service-description.ts @@ -0,0 +1,40 @@ +import { ServiceExtension } from './extensions/extension-interfaces'; + +/** + * A description of a service to construct. This construct collects + * all of the extensions that a user wants to add to their Service. + * It is used as a shared collection of all the extensions, allowing + * extensions to query the full list of extensions to determine + * information about how to self configure. + */ +export class ServiceDescription { + /** + * The list of extensions that have been registered to run when + * preparing this service. + */ + public extensions: Record = {}; + + /** + * Adds a new extension to the service. The extensions mutate a service + * to add resources or features to the service + * @param extension - The extension that you wish to add + */ + public add(extension: ServiceExtension) { + if (this.extensions[extension.name]) { + throw new Error(`An extension called ${extension.name} has already been added`); + } + + this.extensions[extension.name] = extension; + + return this; + } + + /** + * Get the extension with a specific name. This is generally used for + * extensions to discover each other's existence. + * @param name + */ + public get(name: string) { + return this.extensions[name]; + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts new file mode 100644 index 0000000000000..0b46f782a64d8 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -0,0 +1,248 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Environment } from './environment'; +import { EnvironmentCapacityType, ServiceBuild } from './extensions/extension-interfaces'; +import { ServiceDescription } from './service-description'; + +/** + * The settings for an ECS Service + */ +export interface ServiceProps { + /** + * A service description to use in building out the service + */ + readonly serviceDescription: ServiceDescription; + + /** + * The environment to launch the service in + */ + readonly environment: Environment +} + +/** + * A service builder class. This construct support various extensions + * which can construct an ECS service progressively. + */ +export class Service extends cdk.Construct { + /** + * The underlying ECS service that was created + */ + public ecsService!: ecs.Ec2Service | ecs.FargateService; + + /** + * The name of this service + */ + public readonly id: string; + + /** + * The VPC into which this service should be placed + */ + public readonly vpc: ec2.IVpc; + + /** + * The cluster that is providing capacity for this service + * [disable-awslint:ref-via-interface] + */ + public readonly cluster: ecs.Cluster; + + /** + * The capacity type that this service will use + */ + public readonly capacityType: EnvironmentCapacityType; + + /** + * The service description used to build this service + */ + public readonly serviceDescription: ServiceDescription; + + /** + * The environment this service was launched in + */ + public readonly environment: Environment; + + /** + * The generated task definition for this service, is only + * generated once .prepare() has been executed + */ + protected taskDefinition!: ecs.TaskDefinition; + + /** + * The list of URL's associated with this service + */ + private urls: Record = {}; + + private readonly scope: cdk.Construct; + + constructor(scope: cdk.Construct, id: string, props: ServiceProps) { + super(scope, id); + + this.scope = scope; + this.id = id; + this.environment = props.environment; + this.vpc = props.environment.vpc; + this.cluster = props.environment.cluster; + this.capacityType = props.environment.capacityType; + this.serviceDescription = props.serviceDescription; + + // Check to make sure that the user has actually added a container + const containerextension = this.serviceDescription.get('service-container'); + + if (!containerextension) { + throw new Error(`Service '${this.id}' must have a Container extension`); + } + + // First set the scope for all the extensions + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].prehook(this, this.scope); + } + } + + // At the point of preparation all extensions have been defined on the service + // so give each extension a chance to now add hooks to other extensions if + // needed + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].addHooks(); + } + } + + // Give each extension a chance to mutate the task def creation properties + let taskDefProps = { + // Default CPU and memory + cpu: '256', + memory: '512', + + // Ensure that the task definition supports both EC2 and Fargate + compatibility: ecs.Compatibility.EC2_AND_FARGATE, + } as ecs.TaskDefinitionProps; + + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + taskDefProps = this.serviceDescription.extensions[extensions].modifyTaskDefinitionProps(taskDefProps); + } + } + + // Now that the task definition properties are assembled, create it + this.taskDefinition = new ecs.TaskDefinition(this.scope, `${this.id}-task-definition`, taskDefProps); + + // Now give each extension a chance to use the task definition + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].useTaskDefinition(this.taskDefinition); + } + } + + // Now that all containers are created, give each extension a chance + // to bake its dependency graph + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].resolveContainerDependencies(); + } + } + + // Give each extension a chance to mutate the service props before + // service creation + let serviceProps = { + cluster: this.cluster, + taskDefinition: this.taskDefinition, + minHealthyPercent: 100, + maxHealthyPercent: 200, + desiredCount: 1, + } as ServiceBuild; + + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + serviceProps = this.serviceDescription.extensions[extensions].modifyServiceProps(serviceProps); + } + } + + // If a maxHealthyPercent and desired count has been set while minHealthyPercent == 100% then we + // need to do some failsafe checking to ensure that the maxHealthyPercent + // actually allows a rolling deploy. Otherwise it is possible to end up with + // blocked deploys that can take no action because minHealtyhPercent == 100% + // prevents running, healthy tasks from being stopped, but a low maxHealthyPercent + // can also prevents new parallel tasks from being started. + if (serviceProps.maxHealthyPercent && serviceProps.desiredCount && serviceProps.minHealthyPercent && serviceProps.minHealthyPercent == 100) { + if (serviceProps.desiredCount == 1) { + // If there is one task then we must allow max percentage to be at + // least 200% for another replacement task to be added + serviceProps = { + ...serviceProps, + maxHealthyPercent: Math.max(200, serviceProps.maxHealthyPercent), + }; + } else if (serviceProps.desiredCount <= 3) { + // If task count is 2 or 3 then max percent must be at least 150% to + // allow one replacement task to be launched at a time. + serviceProps = { + ...serviceProps, + maxHealthyPercent: Math.max(150, serviceProps.maxHealthyPercent), + }; + } else { + // For anything higher than 3 tasks set max percent to at least 125% + // For 4 tasks this will allow exactly one extra replacement task + // at a time, for any higher task count it will allow 25% of the tasks + // to be replaced at a time. + serviceProps = { + ...serviceProps, + maxHealthyPercent: Math.max(125, serviceProps.maxHealthyPercent), + }; + } + } + + // Now that the service props are determined we can create + // the service + if (this.capacityType === EnvironmentCapacityType.EC2) { + this.ecsService = new ecs.Ec2Service(this.scope, `${this.id}-service`, serviceProps); + } else if (this.capacityType === EnvironmentCapacityType.FARGATE) { + this.ecsService = new ecs.FargateService(this.scope, `${this.id}-service`, serviceProps); + } else { + throw new Error(`Unknown capacity type for service ${this.id}`); + } + + // Now give all extensions a chance to use the service + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].useService(this.ecsService); + } + } + } + + /** + * Tell extensions from one service to connect to extensions from + * another sevice if they have implemented a hook for that. + * @param service + */ + public connectTo(service: Service) { + for (const extensions in this.serviceDescription.extensions) { + if (this.serviceDescription.extensions[extensions]) { + this.serviceDescription.extensions[extensions].connectToService(service); + } + } + } + + /** + * This method adds a new URL for the service. This allows extensions + * to submit a URL for the service, for example LB might add its URL + * or App Mesh can add its DNS name for the service. + * @param urlName - The identifier name for this URL + * @param url - The URL itself. + */ + public addURL(urlName: string, url: string) { + this.urls[urlName] = url; + } + + /** + * Retrieve a URL for the service. The URL must have previously been + * stored by one of the URL providing extensions. + * @param urlName - The URL to look up. + */ + public getURL(urlName: string) { + if (!this.urls[urlName]) { + throw new Error(`Unable to find a URL with name '${urlName}'`); + } + + return this.urls[urlName]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/package.json b/packages/@aws-cdk-containers/ecs-service-extensions/package.json new file mode 100644 index 0000000000000..31af1ded594ec --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/package.json @@ -0,0 +1,98 @@ +{ + "name": "@aws-cdk-containers/ecs-service-extensions", + "version": "0.0.0", + "description": "The CDK Construct Library that helps you build ECS services using simple extensions", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk-containers/ecs-service-extensions" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "ecs" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "@types/nodeunit": "^0.0.31", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "cfn2ts": "0.0.0", + "jest": "^25.5.4", + "nodeunit": "^0.11.3", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/aws-applicationautoscaling": "0.0.0", + "@aws-cdk/aws-appmesh": "0.0.0", + "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecr": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-events-targets": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-route53": "0.0.0", + "@aws-cdk/aws-route53-targets": "0.0.0", + "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/region-info": "0.0.0", + "constructs": "^3.0.4" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-applicationautoscaling": "0.0.0", + "@aws-cdk/aws-appmesh": "0.0.0", + "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecr": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-events": "0.0.0", + "@aws-cdk/aws-events-targets": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-route53": "0.0.0", + "@aws-cdk/aws-route53-targets": "0.0.0", + "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/region-info": "0.0.0", + "constructs": "^3.0.4" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "awscdkio": { + "announce": false + }, + "maturity": "experimental", + "stability": "experimental" +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json new file mode 100644 index 0000000000000..6e6d796875817 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json @@ -0,0 +1,3515 @@ +{ + "Resources": { + "mymeshEA67EDEF": { + "Type": "AWS::AppMesh::Mesh", + "Properties": { + "MeshName": "awsecsintegmymeshFCC0D554", + "Spec": {} + } + }, + "productionenvironmentvpcAEB47DF7": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1Subnet8D92C089": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTableAssociationA8117374": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + } + } + }, + "productionenvironmentvpcPublicSubnet1DefaultRoute524C894D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet1EIP54BA88DB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet1EIP54BA88DB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2Subnet298E6C31": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTable842A68D7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTableAssociation0A7549F3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + } + } + }, + "productionenvironmentvpcPublicSubnet2DefaultRoute92CD697D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet2EIP14CA46AA": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet2EIP14CA46AA", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3SubnetC7B5665D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTableAssociationFA34D6E7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + } + } + }, + "productionenvironmentvpcPublicSubnet3DefaultRouteE1ADEA6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet3EIP53405AED": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3NATGateway94604057": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet3EIP53405AED", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1Subnet53F632E6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTableAssociation8BA32463": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + } + } + }, + "productionenvironmentvpcPrivateSubnet1DefaultRouteFBB3DE6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA" + } + } + }, + "productionenvironmentvpcPrivateSubnet2Subnet756FB93C": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTableAssociation09188261": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + } + } + }, + "productionenvironmentvpcPrivateSubnet2DefaultRoute5F9AB6C1": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC" + } + } + }, + "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTableAssociation65F18B9C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + } + }, + "productionenvironmentvpcPrivateSubnet3DefaultRoute2438918B": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet3NATGateway94604057" + } + } + }, + "productionenvironmentvpcIGWE7C39890": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcVPCGW1B428D07": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "InternetGatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + } + }, + "productionenvironmentclusterC6599D2D": { + "Type": "AWS::ECS::Cluster" + }, + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "production", + "Vpc": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "namelogsF4B17D31": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "name-logs", + "RetentionInDays": 7 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "nametaskdefinitionTaskRole50FE844E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSXRayDaemonWriteAccess" + ] + ] + } + ] + } + }, + "nametaskdefinitionTaskRoleDefaultPolicyE66EDC68": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nametaskdefinitionTaskRoleDefaultPolicyE66EDC68", + "Roles": [ + { + "Ref": "nametaskdefinitionTaskRole50FE844E" + } + ] + } + }, + "nametaskdefinition690762BB": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "DependsOn": [ + { + "Condition": "START", + "ContainerName": "firelens" + }, + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + }, + { + "Condition": "START", + "ContainerName": "cloudwatch-agent" + }, + { + "Condition": "HEALTHY", + "ContainerName": "xray" + } + ], + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "cloudwatch", + "region": { + "Ref": "AWS::Region" + }, + "log_group_name": { + "Ref": "namelogsF4B17D31" + }, + "log_stream_prefix": "name/" + } + }, + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + }, + { + "Environment": [ + { + "Name": "APPMESH_VIRTUAL_NODE_NAME", + "Value": { + "Fn::Join": [ + "", + [ + "mesh/", + { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "/virtualNode/name" + ] + ] + } + }, + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "ENABLE_ENVOY_STATS_TAGS", + "Value": "1" + }, + { + "Name": "ENABLE_ENVOY_DOG_STATSD", + "Value": "1" + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "nameenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-appmesh-envoy:v1.15.0.0-prod" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "nametaskdefinitionenvoyLogGroup258B673B" + }, + "awslogs-stream-prefix": "envoy", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 128, + "Name": "envoy", + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ], + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Essential": true, + "FirelensConfiguration": { + "Type": "fluentbit" + }, + "Image": { + "Ref": "SsmParameterValueawsserviceawsforfluentbitlatestC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "nametaskdefinitionfirelensLogGroup80DDA60F" + }, + "awslogs-stream-prefix": "firelens", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "firelens", + "User": "0:1338" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:2000" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": "amazon/aws-xray-daemon:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "nametaskdefinitionxrayLogGroup4AF4CA37" + }, + "awslogs-stream-prefix": "xray", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 256, + "Name": "xray", + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "CW_CONFIG_CONTENT", + "Value": "{\"logs\":{\"metrics_collected\":{\"emf\":{}}},\"metrics\":{\"metrics_collected\":{\"statsd\":{}}}}" + } + ], + "Essential": true, + "Image": "amazon/cloudwatch-agent:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "nametaskdefinitioncloudwatchagentLogGroup78DDC685" + }, + "awslogs-stream-prefix": "cloudwatch-agent", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "cloudwatch-agent", + "User": "0:1338" + } + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "nametaskdefinitionExecutionRole45AC5C9A", + "Arn" + ] + }, + "Family": "awsecsintegnametaskdefinition0EA6A1A0", + "Memory": "2048", + "NetworkMode": "awsvpc", + "ProxyConfiguration": { + "ContainerName": "envoy", + "ProxyConfigurationProperties": [ + { + "Name": "AppPorts", + "Value": "80" + }, + { + "Name": "ProxyEgressPort", + "Value": "15001" + }, + { + "Name": "ProxyIngressPort", + "Value": "15000" + }, + { + "Name": "IgnoredUID", + "Value": "1337" + }, + { + "Name": "IgnoredGID", + "Value": "1338" + }, + { + "Name": "EgressIgnoredIPs", + "Value": "169.254.170.2,169.254.169.254" + } + ], + "Type": "APPMESH" + }, + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "nametaskdefinitionTaskRole50FE844E", + "Arn" + ] + } + } + }, + "nametaskdefinitionenvoyLogGroup258B673B": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "nametaskdefinitionExecutionRole45AC5C9A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "nametaskdefinitionExecutionRoleDefaultPolicyF7942D20": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "nameenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ":repository/aws-appmesh-envoy" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nametaskdefinitionenvoyLogGroup258B673B", + "Arn" + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nametaskdefinitionfirelensLogGroup80DDA60F", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nametaskdefinitionxrayLogGroup4AF4CA37", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nametaskdefinitioncloudwatchagentLogGroup78DDC685", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nametaskdefinitionExecutionRoleDefaultPolicyF7942D20", + "Roles": [ + { + "Ref": "nametaskdefinitionExecutionRole45AC5C9A" + } + ] + } + }, + "nametaskdefinitionfirelensLogGroup80DDA60F": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "nametaskdefinitionxrayLogGroup4AF4CA37": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "nametaskdefinitioncloudwatchagentLogGroup78DDC685": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "nameenvoytoappmesh2767D3E6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appmesh:StreamAggregatedResources", + "Effect": "Allow", + "Resource": { + "Ref": "mymeshEA67EDEF" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameenvoytoappmesh2767D3E6", + "Roles": [ + { + "Ref": "nametaskdefinitionTaskRole50FE844E" + } + ] + } + }, + "namepublishmetricsF329C7AE": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudwatch:PutMetricData", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "namepublishmetricsF329C7AE", + "Roles": [ + { + "Ref": "nametaskdefinitionTaskRole50FE844E" + } + ] + } + }, + "nameserviceService8015C8D6": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 150, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 2, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "nameserviceSecurityGroup33F4662C", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + ] + } + }, + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "nameserviceCloudmapService3D5B0548", + "Arn" + ] + } + } + ], + "TaskDefinition": { + "Ref": "nametaskdefinition690762BB" + } + } + }, + "nameserviceCloudmapService3D5B0548": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 10, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 2 + }, + "Name": "name", + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + } + } + }, + "nameserviceSecurityGroup33F4662C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/name-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "nameserviceSecurityGroupfromawsecsinteggreeterserviceSecurityGroup055DC23B8048057EFF": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Accept inbound traffic from greeter", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "nameserviceSecurityGroup33F4662C", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "greeterserviceSecurityGroupDB4AC3A9", + "GroupId" + ] + }, + "ToPort": 80 + } + }, + "nameserviceTaskCountTarget366C2B3A": { + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + "Properties": { + "MaxCapacity": 8, + "MinCapacity": 2, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "/", + { + "Fn::GetAtt": [ + "nameserviceService8015C8D6", + "Name" + ] + } + ] + ] + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService" + ] + ] + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs" + } + }, + "nameserviceTaskCountTargetnametargetcpuutilization5018B16243": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecsintegnameserviceTaskCountTargetnametargetcpuutilization50CAB59E05", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "nameserviceTaskCountTarget366C2B3A" + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleInCooldown": 60, + "ScaleOutCooldown": 60, + "TargetValue": 50 + } + } + }, + "namevirtualnode6C99CB14": { + "Type": "AWS::AppMesh::VirtualNode", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + }, + "ServiceDiscovery": { + "AWSCloudMap": { + "NamespaceName": "production", + "ServiceName": { + "Fn::GetAtt": [ + "nameserviceCloudmapService3D5B0548", + "Name" + ] + } + } + } + }, + "VirtualNodeName": "name" + } + }, + "namevirtualrouterC00E1ACE": { + "Type": "AWS::AppMesh::VirtualRouter", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ] + }, + "VirtualRouterName": "name" + } + }, + "namevirtualrouternamerouteDCDF3715": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "RouteName": "name-route", + "Spec": { + "HttpRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "namevirtualnode6C99CB14", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "namevirtualrouterC00E1ACE", + "VirtualRouterName" + ] + } + } + }, + "namevirtualservice3DDDDF1E": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "namevirtualrouterC00E1ACE", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "name.production" + } + }, + "greetinglogsCC360934": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "greeting-logs", + "RetentionInDays": 7 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "greetingtaskdefinitionTaskRole9179DA4A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSXRayDaemonWriteAccess" + ] + ] + } + ] + } + }, + "greetingtaskdefinitionTaskRoleDefaultPolicy5DB4510A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetingtaskdefinitionTaskRoleDefaultPolicy5DB4510A", + "Roles": [ + { + "Ref": "greetingtaskdefinitionTaskRole9179DA4A" + } + ] + } + }, + "greetingtaskdefinition31690093": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "DependsOn": [ + { + "Condition": "START", + "ContainerName": "firelens" + }, + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + }, + { + "Condition": "START", + "ContainerName": "cloudwatch-agent" + }, + { + "Condition": "HEALTHY", + "ContainerName": "xray" + } + ], + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/greeting", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "cloudwatch", + "region": { + "Ref": "AWS::Region" + }, + "log_group_name": { + "Ref": "greetinglogsCC360934" + }, + "log_stream_prefix": "greeting/" + } + }, + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + }, + { + "Environment": [ + { + "Name": "APPMESH_VIRTUAL_NODE_NAME", + "Value": { + "Fn::Join": [ + "", + [ + "mesh/", + { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "/virtualNode/greeting" + ] + ] + } + }, + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "ENABLE_ENVOY_STATS_TAGS", + "Value": "1" + }, + { + "Name": "ENABLE_ENVOY_DOG_STATSD", + "Value": "1" + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "greetingenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-appmesh-envoy:v1.15.0.0-prod" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetingtaskdefinitionenvoyLogGroup6556AC35" + }, + "awslogs-stream-prefix": "envoy", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 128, + "Name": "envoy", + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ], + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Essential": true, + "FirelensConfiguration": { + "Type": "fluentbit" + }, + "Image": { + "Ref": "SsmParameterValueawsserviceawsforfluentbitlatestC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetingtaskdefinitionfirelensLogGroupD7A398A7" + }, + "awslogs-stream-prefix": "firelens", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "firelens", + "User": "0:1338" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:2000" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": "amazon/aws-xray-daemon:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetingtaskdefinitionxrayLogGroupD25C072D" + }, + "awslogs-stream-prefix": "xray", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 256, + "Name": "xray", + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "CW_CONFIG_CONTENT", + "Value": "{\"logs\":{\"metrics_collected\":{\"emf\":{}}},\"metrics\":{\"metrics_collected\":{\"statsd\":{}}}}" + } + ], + "Essential": true, + "Image": "amazon/cloudwatch-agent:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetingtaskdefinitioncloudwatchagentLogGroupCEF72742" + }, + "awslogs-stream-prefix": "cloudwatch-agent", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "cloudwatch-agent", + "User": "0:1338" + } + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "greetingtaskdefinitionExecutionRole9E3A7CF3", + "Arn" + ] + }, + "Family": "awsecsinteggreetingtaskdefinitionA6E8A57B", + "Memory": "2048", + "NetworkMode": "awsvpc", + "ProxyConfiguration": { + "ContainerName": "envoy", + "ProxyConfigurationProperties": [ + { + "Name": "AppPorts", + "Value": "80" + }, + { + "Name": "ProxyEgressPort", + "Value": "15001" + }, + { + "Name": "ProxyIngressPort", + "Value": "15000" + }, + { + "Name": "IgnoredUID", + "Value": "1337" + }, + { + "Name": "IgnoredGID", + "Value": "1338" + }, + { + "Name": "EgressIgnoredIPs", + "Value": "169.254.170.2,169.254.169.254" + } + ], + "Type": "APPMESH" + }, + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "greetingtaskdefinitionTaskRole9179DA4A", + "Arn" + ] + } + } + }, + "greetingtaskdefinitionenvoyLogGroup6556AC35": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetingtaskdefinitionExecutionRole9E3A7CF3": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "greetingtaskdefinitionExecutionRoleDefaultPolicy31B93022": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "greetingenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ":repository/aws-appmesh-envoy" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetingtaskdefinitionenvoyLogGroup6556AC35", + "Arn" + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetingtaskdefinitionfirelensLogGroupD7A398A7", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetingtaskdefinitionxrayLogGroupD25C072D", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetingtaskdefinitioncloudwatchagentLogGroupCEF72742", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetingtaskdefinitionExecutionRoleDefaultPolicy31B93022", + "Roles": [ + { + "Ref": "greetingtaskdefinitionExecutionRole9E3A7CF3" + } + ] + } + }, + "greetingtaskdefinitionfirelensLogGroupD7A398A7": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetingtaskdefinitionxrayLogGroupD25C072D": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetingtaskdefinitioncloudwatchagentLogGroupCEF72742": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetingenvoytoappmesh97051B23": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appmesh:StreamAggregatedResources", + "Effect": "Allow", + "Resource": { + "Ref": "mymeshEA67EDEF" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetingenvoytoappmesh97051B23", + "Roles": [ + { + "Ref": "greetingtaskdefinitionTaskRole9179DA4A" + } + ] + } + }, + "greetingpublishmetricsF17124EF": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudwatch:PutMetricData", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetingpublishmetricsF17124EF", + "Roles": [ + { + "Ref": "greetingtaskdefinitionTaskRole9179DA4A" + } + ] + } + }, + "greetingserviceService8DA58640": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 150, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 2, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "greetingserviceSecurityGroupE9BE665B", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + ] + } + }, + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "greetingserviceCloudmapService0A2D7385", + "Arn" + ] + } + } + ], + "TaskDefinition": { + "Ref": "greetingtaskdefinition31690093" + } + } + }, + "greetingserviceCloudmapService0A2D7385": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 10, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 2 + }, + "Name": "greeting", + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + } + } + }, + "greetingserviceSecurityGroupE9BE665B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/greeting-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "greetingserviceSecurityGroupfromawsecsinteggreeterserviceSecurityGroup055DC23B807B9C4357": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Accept inbound traffic from greeter", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "greetingserviceSecurityGroupE9BE665B", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "greeterserviceSecurityGroupDB4AC3A9", + "GroupId" + ] + }, + "ToPort": 80 + } + }, + "greetingserviceTaskCountTargetA036048C": { + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + "Properties": { + "MaxCapacity": 8, + "MinCapacity": 2, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "/", + { + "Fn::GetAtt": [ + "greetingserviceService8DA58640", + "Name" + ] + } + ] + ] + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService" + ] + ] + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs" + } + }, + "greetingserviceTaskCountTargetgreetingtargetcpuutilization50FEAF6434": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecsinteggreetingserviceTaskCountTargetgreetingtargetcpuutilization504C76B1F4", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "greetingserviceTaskCountTargetA036048C" + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleInCooldown": 60, + "ScaleOutCooldown": 60, + "TargetValue": 50 + } + } + }, + "greetingvirtualnodeC4A2C517": { + "Type": "AWS::AppMesh::VirtualNode", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + }, + "ServiceDiscovery": { + "AWSCloudMap": { + "NamespaceName": "production", + "ServiceName": { + "Fn::GetAtt": [ + "greetingserviceCloudmapService0A2D7385", + "Name" + ] + } + } + } + }, + "VirtualNodeName": "greeting" + } + }, + "greetingvirtualrouter0F898D1A": { + "Type": "AWS::AppMesh::VirtualRouter", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ] + }, + "VirtualRouterName": "greeting" + } + }, + "greetingvirtualroutergreetingroute46305F50": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "RouteName": "greeting-route", + "Spec": { + "HttpRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "greetingvirtualnodeC4A2C517", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "greetingvirtualrouter0F898D1A", + "VirtualRouterName" + ] + } + } + }, + "greetingvirtualservice60AD3AD9": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "greetingvirtualrouter0F898D1A", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "greeting.production" + } + }, + "greeterlogsCCD2F8B2": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "greeter-logs", + "RetentionInDays": 7 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "greeterloadbalancer85256741": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "greeterloadbalancerSecurityGroupEE1B20F3", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + }, + { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + }, + { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + } + ], + "Type": "application" + }, + "DependsOn": [ + "productionenvironmentvpcPublicSubnet1DefaultRoute524C894D", + "productionenvironmentvpcPublicSubnet2DefaultRoute92CD697D", + "productionenvironmentvpcPublicSubnet3DefaultRouteE1ADEA6C" + ] + }, + "greeterloadbalancerSecurityGroupEE1B20F3": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awsecsinteggreeterloadbalancer147D1D5C", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "greeterloadbalancerSecurityGrouptoawsecsinteggreeterserviceSecurityGroup055DC23B803EEF3320": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "greeterloadbalancerSecurityGroupEE1B20F3", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "greeterserviceSecurityGroupDB4AC3A9", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "greeterloadbalancergreeterlistener952E028B": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "greeterloadbalancergreeterlistenergreeterGroupFFBFF0C2" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "greeterloadbalancer85256741" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "greeterloadbalancergreeterlistenergreeterGroupFFBFF0C2": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "10" + } + ], + "TargetType": "ip", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "greetertaskdefinitionTaskRole2A098ACC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSXRayDaemonWriteAccess" + ] + ] + } + ] + } + }, + "greetertaskdefinitionTaskRoleDefaultPolicyD0F53B1C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetertaskdefinitionTaskRoleDefaultPolicyD0F53B1C", + "Roles": [ + { + "Ref": "greetertaskdefinitionTaskRole2A098ACC" + } + ] + } + }, + "greetertaskdefinitionE956EEA2": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "DependsOn": [ + { + "Condition": "START", + "ContainerName": "firelens" + }, + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + }, + { + "Condition": "START", + "ContainerName": "cloudwatch-agent" + }, + { + "Condition": "HEALTHY", + "ContainerName": "xray" + } + ], + "Environment": [ + { + "Name": "PORT", + "Value": "80" + }, + { + "Name": "GREETING_URL", + "Value": "http://greeting.production" + }, + { + "Name": "NAME_URL", + "Value": "http://name.production" + } + ], + "Essential": true, + "Image": "nathanpeck/greeter", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "cloudwatch", + "region": { + "Ref": "AWS::Region" + }, + "log_group_name": { + "Ref": "greeterlogsCCD2F8B2" + }, + "log_stream_prefix": "greeter/" + } + }, + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + }, + { + "Environment": [ + { + "Name": "APPMESH_VIRTUAL_NODE_NAME", + "Value": { + "Fn::Join": [ + "", + [ + "mesh/", + { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "/virtualNode/greeter" + ] + ] + } + }, + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "ENABLE_ENVOY_STATS_TAGS", + "Value": "1" + }, + { + "Name": "ENABLE_ENVOY_DOG_STATSD", + "Value": "1" + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "greeterenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-appmesh-envoy:v1.15.0.0-prod" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetertaskdefinitionenvoyLogGroup6E10B93E" + }, + "awslogs-stream-prefix": "envoy", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 128, + "Name": "envoy", + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ], + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Essential": true, + "FirelensConfiguration": { + "Type": "fluentbit" + }, + "Image": { + "Ref": "SsmParameterValueawsserviceawsforfluentbitlatestC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetertaskdefinitionfirelensLogGroupD5BAAC35" + }, + "awslogs-stream-prefix": "firelens", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "firelens", + "User": "0:1338" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:2000" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": "amazon/aws-xray-daemon:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetertaskdefinitionxrayLogGroupBC1558B6" + }, + "awslogs-stream-prefix": "xray", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 256, + "Name": "xray", + "User": "1337" + }, + { + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "CW_CONFIG_CONTENT", + "Value": "{\"logs\":{\"metrics_collected\":{\"emf\":{}}},\"metrics\":{\"metrics_collected\":{\"statsd\":{}}}}" + } + ], + "Essential": true, + "Image": "amazon/cloudwatch-agent:latest", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "greetertaskdefinitioncloudwatchagentLogGroupE7EAF327" + }, + "awslogs-stream-prefix": "cloudwatch-agent", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 50, + "Name": "cloudwatch-agent", + "User": "0:1338" + } + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "greetertaskdefinitionExecutionRoleAED0EC79", + "Arn" + ] + }, + "Family": "awsecsinteggreetertaskdefinitionB95239FB", + "Memory": "2048", + "NetworkMode": "awsvpc", + "ProxyConfiguration": { + "ContainerName": "envoy", + "ProxyConfigurationProperties": [ + { + "Name": "AppPorts", + "Value": "80" + }, + { + "Name": "ProxyEgressPort", + "Value": "15001" + }, + { + "Name": "ProxyIngressPort", + "Value": "15000" + }, + { + "Name": "IgnoredUID", + "Value": "1337" + }, + { + "Name": "IgnoredGID", + "Value": "1338" + }, + { + "Name": "EgressIgnoredIPs", + "Value": "169.254.170.2,169.254.169.254" + } + ], + "Type": "APPMESH" + }, + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "greetertaskdefinitionTaskRole2A098ACC", + "Arn" + ] + } + } + }, + "greetertaskdefinitionenvoyLogGroup6E10B93E": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetertaskdefinitionExecutionRoleAED0EC79": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "greetertaskdefinitionExecutionRoleDefaultPolicy0D8E9106": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "greeterenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ":repository/aws-appmesh-envoy" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetertaskdefinitionenvoyLogGroup6E10B93E", + "Arn" + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetertaskdefinitionfirelensLogGroupD5BAAC35", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetertaskdefinitionxrayLogGroupBC1558B6", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "greetertaskdefinitioncloudwatchagentLogGroupE7EAF327", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greetertaskdefinitionExecutionRoleDefaultPolicy0D8E9106", + "Roles": [ + { + "Ref": "greetertaskdefinitionExecutionRoleAED0EC79" + } + ] + } + }, + "greetertaskdefinitionfirelensLogGroupD5BAAC35": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetertaskdefinitionxrayLogGroupBC1558B6": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greetertaskdefinitioncloudwatchagentLogGroupE7EAF327": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "greeterenvoytoappmesh1A94938B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appmesh:StreamAggregatedResources", + "Effect": "Allow", + "Resource": { + "Ref": "mymeshEA67EDEF" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greeterenvoytoappmesh1A94938B", + "Roles": [ + { + "Ref": "greetertaskdefinitionTaskRole2A098ACC" + } + ] + } + }, + "greeterpublishmetrics0CCA359A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudwatch:PutMetricData", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "greeterpublishmetrics0CCA359A", + "Roles": [ + { + "Ref": "greetertaskdefinitionTaskRole2A098ACC" + } + ] + } + }, + "greeterserviceService8EDD3244": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 150, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 2, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "app", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "greeterloadbalancergreeterlistenergreeterGroupFFBFF0C2" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "greeterserviceSecurityGroupDB4AC3A9", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + ] + } + }, + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "greeterserviceCloudmapServiceEE292978", + "Arn" + ] + } + } + ], + "TaskDefinition": { + "Ref": "greetertaskdefinitionE956EEA2" + } + }, + "DependsOn": [ + "greeterloadbalancergreeterlistenergreeterGroupFFBFF0C2", + "greeterloadbalancergreeterlistener952E028B" + ] + }, + "greeterserviceCloudmapServiceEE292978": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 10, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 2 + }, + "Name": "greeter", + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + } + } + }, + "greeterserviceSecurityGroupDB4AC3A9": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/greeter-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "greeterserviceSecurityGroupfromawsecsinteggreeterloadbalancerSecurityGroupF791FA838037FDF8F5": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "greeterserviceSecurityGroupDB4AC3A9", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "greeterloadbalancerSecurityGroupEE1B20F3", + "GroupId" + ] + }, + "ToPort": 80 + } + }, + "greeterserviceTaskCountTargetAB95B3D0": { + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + "Properties": { + "MaxCapacity": 8, + "MinCapacity": 2, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "/", + { + "Fn::GetAtt": [ + "greeterserviceService8EDD3244", + "Name" + ] + } + ] + ] + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService" + ] + ] + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs" + } + }, + "greeterserviceTaskCountTargetgreetertargetcpuutilization50299508D1": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecsinteggreeterserviceTaskCountTargetgreetertargetcpuutilization50A75D0C0D", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "greeterserviceTaskCountTargetAB95B3D0" + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleInCooldown": 60, + "ScaleOutCooldown": 60, + "TargetValue": 50 + } + } + }, + "greetervirtualnode21EA7CC9": { + "Type": "AWS::AppMesh::VirtualNode", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Backends": [ + { + "VirtualService": { + "VirtualServiceName": { + "Fn::GetAtt": [ + "namevirtualservice3DDDDF1E", + "VirtualServiceName" + ] + } + } + }, + { + "VirtualService": { + "VirtualServiceName": { + "Fn::GetAtt": [ + "greetingvirtualservice60AD3AD9", + "VirtualServiceName" + ] + } + } + } + ], + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + }, + "ServiceDiscovery": { + "AWSCloudMap": { + "NamespaceName": "production", + "ServiceName": { + "Fn::GetAtt": [ + "greeterserviceCloudmapServiceEE292978", + "Name" + ] + } + } + } + }, + "VirtualNodeName": "greeter" + } + }, + "greetervirtualrouter193840BB": { + "Type": "AWS::AppMesh::VirtualRouter", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ] + }, + "VirtualRouterName": "greeter" + } + }, + "greetervirtualroutergreeterroute3EC6ACB0": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "RouteName": "greeter-route", + "Spec": { + "HttpRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "greetervirtualnode21EA7CC9", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "greetervirtualrouter193840BB", + "VirtualRouterName" + ] + } + } + }, + "greetervirtualservice6559950C": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "mymeshEA67EDEF", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "greetervirtualrouter193840BB", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "greeter.production" + } + } + }, + "Mappings": { + "nameenvoyimageaccountmapping": { + "ap-northeast-1": { + "ecrRepo": "840364872350" + }, + "ap-northeast-2": { + "ecrRepo": "840364872350" + }, + "ap-south-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-2": { + "ecrRepo": "840364872350" + }, + "ca-central-1": { + "ecrRepo": "840364872350" + }, + "eu-central-1": { + "ecrRepo": "840364872350" + }, + "eu-north-1": { + "ecrRepo": "840364872350" + }, + "eu-south-1": { + "ecrRepo": "840364872350" + }, + "eu-west-1": { + "ecrRepo": "840364872350" + }, + "eu-west-2": { + "ecrRepo": "840364872350" + }, + "eu-west-3": { + "ecrRepo": "840364872350" + }, + "sa-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-2": { + "ecrRepo": "840364872350" + }, + "us-west-1": { + "ecrRepo": "840364872350" + }, + "us-west-2": { + "ecrRepo": "840364872350" + }, + "me-south-1": { + "ecrRepo": "772975370895" + }, + "ap-east-1": { + "ecrRepo": "856666278305" + } + }, + "greetingenvoyimageaccountmapping": { + "ap-northeast-1": { + "ecrRepo": "840364872350" + }, + "ap-northeast-2": { + "ecrRepo": "840364872350" + }, + "ap-south-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-2": { + "ecrRepo": "840364872350" + }, + "ca-central-1": { + "ecrRepo": "840364872350" + }, + "eu-central-1": { + "ecrRepo": "840364872350" + }, + "eu-north-1": { + "ecrRepo": "840364872350" + }, + "eu-south-1": { + "ecrRepo": "840364872350" + }, + "eu-west-1": { + "ecrRepo": "840364872350" + }, + "eu-west-2": { + "ecrRepo": "840364872350" + }, + "eu-west-3": { + "ecrRepo": "840364872350" + }, + "sa-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-2": { + "ecrRepo": "840364872350" + }, + "us-west-1": { + "ecrRepo": "840364872350" + }, + "us-west-2": { + "ecrRepo": "840364872350" + }, + "me-south-1": { + "ecrRepo": "772975370895" + }, + "ap-east-1": { + "ecrRepo": "856666278305" + } + }, + "greeterenvoyimageaccountmapping": { + "ap-northeast-1": { + "ecrRepo": "840364872350" + }, + "ap-northeast-2": { + "ecrRepo": "840364872350" + }, + "ap-south-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-2": { + "ecrRepo": "840364872350" + }, + "ca-central-1": { + "ecrRepo": "840364872350" + }, + "eu-central-1": { + "ecrRepo": "840364872350" + }, + "eu-north-1": { + "ecrRepo": "840364872350" + }, + "eu-south-1": { + "ecrRepo": "840364872350" + }, + "eu-west-1": { + "ecrRepo": "840364872350" + }, + "eu-west-2": { + "ecrRepo": "840364872350" + }, + "eu-west-3": { + "ecrRepo": "840364872350" + }, + "sa-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-2": { + "ecrRepo": "840364872350" + }, + "us-west-1": { + "ecrRepo": "840364872350" + }, + "us-west-2": { + "ecrRepo": "840364872350" + }, + "me-south-1": { + "ecrRepo": "772975370895" + }, + "ap-east-1": { + "ecrRepo": "856666278305" + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceawsforfluentbitlatestC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/aws-for-fluent-bit/latest" + } + }, + "Outputs": { + "greeterloadbalancerdnsoutput": { + "Value": { + "Fn::GetAtt": [ + "greeterloadbalancer85256741", + "DNSName" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.ts new file mode 100644 index 0000000000000..7241ff0dd8ef1 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.ts @@ -0,0 +1,100 @@ +import { Mesh } from '@aws-cdk/aws-appmesh'; +import { ContainerImage } from '@aws-cdk/aws-ecs'; +import { App, Stack } from '@aws-cdk/core'; +import { AppMeshExtension, CloudwatchAgentExtension, Container, Environment, FireLensExtension, HttpLoadBalancerExtension, ScaleOnCpuUtilization, Service, ServiceDescription, XRayExtension } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'aws-ecs-integ'); + +const mesh = new Mesh(stack, 'my-mesh'); +const environment = new Environment(stack, 'production'); + +/** Name service */ +const nameDescription = new ServiceDescription(); +nameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +nameDescription.add(new AppMeshExtension({ mesh })); +nameDescription.add(new FireLensExtension()); +nameDescription.add(new XRayExtension()); +nameDescription.add(new CloudwatchAgentExtension()); +nameDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 2, + minTaskCount: 2, +})); + +const nameService = new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, +}); + +/** Greeting service */ +const greetingDescription = new ServiceDescription(); +greetingDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/greeting'), + environment: { + PORT: '80', + }, +})); +greetingDescription.add(new AppMeshExtension({ mesh })); +greetingDescription.add(new FireLensExtension()); +greetingDescription.add(new XRayExtension()); +greetingDescription.add(new CloudwatchAgentExtension()); +greetingDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 2, + minTaskCount: 2, +})); + +const greetingService = new Service(stack, 'greeting', { + environment: environment, + serviceDescription: greetingDescription, +}); + +/** Greeter service */ +const greeterDescription = new ServiceDescription(); +greeterDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/greeter'), + environment: { + PORT: '80', + GREETING_URL: 'http://greeting.production', + NAME_URL: 'http://name.production', + }, +})); +greeterDescription.add(new AppMeshExtension({ mesh })); +greeterDescription.add(new FireLensExtension()); +greeterDescription.add(new XRayExtension()); +greeterDescription.add(new CloudwatchAgentExtension()); +greeterDescription.add(new HttpLoadBalancerExtension()); +greeterDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 2, + minTaskCount: 2, +})); + +const greeterService = new Service(stack, 'greeter', { + environment: environment, + serviceDescription: greeterDescription, +}); + +greeterService.connectTo(nameService); +greeterService.connectTo(greetingService); + +/** + * Expectations are that you should see an output + * of the load balancer URL for the greeter service, make + * a request to it and see a greeting phrase constructed out + * of a random greeting and a random name from the two underlying + * services. The other addons enable tracing and logging which must + * be verified separately. + */ \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.expected.json new file mode 100644 index 0000000000000..eca0061871041 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.expected.json @@ -0,0 +1,704 @@ +{ + "Resources": { + "productionenvironmentvpcAEB47DF7": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1Subnet8D92C089": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTableAssociationA8117374": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + } + } + }, + "productionenvironmentvpcPublicSubnet1DefaultRoute524C894D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet1EIP54BA88DB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet1EIP54BA88DB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2Subnet298E6C31": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTable842A68D7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTableAssociation0A7549F3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + } + } + }, + "productionenvironmentvpcPublicSubnet2DefaultRoute92CD697D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet2EIP14CA46AA": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet2EIP14CA46AA", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3SubnetC7B5665D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTableAssociationFA34D6E7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + } + } + }, + "productionenvironmentvpcPublicSubnet3DefaultRouteE1ADEA6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet3EIP53405AED": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3NATGateway94604057": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet3EIP53405AED", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1Subnet53F632E6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTableAssociation8BA32463": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + } + } + }, + "productionenvironmentvpcPrivateSubnet1DefaultRouteFBB3DE6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA" + } + } + }, + "productionenvironmentvpcPrivateSubnet2Subnet756FB93C": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTableAssociation09188261": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + } + } + }, + "productionenvironmentvpcPrivateSubnet2DefaultRoute5F9AB6C1": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC" + } + } + }, + "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTableAssociation65F18B9C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + } + }, + "productionenvironmentvpcPrivateSubnet3DefaultRoute2438918B": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet3NATGateway94604057" + } + } + }, + "productionenvironmentvpcIGWE7C39890": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcVPCGW1B428D07": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "InternetGatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + } + }, + "productionenvironmentclusterC6599D2D": { + "Type": "AWS::ECS::Cluster" + }, + "nametaskdefinitionTaskRole50FE844E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "nametaskdefinition690762BB": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + } + ], + "Cpu": "1024", + "Family": "awsecsintegnametaskdefinition0EA6A1A0", + "Memory": "2048", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "nametaskdefinitionTaskRole50FE844E", + "Arn" + ] + } + } + }, + "nameserviceService8015C8D6": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 10, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "nameserviceSecurityGroup33F4662C", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + ] + } + }, + "TaskDefinition": { + "Ref": "nametaskdefinition690762BB" + } + } + }, + "nameserviceSecurityGroup33F4662C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/name-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "nameserviceTaskCountTarget366C2B3A": { + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + "Properties": { + "MaxCapacity": 20, + "MinCapacity": 5, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "/", + { + "Fn::GetAtt": [ + "nameserviceService8015C8D6", + "Name" + ] + } + ] + ] + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService" + ] + ] + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs" + } + }, + "nameserviceTaskCountTargetTargetCpuUtilization50E2840097": { + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + "Properties": { + "PolicyName": "awsecsintegnameserviceTaskCountTargetTargetCpuUtilization508522EB56", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "nameserviceTaskCountTarget366C2B3A" + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilization" + }, + "ScaleInCooldown": 60, + "ScaleOutCooldown": 60, + "TargetValue": 50 + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.ts new file mode 100644 index 0000000000000..a33e066841034 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.custom-service-extension.ts @@ -0,0 +1,65 @@ +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Container, Environment, Service, ServiceBuild, ServiceDescription, ServiceExtension } from '../lib'; + +class MyCustomAutoscaling extends ServiceExtension { + constructor() { + super('my-custom-autoscaling'); + } + + // This service modifies properties of the service prior + // to construct creation. + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + + // Initially launch 10 copies of the service + desiredCount: 10, + } as ServiceBuild; + } + + // This hook utilizes the resulting service construct + // once it is created + public useService(service: ecs.Ec2Service | ecs.FargateService) { + const scalingTarget = service.autoScaleTaskCount({ + minCapacity: 5, // Min 5 tasks + maxCapacity: 20, // Max 20 tasks + }); + + scalingTarget.scaleOnCpuUtilization('TargetCpuUtilization50', { + targetUtilizationPercent: 50, + scaleInCooldown: cdk.Duration.seconds(60), + scaleOutCooldown: cdk.Duration.seconds(60), + }); + } +} + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ'); + +const environment = new Environment(stack, 'production'); + +/** Name service */ +const nameDescription = new ServiceDescription(); +nameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +nameDescription.add(new MyCustomAutoscaling()); + +new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, +}); + +/** + * Expectation is that the user is able to implement their own extension + * using the abstract class, and that it will function. This will help + * catch breaking changes to extensions. (Might need to make this example + * custom extension more complex eventually) + */ \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json new file mode 100644 index 0000000000000..bd31a465f6862 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json @@ -0,0 +1,2252 @@ +{ + "Resources": { + "productionenvironmentvpcAEB47DF7": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1Subnet8D92C089": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1RouteTableAssociationA8117374": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + } + } + }, + "productionenvironmentvpcPublicSubnet1DefaultRoute524C894D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet1RouteTable6E9ABC21" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet1EIP54BA88DB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet1EIP54BA88DB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet1" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2Subnet298E6C31": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTable842A68D7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2RouteTableAssociation0A7549F3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + } + } + }, + "productionenvironmentvpcPublicSubnet2DefaultRoute92CD697D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet2RouteTable842A68D7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet2EIP14CA46AA": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet2EIP14CA46AA", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet2" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3SubnetC7B5665D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3RouteTableAssociationFA34D6E7": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + } + } + }, + "productionenvironmentvpcPublicSubnet3DefaultRouteE1ADEA6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPublicSubnet3RouteTable00E3BF60" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + }, + "DependsOn": [ + "productionenvironmentvpcVPCGW1B428D07" + ] + }, + "productionenvironmentvpcPublicSubnet3EIP53405AED": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPublicSubnet3NATGateway94604057": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "productionenvironmentvpcPublicSubnet3EIP53405AED", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PublicSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1Subnet53F632E6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet1RouteTableAssociation8BA32463": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + } + } + }, + "productionenvironmentvpcPrivateSubnet1DefaultRouteFBB3DE6C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet1RouteTable2C6DFF0C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA" + } + } + }, + "productionenvironmentvpcPrivateSubnet2Subnet756FB93C": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet2RouteTableAssociation09188261": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + } + } + }, + "productionenvironmentvpcPrivateSubnet2DefaultRoute5F9AB6C1": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet2RouteTable2F77D0D2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC" + } + } + }, + "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "productionenvironmentvpcPrivateSubnet3RouteTableAssociation65F18B9C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "SubnetId": { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + } + }, + "productionenvironmentvpcPrivateSubnet3DefaultRoute2438918B": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "productionenvironmentvpcPrivateSubnet3RouteTable1A244D34" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "productionenvironmentvpcPublicSubnet3NATGateway94604057" + } + } + }, + "productionenvironmentvpcIGWE7C39890": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/production-environment-vpc" + } + ] + } + }, + "productionenvironmentvpcVPCGW1B428D07": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + }, + "InternetGatewayId": { + "Ref": "productionenvironmentvpcIGWE7C39890" + } + } + }, + "productionenvironmentclusterC6599D2D": { + "Type": "AWS::ECS::Cluster" + }, + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "production", + "Vpc": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "developmentenvironmentvpcCB4216A4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet1Subnet0D18046B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet1RouteTable8FC58983": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet1RouteTableAssociation8F257A0C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet1RouteTable8FC58983" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet1Subnet0D18046B" + } + } + }, + "developmentenvironmentvpcPublicSubnet1DefaultRoute2BAA794D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet1RouteTable8FC58983" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "developmentenvironmentvpcIGW2B4E91B1" + } + }, + "DependsOn": [ + "developmentenvironmentvpcVPCGW702C2C38" + ] + }, + "developmentenvironmentvpcPublicSubnet1EIPE8B9F4D4": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet1NATGateway6B01CC4E": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "developmentenvironmentvpcPublicSubnet1EIPE8B9F4D4", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet1Subnet0D18046B" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet2SubnetC2026CD3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet2RouteTable803A92C2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet2RouteTableAssociation6630985C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet2RouteTable803A92C2" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet2SubnetC2026CD3" + } + } + }, + "developmentenvironmentvpcPublicSubnet2DefaultRoute2C5118B5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet2RouteTable803A92C2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "developmentenvironmentvpcIGW2B4E91B1" + } + }, + "DependsOn": [ + "developmentenvironmentvpcVPCGW702C2C38" + ] + }, + "developmentenvironmentvpcPublicSubnet2EIP560C2BBA": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet2NATGateway15A9C252": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "developmentenvironmentvpcPublicSubnet2EIP560C2BBA", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet2SubnetC2026CD3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet3Subnet00B8A77C": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet3RouteTableAEB3B528": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet3RouteTableAssociationF90257AE": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet3RouteTableAEB3B528" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet3Subnet00B8A77C" + } + } + }, + "developmentenvironmentvpcPublicSubnet3DefaultRoute11687292": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPublicSubnet3RouteTableAEB3B528" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "developmentenvironmentvpcIGW2B4E91B1" + } + }, + "DependsOn": [ + "developmentenvironmentvpcVPCGW702C2C38" + ] + }, + "developmentenvironmentvpcPublicSubnet3EIPE2E366D3": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPublicSubnet3NATGatewayAD65DE0B": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "developmentenvironmentvpcPublicSubnet3EIPE2E366D3", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPublicSubnet3Subnet00B8A77C" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PublicSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet1SubnetAAECD171": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet1RouteTable29CEB1DB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet1" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet1RouteTableAssociation95EE92E6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet1RouteTable29CEB1DB" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPrivateSubnet1SubnetAAECD171" + } + } + }, + "developmentenvironmentvpcPrivateSubnet1DefaultRoute1813B5F3": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet1RouteTable29CEB1DB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "developmentenvironmentvpcPublicSubnet1NATGateway6B01CC4E" + } + } + }, + "developmentenvironmentvpcPrivateSubnet2Subnet20C1C37D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet2RouteTable687AB03B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet2" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet2RouteTableAssociation1C336DAE": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet2RouteTable687AB03B" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPrivateSubnet2Subnet20C1C37D" + } + } + }, + "developmentenvironmentvpcPrivateSubnet2DefaultRoute1B4A6D0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet2RouteTable687AB03B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "developmentenvironmentvpcPublicSubnet2NATGateway15A9C252" + } + } + }, + "developmentenvironmentvpcPrivateSubnet3SubnetAE84C610": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet3RouteTableBD7C810A": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc/PrivateSubnet3" + } + ] + } + }, + "developmentenvironmentvpcPrivateSubnet3RouteTableAssociation08099995": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet3RouteTableBD7C810A" + }, + "SubnetId": { + "Ref": "developmentenvironmentvpcPrivateSubnet3SubnetAE84C610" + } + } + }, + "developmentenvironmentvpcPrivateSubnet3DefaultRoute93B77208": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "developmentenvironmentvpcPrivateSubnet3RouteTableBD7C810A" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "developmentenvironmentvpcPublicSubnet3NATGatewayAD65DE0B" + } + } + }, + "developmentenvironmentvpcIGW2B4E91B1": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/development-environment-vpc" + } + ] + } + }, + "developmentenvironmentvpcVPCGW702C2C38": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + }, + "InternetGatewayId": { + "Ref": "developmentenvironmentvpcIGW2B4E91B1" + } + } + }, + "developmentenvironmentcluster1B64BE8B": { + "Type": "AWS::ECS::Cluster" + }, + "developmentenvironmentclusterDefaultServiceDiscoveryNamespace0F234767": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "development", + "Vpc": { + "Ref": "developmentenvironmentvpcCB4216A4" + } + } + }, + "productionmeshB6E81744": { + "Type": "AWS::AppMesh::Mesh", + "Properties": { + "MeshName": "awsecsintegproductionmesh1A4ED585", + "Spec": {} + } + }, + "developmentmeshCEC28AFE": { + "Type": "AWS::AppMesh::Mesh", + "Properties": { + "MeshName": "awsecsintegdevelopmentmeshEFC35179", + "Spec": {} + } + }, + "nameproductiontaskdefinitionTaskRole93E922EB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "nameproductiontaskdefinition33A5D5E0": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + }, + { + "Environment": [ + { + "Name": "APPMESH_VIRTUAL_NODE_NAME", + "Value": { + "Fn::Join": [ + "", + [ + "mesh/", + { + "Fn::GetAtt": [ + "productionmeshB6E81744", + "MeshName" + ] + }, + "/virtualNode/name-production" + ] + ] + } + }, + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "ENABLE_ENVOY_STATS_TAGS", + "Value": "1" + }, + { + "Name": "ENABLE_ENVOY_DOG_STATSD", + "Value": "1" + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "nameproductionenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-appmesh-envoy:v1.15.0.0-prod" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "nameproductiontaskdefinitionenvoyLogGroupF79A2732" + }, + "awslogs-stream-prefix": "envoy", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 128, + "Name": "envoy", + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ], + "User": "1337" + } + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "nameproductiontaskdefinitionExecutionRoleB72DD86B", + "Arn" + ] + }, + "Family": "awsecsintegnameproductiontaskdefinition9E1C363B", + "Memory": "2048", + "NetworkMode": "awsvpc", + "ProxyConfiguration": { + "ContainerName": "envoy", + "ProxyConfigurationProperties": [ + { + "Name": "AppPorts", + "Value": "80" + }, + { + "Name": "ProxyEgressPort", + "Value": "15001" + }, + { + "Name": "ProxyIngressPort", + "Value": "15000" + }, + { + "Name": "IgnoredUID", + "Value": "1337" + }, + { + "Name": "IgnoredGID", + "Value": "1338" + }, + { + "Name": "EgressIgnoredIPs", + "Value": "169.254.170.2,169.254.169.254" + } + ], + "Type": "APPMESH" + }, + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "nameproductiontaskdefinitionTaskRole93E922EB", + "Arn" + ] + } + } + }, + "nameproductiontaskdefinitionenvoyLogGroupF79A2732": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "nameproductiontaskdefinitionExecutionRoleB72DD86B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "nameproductiontaskdefinitionExecutionRoleDefaultPolicyB66C67BE": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "nameproductionenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ":repository/aws-appmesh-envoy" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nameproductiontaskdefinitionenvoyLogGroupF79A2732", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameproductiontaskdefinitionExecutionRoleDefaultPolicyB66C67BE", + "Roles": [ + { + "Ref": "nameproductiontaskdefinitionExecutionRoleB72DD86B" + } + ] + } + }, + "nameproductionenvoytoappmesh1B44B04A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appmesh:StreamAggregatedResources", + "Effect": "Allow", + "Resource": { + "Ref": "productionmeshB6E81744" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameproductionenvoytoappmesh1B44B04A", + "Roles": [ + { + "Ref": "nameproductiontaskdefinitionTaskRole93E922EB" + } + ] + } + }, + "nameproductionserviceServiceB2D31516": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "nameproductionserviceSecurityGroupF496E439", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "productionenvironmentvpcPrivateSubnet1Subnet53F632E6" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet2Subnet756FB93C" + }, + { + "Ref": "productionenvironmentvpcPrivateSubnet3Subnet3BD4064E" + } + ] + } + }, + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "nameproductionserviceCloudmapService60E3273A", + "Arn" + ] + } + } + ], + "TaskDefinition": { + "Ref": "nameproductiontaskdefinition33A5D5E0" + } + } + }, + "nameproductionserviceCloudmapService60E3273A": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 10, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 2 + }, + "Name": "name-production", + "NamespaceId": { + "Fn::GetAtt": [ + "productionenvironmentclusterDefaultServiceDiscoveryNamespaceBE74D64D", + "Id" + ] + } + } + }, + "nameproductionserviceSecurityGroupF496E439": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/name-production-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "productionenvironmentvpcAEB47DF7" + } + } + }, + "nameproductionvirtualnodeC78443D9": { + "Type": "AWS::AppMesh::VirtualNode", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "productionmeshB6E81744", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + }, + "ServiceDiscovery": { + "AWSCloudMap": { + "NamespaceName": "production", + "ServiceName": { + "Fn::GetAtt": [ + "nameproductionserviceCloudmapService60E3273A", + "Name" + ] + } + } + } + }, + "VirtualNodeName": "name-production" + } + }, + "nameproductionvirtualrouter00E3366D": { + "Type": "AWS::AppMesh::VirtualRouter", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "productionmeshB6E81744", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ] + }, + "VirtualRouterName": "name-production" + } + }, + "nameproductionvirtualrouternameproductionrouteE886BEFD": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "productionmeshB6E81744", + "MeshName" + ] + }, + "RouteName": "name-production-route", + "Spec": { + "HttpRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "nameproductionvirtualnodeC78443D9", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "nameproductionvirtualrouter00E3366D", + "VirtualRouterName" + ] + } + } + }, + "nameproductionvirtualservice4D49D5F6": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "productionmeshB6E81744", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "nameproductionvirtualrouter00E3366D", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "name-production.production" + } + }, + "namedevelopmenttaskdefinitionTaskRole66A85BBB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "namedevelopmenttaskdefinition0468FBFF": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 1024, + "DependsOn": [ + { + "Condition": "HEALTHY", + "ContainerName": "envoy" + } + ], + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 2048, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + }, + { + "Environment": [ + { + "Name": "APPMESH_VIRTUAL_NODE_NAME", + "Value": { + "Fn::Join": [ + "", + [ + "mesh/", + { + "Fn::GetAtt": [ + "developmentmeshCEC28AFE", + "MeshName" + ] + }, + "/virtualNode/name-development" + ] + ] + } + }, + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "ENABLE_ENVOY_STATS_TAGS", + "Value": "1" + }, + { + "Name": "ENABLE_ENVOY_DOG_STATSD", + "Value": "1" + } + ], + "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE" + ], + "Interval": 5, + "Retries": 3, + "StartPeriod": 10, + "Timeout": 2 + }, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "namedevelopmentenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-appmesh-envoy:v1.15.0.0-prod" + ] + ] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "namedevelopmenttaskdefinitionenvoyLogGroupF8FCAFD6" + }, + "awslogs-stream-prefix": "envoy", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "MemoryReservation": 128, + "Name": "envoy", + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ], + "User": "1337" + } + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "namedevelopmenttaskdefinitionExecutionRole48B53E4E", + "Arn" + ] + }, + "Family": "awsecsintegnamedevelopmenttaskdefinitionC3797E10", + "Memory": "2048", + "NetworkMode": "awsvpc", + "ProxyConfiguration": { + "ContainerName": "envoy", + "ProxyConfigurationProperties": [ + { + "Name": "AppPorts", + "Value": "80" + }, + { + "Name": "ProxyEgressPort", + "Value": "15001" + }, + { + "Name": "ProxyIngressPort", + "Value": "15000" + }, + { + "Name": "IgnoredUID", + "Value": "1337" + }, + { + "Name": "IgnoredGID", + "Value": "1338" + }, + { + "Name": "EgressIgnoredIPs", + "Value": "169.254.170.2,169.254.169.254" + } + ], + "Type": "APPMESH" + }, + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "namedevelopmenttaskdefinitionTaskRole66A85BBB", + "Arn" + ] + } + } + }, + "namedevelopmenttaskdefinitionenvoyLogGroupF8FCAFD6": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "namedevelopmenttaskdefinitionExecutionRole48B53E4E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "namedevelopmenttaskdefinitionExecutionRoleDefaultPolicy34EAA008": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "namedevelopmentenvoyimageaccountmapping", + { + "Ref": "AWS::Region" + }, + "ecrRepo" + ] + }, + ":repository/aws-appmesh-envoy" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "namedevelopmenttaskdefinitionenvoyLogGroupF8FCAFD6", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "namedevelopmenttaskdefinitionExecutionRoleDefaultPolicy34EAA008", + "Roles": [ + { + "Ref": "namedevelopmenttaskdefinitionExecutionRole48B53E4E" + } + ] + } + }, + "namedevelopmentenvoytoappmesh45FF08AA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "appmesh:StreamAggregatedResources", + "Effect": "Allow", + "Resource": { + "Ref": "developmentmeshCEC28AFE" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "namedevelopmentenvoytoappmesh45FF08AA", + "Roles": [ + { + "Ref": "namedevelopmenttaskdefinitionTaskRole66A85BBB" + } + ] + } + }, + "namedevelopmentserviceService8A24F793": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "developmentenvironmentcluster1B64BE8B" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "namedevelopmentserviceSecurityGroupE2ECED97", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "developmentenvironmentvpcPrivateSubnet1SubnetAAECD171" + }, + { + "Ref": "developmentenvironmentvpcPrivateSubnet2Subnet20C1C37D" + }, + { + "Ref": "developmentenvironmentvpcPrivateSubnet3SubnetAE84C610" + } + ] + } + }, + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "namedevelopmentserviceCloudmapService63674467", + "Arn" + ] + } + } + ], + "TaskDefinition": { + "Ref": "namedevelopmenttaskdefinition0468FBFF" + } + } + }, + "namedevelopmentserviceCloudmapService63674467": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 10, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "developmentenvironmentclusterDefaultServiceDiscoveryNamespace0F234767", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 2 + }, + "Name": "name-development", + "NamespaceId": { + "Fn::GetAtt": [ + "developmentenvironmentclusterDefaultServiceDiscoveryNamespace0F234767", + "Id" + ] + } + } + }, + "namedevelopmentserviceSecurityGroupE2ECED97": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/name-development-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "developmentenvironmentvpcCB4216A4" + } + } + }, + "namedevelopmentvirtualnode55118E80": { + "Type": "AWS::AppMesh::VirtualNode", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "developmentmeshCEC28AFE", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + }, + "ServiceDiscovery": { + "AWSCloudMap": { + "NamespaceName": "development", + "ServiceName": { + "Fn::GetAtt": [ + "namedevelopmentserviceCloudmapService63674467", + "Name" + ] + } + } + } + }, + "VirtualNodeName": "name-development" + } + }, + "namedevelopmentvirtualrouter0AE5105D": { + "Type": "AWS::AppMesh::VirtualRouter", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "developmentmeshCEC28AFE", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 80, + "Protocol": "http" + } + } + ] + }, + "VirtualRouterName": "name-development" + } + }, + "namedevelopmentvirtualrouternamedevelopmentroute611E499A": { + "Type": "AWS::AppMesh::Route", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "developmentmeshCEC28AFE", + "MeshName" + ] + }, + "RouteName": "name-development-route", + "Spec": { + "HttpRoute": { + "Action": { + "WeightedTargets": [ + { + "VirtualNode": { + "Fn::GetAtt": [ + "namedevelopmentvirtualnode55118E80", + "VirtualNodeName" + ] + }, + "Weight": 1 + } + ] + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualRouterName": { + "Fn::GetAtt": [ + "namedevelopmentvirtualrouter0AE5105D", + "VirtualRouterName" + ] + } + } + }, + "namedevelopmentvirtualserviceD936E3FD": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "developmentmeshCEC28AFE", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "namedevelopmentvirtualrouter0AE5105D", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "name-development.development" + } + } + }, + "Mappings": { + "nameproductionenvoyimageaccountmapping": { + "ap-northeast-1": { + "ecrRepo": "840364872350" + }, + "ap-northeast-2": { + "ecrRepo": "840364872350" + }, + "ap-south-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-2": { + "ecrRepo": "840364872350" + }, + "ca-central-1": { + "ecrRepo": "840364872350" + }, + "eu-central-1": { + "ecrRepo": "840364872350" + }, + "eu-north-1": { + "ecrRepo": "840364872350" + }, + "eu-south-1": { + "ecrRepo": "840364872350" + }, + "eu-west-1": { + "ecrRepo": "840364872350" + }, + "eu-west-2": { + "ecrRepo": "840364872350" + }, + "eu-west-3": { + "ecrRepo": "840364872350" + }, + "sa-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-2": { + "ecrRepo": "840364872350" + }, + "us-west-1": { + "ecrRepo": "840364872350" + }, + "us-west-2": { + "ecrRepo": "840364872350" + }, + "me-south-1": { + "ecrRepo": "772975370895" + }, + "ap-east-1": { + "ecrRepo": "856666278305" + } + }, + "namedevelopmentenvoyimageaccountmapping": { + "ap-northeast-1": { + "ecrRepo": "840364872350" + }, + "ap-northeast-2": { + "ecrRepo": "840364872350" + }, + "ap-south-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-1": { + "ecrRepo": "840364872350" + }, + "ap-southeast-2": { + "ecrRepo": "840364872350" + }, + "ca-central-1": { + "ecrRepo": "840364872350" + }, + "eu-central-1": { + "ecrRepo": "840364872350" + }, + "eu-north-1": { + "ecrRepo": "840364872350" + }, + "eu-south-1": { + "ecrRepo": "840364872350" + }, + "eu-west-1": { + "ecrRepo": "840364872350" + }, + "eu-west-2": { + "ecrRepo": "840364872350" + }, + "eu-west-3": { + "ecrRepo": "840364872350" + }, + "sa-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-1": { + "ecrRepo": "840364872350" + }, + "us-east-2": { + "ecrRepo": "840364872350" + }, + "us-west-1": { + "ecrRepo": "840364872350" + }, + "us-west-2": { + "ecrRepo": "840364872350" + }, + "me-south-1": { + "ecrRepo": "772975370895" + }, + "ap-east-1": { + "ecrRepo": "856666278305" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.ts new file mode 100644 index 0000000000000..bc5bd3f32e742 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.ts @@ -0,0 +1,54 @@ +import { Mesh } from '@aws-cdk/aws-appmesh'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Container, Environment, Service, ServiceDescription, AppMeshExtension } from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ'); + +const production = new Environment(stack, 'production'); +const development = new Environment(stack, 'development'); + +const productionMesh = new Mesh(stack, 'production-mesh'); +const developmentMesh = new Mesh(stack, 'development-mesh'); + +/** Production name service */ +const productionNameDescription = new ServiceDescription(); +productionNameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +productionNameDescription.add(new AppMeshExtension({ mesh: productionMesh })); + +new Service(stack, 'name-production', { + environment: production, + serviceDescription: productionNameDescription, +}); + +/** Development name service */ +const developmentNameDescription = new ServiceDescription(); +developmentNameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); +developmentNameDescription.add(new AppMeshExtension({ mesh: developmentMesh })); + +new Service(stack, 'name-development', { + environment: development, + serviceDescription: developmentNameDescription, +}); + +/** + * This test verifies the edge case of creating multiple environments + * on the same account to ensure that there are no conflicts. + */ \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.appmesh.ts new file mode 100644 index 0000000000000..3c34c99ff8f18 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.appmesh.ts @@ -0,0 +1,591 @@ +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import * as appmesh from '@aws-cdk/aws-appmesh'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; + +import { Test } from 'nodeunit'; +import { AppMeshExtension, Container, Environment, ScaleOnCpuUtilization, ServiceDescription, Service } from '../lib'; + +export = { + 'should be able to add AWS App Mesh to a service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + + // Ensure that task has an App Mesh sidecar + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + DependsOn: [ + { + Condition: 'HEALTHY', + ContainerName: 'envoy', + }, + ], + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + { + Environment: [ + { + Name: 'APPMESH_VIRTUAL_NODE_NAME', + Value: { + 'Fn::Join': [ + '', + [ + 'mesh/', + { + 'Fn::GetAtt': [ + 'mymeshEA67EDEF', + 'MeshName', + ], + }, + '/virtualNode/my-service', + ], + ], + }, + }, + { + Name: 'AWS_REGION', + Value: { + Ref: 'AWS::Region', + }, + }, + { + Name: 'ENABLE_ENVOY_STATS_TAGS', + Value: '1', + }, + { + Name: 'ENABLE_ENVOY_DOG_STATSD', + Value: '1', + }, + ], + Essential: true, + HealthCheck: { + Command: [ + 'CMD-SHELL', + 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE', + ], + Interval: 5, + Retries: 3, + StartPeriod: 10, + Timeout: 2, + }, + Image: { + 'Fn::Join': [ + '', + [ + { + 'Fn::FindInMap': [ + 'myserviceenvoyimageaccountmapping', + { + Ref: 'AWS::Region', + }, + 'ecrRepo', + ], + }, + '.dkr.ecr.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + '/aws-appmesh-envoy:v1.15.0.0-prod', + ], + ], + }, + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'myservicetaskdefinitionenvoyLogGroup0C27EBDB', + }, + 'awslogs-stream-prefix': 'envoy', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, + }, + MemoryReservation: 128, + Name: 'envoy', + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + User: '1337', + }, + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionExecutionRole0CE74AD0', + 'Arn', + ], + }, + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + ProxyConfiguration: { + ContainerName: 'envoy', + ProxyConfigurationProperties: [ + { + Name: 'AppPorts', + Value: '80', + }, + { + Name: 'ProxyEgressPort', + Value: '15001', + }, + { + Name: 'ProxyIngressPort', + Value: '15000', + }, + { + Name: 'IgnoredUID', + Value: '1337', + }, + { + Name: 'IgnoredGID', + Value: '1338', + }, + { + Name: 'EgressIgnoredIPs', + Value: '169.254.170.2,169.254.169.254', + }, + ], + Type: 'APPMESH', + }, + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + // Ensure that the service has the right settings + expect(stack).to(haveResource('AWS::ECS::Service', { + Cluster: { + Ref: 'productionenvironmentclusterC6599D2D', + }, + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 100, + }, + DesiredCount: 1, + EnableECSManagedTags: false, + LaunchType: 'FARGATE', + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'myserviceserviceSecurityGroup3A44A969', + 'GroupId', + ], + }, + ], + Subnets: [ + { + Ref: 'productionenvironmentvpcPrivateSubnet1Subnet53F632E6', + }, + { + Ref: 'productionenvironmentvpcPrivateSubnet2Subnet756FB93C', + }, + ], + }, + }, + ServiceRegistries: [ + { + RegistryArn: { + 'Fn::GetAtt': [ + 'myserviceserviceCloudmapService32F63163', + 'Arn', + ], + }, + }, + ], + TaskDefinition: { + Ref: 'myservicetaskdefinitionF3E2D86F', + }, + })); + + test.done(); + }, + + 'should have the right maximumPercentage at desired count == 1'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 1, + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 100, + }, + DesiredCount: 1, + })); + + test.done(); + }, + + 'should have the right maximumPercentage at desired count == 2'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 2, + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 150, + MinimumHealthyPercent: 100, + }, + DesiredCount: 2, + })); + + test.done(); + }, + + 'should have the right maximumPercentage at desired count == 3'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 3, + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 150, + MinimumHealthyPercent: 100, + }, + DesiredCount: 3, + })); + + test.done(); + }, + + 'should have the right maximumPercentage at desired count == 4'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 4, + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 125, + MinimumHealthyPercent: 100, + }, + DesiredCount: 4, + })); + + test.done(); + }, + + 'should have the right maximumPercentage at desired count > 4'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 8, + })); + + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + + serviceDescription.add(new AppMeshExtension({ + mesh, + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 125, + MinimumHealthyPercent: 100, + }, + DesiredCount: 8, + })); + + test.done(); + }, + + 'should be able to create multiple App Mesh enabled services and connect'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'my-mesh'); + const environment = new Environment(stack, 'production'); + + const nameDescription = new ServiceDescription(); + nameDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, + })); + nameDescription.add(new AppMeshExtension({ mesh })); + + const greetingDescription = new ServiceDescription(); + greetingDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/greeting'), + environment: { + PORT: '80', + }, + })); + greetingDescription.add(new AppMeshExtension({ mesh })); + + const greeterDescription = new ServiceDescription(); + greeterDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/greeter'), + environment: { + PORT: '80', + }, + })); + greeterDescription.add(new AppMeshExtension({ mesh })); + + const greeterService = new Service(stack, 'greeter', { + environment, + serviceDescription: greeterDescription, + }); + const greetingService = new Service(stack, 'greeting', { + environment, + serviceDescription: greetingDescription, + }); + const nameService = new Service(stack, 'name', { + environment, + serviceDescription: nameDescription, + }); + + greeterService.connectTo(nameService); + greeterService.connectTo(greetingService); + + // THEN + expect(stack).to(haveResource('AWS::ECS::TaskDefinition')); + + test.done(); + }, + + 'should detect when attempting to connect services from two different envs'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const production = new Environment(stack, 'production'); + const development = new Environment(stack, 'development'); + + const productionMesh = new appmesh.Mesh(stack, 'production-mesh'); + const developmentMesh = new appmesh.Mesh(stack, 'development-mesh'); + + /** Production name service */ + const productionNameDescription = new ServiceDescription(); + productionNameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, + })); + productionNameDescription.add(new AppMeshExtension({ mesh: productionMesh })); + + const productionNameService = new Service(stack, 'name-production', { + environment: production, + serviceDescription: productionNameDescription, + }); + + /** Development name service */ + const developmentNameDescription = new ServiceDescription(); + developmentNameDescription.add(new Container({ + cpu: 1024, + memoryMiB: 2048, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, + })); + developmentNameDescription.add(new AppMeshExtension({ mesh: developmentMesh })); + + const developmentNameService = new Service(stack, 'name-development', { + environment: development, + serviceDescription: developmentNameDescription, + }); + + // THEN + test.throws(() => { + developmentNameService.connectTo(productionNameService); + }, /Unable to connect service 'name-development' in environment 'development' to service 'name-production' in environment 'production' because services can not be connected across environment boundaries/); + + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.cloudwatch-agent.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.cloudwatch-agent.ts new file mode 100644 index 0000000000000..a8d94c3dc8e25 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.cloudwatch-agent.ts @@ -0,0 +1,111 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { CloudwatchAgentExtension, Container, Environment, Service, ServiceDescription } from '../lib'; + +export = { + 'should be able to add AWS X-Ray to a service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new CloudwatchAgentExtension()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + DependsOn: [ + { + Condition: 'START', + ContainerName: 'cloudwatch-agent', + }, + ], + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + { + Environment: [ + { + Name: 'CW_CONFIG_CONTENT', + Value: '{"logs":{"metrics_collected":{"emf":{}}},"metrics":{"metrics_collected":{"statsd":{}}}}', + }, + ], + Essential: true, + Image: 'amazon/cloudwatch-agent:latest', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'myservicetaskdefinitioncloudwatchagentLogGroupDF0CD679', + }, + 'awslogs-stream-prefix': 'cloudwatch-agent', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, + }, + MemoryReservation: 50, + Name: 'cloudwatch-agent', + User: '0:1338', + }, + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionExecutionRole0CE74AD0', + 'Arn', + ], + }, + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts new file mode 100644 index 0000000000000..cb19e71de1d82 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -0,0 +1,220 @@ +import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, Environment, EnvironmentCapacityType, Service, ServiceDescription } from '../lib'; + +export = { + 'should be able to add a service to an environment'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(countResources('AWS::ECS::Service', 1)); + + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + + 'should be able to create a Fargate environment with a given VPC and cluster'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const environment = new Environment(stack, 'production', { + vpc, + cluster, + }); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(countResources('AWS::ECS::Service', 1)); + + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + + 'should be able to create an environment for EC2'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + const environment = new Environment(stack, 'production', { + vpc, + cluster, + capacityType: EnvironmentCapacityType.EC2, + }); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(countResources('AWS::ECS::Service', 1)); + + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.firelens.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.firelens.ts new file mode 100644 index 0000000000000..e4ceb68444dd7 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.firelens.ts @@ -0,0 +1,122 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, Environment, FireLensExtension, Service, ServiceDescription } from '../lib'; + +export = { + 'should be able to add Firelens to a service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new FireLensExtension()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + + // Ensure that the log group was created + expect(stack).to(haveResource('AWS::Logs::LogGroup')); + + // Ensure that task has a Firelens sidecar and a log configuration + // pointing at the sidecar + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + DependsOn: [ + { + Condition: 'START', + ContainerName: 'firelens', + }, + ], + Essential: true, + Image: 'nathanpeck/name', + LogConfiguration: { + LogDriver: 'awsfirelens', + Options: { + Name: 'cloudwatch', + region: { + Ref: 'AWS::Region', + }, + log_group_name: { + Ref: 'myservicelogs176EE19F', + }, + log_stream_prefix: 'my-service/', + }, + }, + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + { + Essential: true, + FirelensConfiguration: { + Type: 'fluentbit', + }, + Image: { + Ref: 'SsmParameterValueawsserviceawsforfluentbitlatestC96584B6F00A464EAD1953AFF4B05118Parameter', + }, + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'myservicetaskdefinitionfirelensLogGroup0D59B0EB', + }, + 'awslogs-stream-prefix': 'firelens', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, + }, + MemoryReservation: 50, + Name: 'firelens', + User: '0:1338', + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.http-load-balancer.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.http-load-balancer.ts new file mode 100644 index 0000000000000..2cc26be97ddcf --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.http-load-balancer.ts @@ -0,0 +1,76 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, Environment, HttpLoadBalancerExtension, Service, ServiceDescription } from '../lib'; + +export = { + 'should be able to add an HTTP load balancer to a service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new HttpLoadBalancerExtension()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer')); + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener')); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.scale-on-cpu-utilization.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.scale-on-cpu-utilization.ts new file mode 100644 index 0000000000000..305655129eb1a --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.scale-on-cpu-utilization.ts @@ -0,0 +1,153 @@ +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, Environment, ScaleOnCpuUtilization, Service, ServiceDescription } from '../lib'; + +export = { + 'scale on cpu utilization extension with no parameters should create a default autoscaling setup'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new ScaleOnCpuUtilization()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 100, + }, + DesiredCount: 2, + })); + + expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 8, + MinCapacity: 2, + ResourceId: { + 'Fn::Join': [ + '', + [ + 'service/', + { + Ref: 'productionenvironmentclusterC6599D2D', + }, + '/', + { + 'Fn::GetAtt': [ + 'myserviceserviceServiceE9A5732D', + 'Name', + ], + }, + ], + ], + }, + RoleARN: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService', + ], + ], + }, + ScalableDimension: 'ecs:service:DesiredCount', + ServiceNamespace: 'ecs', + })); + + expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyName: 'myserviceserviceTaskCountTargetmyservicetargetcpuutilization50E6628660', + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { + Ref: 'myserviceserviceTaskCountTarget4268918D', + }, + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { + PredefinedMetricType: 'ECSServiceAverageCPUUtilization', + }, + ScaleInCooldown: 60, + ScaleOutCooldown: 60, + TargetValue: 50, + }, + })); + + test.done(); + }, + + 'should be able to set a custom scaling policy as well'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new ScaleOnCpuUtilization({ + initialTaskCount: 25, + minTaskCount: 15, + maxTaskCount: 30, + targetCpuUtilization: 75, + scaleInCooldown: cdk.Duration.minutes(3), + scaleOutCooldown: cdk.Duration.minutes(3), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 100, + }, + DesiredCount: 25, + })); + + expect(stack).to(haveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 30, + MinCapacity: 15, + })); + + expect(stack).to(haveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + TargetTrackingScalingPolicyConfiguration: { + ScaleInCooldown: 180, + ScaleOutCooldown: 180, + TargetValue: 75, + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.service.ts new file mode 100644 index 0000000000000..23b30f59afe3d --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.service.ts @@ -0,0 +1,101 @@ +import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, EnvironmentCapacityType, Environment, Service, ServiceDescription } from '../lib'; + +export = { + 'should error if a service is prepared with no addons'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + // THEN + test.throws(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + }, /Service 'my-service' must have a Container extension/); + + test.done(); + }, + + 'should be able to add a container to the service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + const environment = new Environment(stack, 'production', { + vpc, + cluster, + capacityType: EnvironmentCapacityType.EC2, + }); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(countResources('AWS::ECS::Service', 1)); + + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + ], + Cpu: '256', + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.xray.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.xray.ts new file mode 100644 index 0000000000000..36443123e8719 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.xray.ts @@ -0,0 +1,122 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Container, Environment, XRayExtension, Service, ServiceDescription } from '../lib'; + +export = { + 'should be able to add AWS X-Ray to a service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new XRayExtension()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Cpu: 256, + DependsOn: [ + { + Condition: 'HEALTHY', + ContainerName: 'xray', + }, + ], + Essential: true, + Image: 'nathanpeck/name', + Memory: 512, + Name: 'app', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + Ulimits: [ + { + HardLimit: 1024000, + Name: 'nofile', + SoftLimit: 1024000, + }, + ], + }, + { + Environment: [ + { + Name: 'AWS_REGION', + Value: { + Ref: 'AWS::Region', + }, + }, + ], + Essential: true, + HealthCheck: { + Command: [ + 'CMD-SHELL', + 'curl -s http://localhost:2000', + ], + Interval: 5, + Retries: 3, + StartPeriod: 10, + Timeout: 2, + }, + Image: 'amazon/aws-xray-daemon:latest', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'myservicetaskdefinitionxrayLogGroupC0252525', + }, + 'awslogs-stream-prefix': 'xray', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, + }, + MemoryReservation: 256, + Name: 'xray', + User: '1337', + }, + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionExecutionRole0CE74AD0', + 'Arn', + ], + }, + Family: 'myservicetaskdefinition', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'EC2', + 'FARGATE', + ], + TaskRoleArn: { + 'Fn::GetAtt': [ + 'myservicetaskdefinitionTaskRole92ACD903', + 'Arn', + ], + }, + })); + + test.done(); + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/tsconfig.json b/packages/@aws-cdk-containers/ecs-service-extensions/tsconfig.json new file mode 100644 index 0000000000000..b886d9b504084 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/tsconfig.json @@ -0,0 +1,108 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2018" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018", + "composite": true, + "incremental": true, + "tsBuildInfoFile": "./tsconfig.tsbuildinfo" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "../../@aws-cdk/aws-applicationautoscaling" + }, + { + "path": "../../@aws-cdk/aws-appmesh" + }, + { + "path": "../../@aws-cdk/aws-certificatemanager" + }, + { + "path": "../../@aws-cdk/aws-dynamodb" + }, + { + "path": "../../@aws-cdk/aws-ec2" + }, + { + "path": "../../@aws-cdk/aws-ecr" + }, + { + "path": "../../@aws-cdk/aws-ecs" + }, + { + "path": "../../@aws-cdk/aws-elasticloadbalancingv2" + }, + { + "path": "../../@aws-cdk/aws-events" + }, + { + "path": "../../@aws-cdk/aws-events-targets" + }, + { + "path": "../../@aws-cdk/aws-iam" + }, + { + "path": "../../@aws-cdk/aws-logs" + }, + { + "path": "../../@aws-cdk/aws-route53" + }, + { + "path": "../../@aws-cdk/aws-route53-targets" + }, + { + "path": "../../@aws-cdk/aws-servicediscovery" + }, + { + "path": "../../@aws-cdk/aws-sqs" + }, + { + "path": "../../@aws-cdk/core" + }, + { + "path": "../../@aws-cdk/region-info" + }, + { + "path": "../../@aws-cdk/assert" + }, + { + "path": "../../../tools/cdk-build-tools" + }, + { + "path": "../../../tools/cdk-integ-tools" + }, + { + "path": "../../../tools/cfn2ts" + }, + { + "path": "../../../tools/pkglint" + } + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} 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 7b474c6042723..16124517c2d5e 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -133,3 +133,28 @@ export const DLC_REPOSITORY_ACCOUNTS: { [region: string]: string } = { 'cn-north-1': '727897471807', 'cn-northwest-1': '727897471807', }; + +// https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html +export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { + 'ap-northeast-1': '840364872350', + 'ap-northeast-2': '840364872350', + 'ap-south-1': '840364872350', + 'ap-southeast-1': '840364872350', + 'ap-southeast-2': '840364872350', + 'ca-central-1': '840364872350', + 'eu-central-1': '840364872350', + 'eu-north-1': '840364872350', + 'eu-south-1': '840364872350', + 'eu-west-1': '840364872350', + 'eu-west-2': '840364872350', + 'eu-west-3': '840364872350', + 'sa-east-1': '840364872350', + 'us-east-1': '840364872350', + 'us-east-2': '840364872350', + 'us-west-1': '840364872350', + 'us-west-2': '840364872350', + + 'me-south-1': '772975370895', + 'ap-east-1': '856666278305', + +}; diff --git a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts index bc042279d8e50..08a9f79a72b2b 100644 --- a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts +++ b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts @@ -3,7 +3,7 @@ import * as fs from 'fs-extra'; import { Default } from '../lib/default'; import { AWS_REGIONS, AWS_SERVICES } from './aws-entities'; import { - AWS_CDK_METADATA, AWS_OLDER_REGIONS, DLC_REPOSITORY_ACCOUNTS, ELBV2_ACCOUNTS, PARTITION_MAP, + APPMESH_ECR_ACCOUNTS, AWS_CDK_METADATA, AWS_OLDER_REGIONS, DLC_REPOSITORY_ACCOUNTS, ELBV2_ACCOUNTS, PARTITION_MAP, ROUTE_53_BUCKET_WEBSITE_ZONE_IDS, } from './fact-tables'; @@ -54,6 +54,8 @@ async function main(): Promise { registerFact(region, 'DLC_REPOSITORY_ACCOUNT', DLC_REPOSITORY_ACCOUNTS[region]); + registerFact(region, 'APPMESH_ECR_ACCOUNT', APPMESH_ECR_ACCOUNTS[region]); + const vpcEndpointServiceNamePrefix = `${domainSuffix.split('.').reverse().join('.')}.vpce`; registerFact(region, 'VPC_ENDPOINT_SERVICE_NAME_PREFIX', vpcEndpointServiceNamePrefix); diff --git a/packages/@aws-cdk/region-info/lib/fact.ts b/packages/@aws-cdk/region-info/lib/fact.ts index 79033f9c9adb9..3b5e57835cc7e 100644 --- a/packages/@aws-cdk/region-info/lib/fact.ts +++ b/packages/@aws-cdk/region-info/lib/fact.ts @@ -146,6 +146,12 @@ export class FactName { */ public static readonly DLC_REPOSITORY_ACCOUNT = 'dlcRepositoryAccount'; + /** + * The ID of the AWS account that owns the public ECR repository that contains the + * AWS App Mesh Envoy Proxy images in a given region. + */ + public static readonly APPMESH_ECR_ACCOUNT = 'appMeshRepositoryAccount'; + /** * The name of the regional service principal for a given service. * diff --git a/packages/@aws-cdk/region-info/lib/region-info.ts b/packages/@aws-cdk/region-info/lib/region-info.ts index 3402cb8fd657f..042b3cec9c177 100644 --- a/packages/@aws-cdk/region-info/lib/region-info.ts +++ b/packages/@aws-cdk/region-info/lib/region-info.ts @@ -109,4 +109,12 @@ export class RegionInfo { public get dlcRepositoryAccount(): string | undefined { return Fact.find(this.name, FactName.DLC_REPOSITORY_ACCOUNT); } + + /** + * The ID of the AWS account that owns the public ECR repository that contains the + * AWS App Mesh Envoy Proxy images in a given region. + */ + public get appMeshRepositoryAccount(): string | undefined { + return Fact.find(this.name, FactName.APPMESH_ECR_ACCOUNT); + } } diff --git a/yarn.lock b/yarn.lock index 074fb6bae06ee..e128716a844cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,6 +1084,17 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@jest/console@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.5.0.tgz#770800799d510f37329c508a9edd0b7b447d9abb" + integrity sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw== + dependencies: + "@jest/types" "^25.5.0" + chalk "^3.0.0" + jest-message-util "^25.5.0" + jest-util "^25.5.0" + slash "^3.0.0" + "@jest/console@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856" @@ -1096,6 +1107,40 @@ jest-util "^26.3.0" slash "^3.0.0" +"@jest/core@^25.5.4": + version "25.5.4" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.5.4.tgz#3ef7412f7339210f003cdf36646bbca786efe7b4" + integrity sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA== + dependencies: + "@jest/console" "^25.5.0" + "@jest/reporters" "^25.5.1" + "@jest/test-result" "^25.5.0" + "@jest/transform" "^25.5.1" + "@jest/types" "^25.5.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^25.5.0" + jest-config "^25.5.4" + jest-haste-map "^25.5.1" + jest-message-util "^25.5.0" + jest-regex-util "^25.2.6" + jest-resolve "^25.5.1" + jest-resolve-dependencies "^25.5.4" + jest-runner "^25.5.4" + jest-runtime "^25.5.4" + jest-snapshot "^25.5.1" + jest-util "^25.5.0" + jest-validate "^25.5.0" + jest-watcher "^25.5.0" + micromatch "^4.0.2" + p-each-series "^2.1.0" + realpath-native "^2.0.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/core@^26.4.2": version "26.4.2" resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc" @@ -1130,6 +1175,15 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/environment@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.5.0.tgz#aa33b0c21a716c65686638e7ef816c0e3a0c7b37" + integrity sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA== + dependencies: + "@jest/fake-timers" "^25.5.0" + "@jest/types" "^25.5.0" + jest-mock "^25.5.0" + "@jest/environment@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0" @@ -1140,6 +1194,17 @@ "@types/node" "*" jest-mock "^26.3.0" +"@jest/fake-timers@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" + integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== + dependencies: + "@jest/types" "^25.5.0" + jest-message-util "^25.5.0" + jest-mock "^25.5.0" + jest-util "^25.5.0" + lolex "^5.0.0" + "@jest/fake-timers@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a" @@ -1152,6 +1217,15 @@ jest-mock "^26.3.0" jest-util "^26.3.0" +"@jest/globals@^25.5.2": + version "25.5.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-25.5.2.tgz#5e45e9de8d228716af3257eeb3991cc2e162ca88" + integrity sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA== + dependencies: + "@jest/environment" "^25.5.0" + "@jest/types" "^25.5.0" + expect "^25.5.0" + "@jest/globals@^26.4.2": version "26.4.2" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a" @@ -1161,6 +1235,38 @@ "@jest/types" "^26.3.0" expect "^26.4.2" +"@jest/reporters@^25.5.1": + version "25.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.5.1.tgz#cb686bcc680f664c2dbaf7ed873e93aa6811538b" + integrity sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^25.5.0" + "@jest/test-result" "^25.5.0" + "@jest/transform" "^25.5.1" + "@jest/types" "^25.5.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^25.5.1" + jest-resolve "^25.5.1" + jest-util "^25.5.0" + jest-worker "^25.5.0" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^3.1.0" + terminal-link "^2.0.0" + v8-to-istanbul "^4.1.3" + optionalDependencies: + node-notifier "^6.0.0" + "@jest/reporters@^26.4.1": version "26.4.1" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795" @@ -1193,6 +1299,15 @@ optionalDependencies: node-notifier "^8.0.0" +"@jest/source-map@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.5.0.tgz#df5c20d6050aa292c2c6d3f0d2c7606af315bd1b" + integrity sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + "@jest/source-map@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9" @@ -1202,6 +1317,16 @@ graceful-fs "^4.2.4" source-map "^0.6.0" +"@jest/test-result@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.5.0.tgz#139a043230cdeffe9ba2d8341b27f2efc77ce87c" + integrity sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A== + dependencies: + "@jest/console" "^25.5.0" + "@jest/types" "^25.5.0" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-result@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb" @@ -1212,6 +1337,17 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-sequencer@^25.5.4": + version "25.5.4" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz#9b4e685b36954c38d0f052e596d28161bdc8b737" + integrity sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA== + dependencies: + "@jest/test-result" "^25.5.0" + graceful-fs "^4.2.4" + jest-haste-map "^25.5.1" + jest-runner "^25.5.4" + jest-runtime "^25.5.4" + "@jest/test-sequencer@^26.4.2": version "26.4.2" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba" @@ -1223,6 +1359,28 @@ jest-runner "^26.4.2" jest-runtime "^26.4.2" +"@jest/transform@^25.5.1": + version "25.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.5.1.tgz#0469ddc17699dd2bf985db55fa0fb9309f5c2db3" + integrity sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^25.5.0" + babel-plugin-istanbul "^6.0.0" + chalk "^3.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^25.5.1" + jest-regex-util "^25.2.6" + jest-util "^25.5.0" + micromatch "^4.0.2" + pirates "^4.0.1" + realpath-native "^2.0.0" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + "@jest/transform@^26.3.0": version "26.3.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55" @@ -2993,7 +3151,7 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" -"@types/jest@^26.0.14": +"@types/jest@^26.0.10", "@types/jest@^26.0.14": version "26.0.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== @@ -3082,6 +3240,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + "@types/prettier@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.2.tgz#5bb52ee68d0f8efa9cc0099920e56be6cc4e37f3" @@ -3286,7 +3449,7 @@ abortcontroller-polyfill@^1.1.9: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz#2c562f530869abbcf88d949a2b60d1d402e87a7c" integrity sha512-O6Xk757Jb4o0LMzMOMdWvxpHWrQzruYBaUruFaIOfAQRnWFxfdXYobw12jrVHGtoXk6WiiyYzc0QWN9aL62HQA== -acorn-globals@^4.3.0: +acorn-globals@^4.3.0, acorn-globals@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== @@ -3322,7 +3485,7 @@ acorn@^6.0.1, acorn@^6.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== @@ -3788,6 +3951,20 @@ axios@^0.19.0: dependencies: follow-redirects "1.5.10" +babel-jest@^25.5.1: + version "25.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853" + integrity sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ== + dependencies: + "@jest/transform" "^25.5.1" + "@jest/types" "^25.5.0" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^25.5.0" + chalk "^3.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + babel-jest@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463" @@ -3820,6 +3997,15 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" +babel-plugin-jest-hoist@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz#129c80ba5c7fc75baf3a45b93e2e372d57ca2677" + integrity sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__traverse" "^7.0.6" + babel-plugin-jest-hoist@^26.2.0: version "26.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd" @@ -3830,7 +4016,7 @@ babel-plugin-jest-hoist@^26.2.0: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-preset-current-node-syntax@^0.1.3: +babel-preset-current-node-syntax@^0.1.2, babel-preset-current-node-syntax@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da" integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ== @@ -3847,6 +4033,14 @@ babel-preset-current-node-syntax@^0.1.3: "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +babel-preset-jest@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz#c1d7f191829487a907764c65307faa0e66590b49" + integrity sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw== + dependencies: + babel-plugin-jest-hoist "^25.5.0" + babel-preset-current-node-syntax "^0.1.2" + babel-preset-jest@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776" @@ -3965,6 +4159,13 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -5243,7 +5444,7 @@ cssom@0.3.x, cssom@^0.3.4, cssom@~0.3.6: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssom@^0.4.4: +cssom@^0.4.1, cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== @@ -5255,7 +5456,7 @@ cssstyle@^1.1.1: dependencies: cssom "0.3.x" -cssstyle@^2.2.0: +cssstyle@^2.0.0, cssstyle@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== @@ -5950,7 +6151,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@1.x.x, escodegen@^1.11.0, escodegen@^1.14.1: +escodegen@1.x.x, escodegen@^1.11.0, escodegen@^1.11.1, escodegen@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -6230,6 +6431,22 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^4.0.0, execa@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" @@ -6263,6 +6480,18 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expect@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-25.5.0.tgz#f07f848712a2813bb59167da3fb828ca21f58bba" + integrity sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA== + dependencies: + "@jest/types" "^25.5.0" + ansi-styles "^4.0.0" + jest-get-type "^25.2.6" + jest-matcher-utils "^25.5.0" + jest-message-util "^25.5.0" + jest-regex-util "^25.2.6" + expect@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1" @@ -8022,6 +8251,15 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jest-changed-files@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c" + integrity sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw== + dependencies: + "@jest/types" "^25.5.0" + execa "^3.2.0" + throat "^5.0.0" + jest-changed-files@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1" @@ -8031,6 +8269,26 @@ jest-changed-files@^26.3.0: execa "^4.0.0" throat "^5.0.0" +jest-cli@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.5.4.tgz#b9f1a84d1301a92c5c217684cb79840831db9f0d" + integrity sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw== + dependencies: + "@jest/core" "^25.5.4" + "@jest/test-result" "^25.5.0" + "@jest/types" "^25.5.0" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^25.5.4" + jest-util "^25.5.0" + jest-validate "^25.5.0" + prompts "^2.0.1" + realpath-native "^2.0.0" + yargs "^15.3.1" + jest-cli@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da" @@ -8050,6 +8308,31 @@ jest-cli@^26.4.2: prompts "^2.0.1" yargs "^15.3.1" +jest-config@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.5.4.tgz#38e2057b3f976ef7309b2b2c8dcd2a708a67f02c" + integrity sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^25.5.4" + "@jest/types" "^25.5.0" + babel-jest "^25.5.1" + chalk "^3.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^25.5.0" + jest-environment-node "^25.5.0" + jest-get-type "^25.2.6" + jest-jasmine2 "^25.5.4" + jest-regex-util "^25.2.6" + jest-resolve "^25.5.1" + jest-util "^25.5.0" + jest-validate "^25.5.0" + micromatch "^4.0.2" + pretty-format "^25.5.0" + realpath-native "^2.0.0" + jest-config@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558" @@ -8074,7 +8357,7 @@ jest-config@^26.4.2: micromatch "^4.0.2" pretty-format "^26.4.2" -jest-diff@^25.2.1: +jest-diff@^25.2.1, jest-diff@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== @@ -8094,6 +8377,13 @@ jest-diff@^26.4.2: jest-get-type "^26.3.0" pretty-format "^26.4.2" +jest-docblock@^25.3.0: + version "25.3.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef" + integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg== + dependencies: + detect-newline "^3.0.0" + jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -8101,6 +8391,17 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" +jest-each@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.5.0.tgz#0c3c2797e8225cb7bec7e4d249dcd96b934be516" + integrity sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA== + dependencies: + "@jest/types" "^25.5.0" + chalk "^3.0.0" + jest-get-type "^25.2.6" + jest-util "^25.5.0" + pretty-format "^25.5.0" + jest-each@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae" @@ -8112,6 +8413,18 @@ jest-each@^26.4.2: jest-util "^26.3.0" pretty-format "^26.4.2" +jest-environment-jsdom@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz#dcbe4da2ea997707997040ecf6e2560aec4e9834" + integrity sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A== + dependencies: + "@jest/environment" "^25.5.0" + "@jest/fake-timers" "^25.5.0" + "@jest/types" "^25.5.0" + jest-mock "^25.5.0" + jest-util "^25.5.0" + jsdom "^15.2.1" + jest-environment-jsdom@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c" @@ -8125,6 +8438,18 @@ jest-environment-jsdom@^26.3.0: jest-util "^26.3.0" jsdom "^16.2.2" +jest-environment-node@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.5.0.tgz#0f55270d94804902988e64adca37c6ce0f7d07a1" + integrity sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA== + dependencies: + "@jest/environment" "^25.5.0" + "@jest/fake-timers" "^25.5.0" + "@jest/types" "^25.5.0" + jest-mock "^25.5.0" + jest-util "^25.5.0" + semver "^6.3.0" + jest-environment-node@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849" @@ -8147,6 +8472,26 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== +jest-haste-map@^25.5.1: + version "25.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943" + integrity sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ== + dependencies: + "@jest/types" "^25.5.0" + "@types/graceful-fs" "^4.1.2" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-serializer "^25.5.0" + jest-util "^25.5.0" + jest-worker "^25.5.0" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + which "^2.0.2" + optionalDependencies: + fsevents "^2.1.2" + jest-haste-map@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726" @@ -8168,6 +8513,29 @@ jest-haste-map@^26.3.0: optionalDependencies: fsevents "^2.1.2" +jest-jasmine2@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz#66ca8b328fb1a3c5364816f8958f6970a8526968" + integrity sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^25.5.0" + "@jest/source-map" "^25.5.0" + "@jest/test-result" "^25.5.0" + "@jest/types" "^25.5.0" + chalk "^3.0.0" + co "^4.6.0" + expect "^25.5.0" + is-generator-fn "^2.0.0" + jest-each "^25.5.0" + jest-matcher-utils "^25.5.0" + jest-message-util "^25.5.0" + jest-runtime "^25.5.4" + jest-snapshot "^25.5.1" + jest-util "^25.5.0" + pretty-format "^25.5.0" + throat "^5.0.0" + jest-jasmine2@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257" @@ -8202,6 +8570,14 @@ jest-junit@^11.1.0: uuid "^3.3.3" xml "^1.0.1" +jest-leak-detector@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb" + integrity sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA== + dependencies: + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + jest-leak-detector@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f" @@ -8210,6 +8586,16 @@ jest-leak-detector@^26.4.2: jest-get-type "^26.3.0" pretty-format "^26.4.2" +jest-matcher-utils@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867" + integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw== + dependencies: + chalk "^3.0.0" + jest-diff "^25.5.0" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + jest-matcher-utils@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06" @@ -8220,6 +8606,20 @@ jest-matcher-utils@^26.4.2: jest-get-type "^26.3.0" pretty-format "^26.4.2" +jest-message-util@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" + integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^25.5.0" + "@types/stack-utils" "^1.0.1" + chalk "^3.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + slash "^3.0.0" + stack-utils "^1.0.1" + jest-message-util@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a" @@ -8234,6 +8634,13 @@ jest-message-util@^26.3.0: slash "^3.0.0" stack-utils "^2.0.2" +jest-mock@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" + integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== + dependencies: + "@jest/types" "^25.5.0" + jest-mock@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba" @@ -8242,16 +8649,30 @@ jest-mock@^26.3.0: "@jest/types" "^26.3.0" "@types/node" "*" -jest-pnp-resolver@^1.2.2: +jest-pnp-resolver@^1.2.1, jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" + integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw== + jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== +jest-resolve-dependencies@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz#85501f53957c8e3be446e863a74777b5a17397a7" + integrity sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw== + dependencies: + "@jest/types" "^25.5.0" + jest-regex-util "^25.2.6" + jest-snapshot "^25.5.1" + jest-resolve-dependencies@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5" @@ -8261,6 +8682,21 @@ jest-resolve-dependencies@^26.4.2: jest-regex-util "^26.0.0" jest-snapshot "^26.4.2" +jest-resolve@^25.5.1: + version "25.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.5.1.tgz#0e6fbcfa7c26d2a5fe8f456088dc332a79266829" + integrity sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ== + dependencies: + "@jest/types" "^25.5.0" + browser-resolve "^1.11.3" + chalk "^3.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.1" + read-pkg-up "^7.0.1" + realpath-native "^2.0.0" + resolve "^1.17.0" + slash "^3.0.0" + jest-resolve@^26.4.0: version "26.4.0" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7" @@ -8275,6 +8711,31 @@ jest-resolve@^26.4.0: resolve "^1.17.0" slash "^3.0.0" +jest-runner@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.5.4.tgz#ffec5df3875da5f5c878ae6d0a17b8e4ecd7c71d" + integrity sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg== + dependencies: + "@jest/console" "^25.5.0" + "@jest/environment" "^25.5.0" + "@jest/test-result" "^25.5.0" + "@jest/types" "^25.5.0" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^25.5.4" + jest-docblock "^25.3.0" + jest-haste-map "^25.5.1" + jest-jasmine2 "^25.5.4" + jest-leak-detector "^25.5.0" + jest-message-util "^25.5.0" + jest-resolve "^25.5.1" + jest-runtime "^25.5.4" + jest-util "^25.5.0" + jest-worker "^25.5.0" + source-map-support "^0.5.6" + throat "^5.0.0" + jest-runner@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853" @@ -8301,6 +8762,38 @@ jest-runner@^26.4.2: source-map-support "^0.5.6" throat "^5.0.0" +jest-runtime@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.5.4.tgz#dc981fe2cb2137abcd319e74ccae7f7eeffbfaab" + integrity sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ== + dependencies: + "@jest/console" "^25.5.0" + "@jest/environment" "^25.5.0" + "@jest/globals" "^25.5.2" + "@jest/source-map" "^25.5.0" + "@jest/test-result" "^25.5.0" + "@jest/transform" "^25.5.1" + "@jest/types" "^25.5.0" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^25.5.4" + jest-haste-map "^25.5.1" + jest-message-util "^25.5.0" + jest-mock "^25.5.0" + jest-regex-util "^25.2.6" + jest-resolve "^25.5.1" + jest-snapshot "^25.5.1" + jest-util "^25.5.0" + jest-validate "^25.5.0" + realpath-native "^2.0.0" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.3.1" + jest-runtime@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42" @@ -8333,6 +8826,13 @@ jest-runtime@^26.4.2: strip-bom "^4.0.0" yargs "^15.3.1" +jest-serializer@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b" + integrity sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA== + dependencies: + graceful-fs "^4.2.4" + jest-serializer@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef" @@ -8341,6 +8841,27 @@ jest-serializer@^26.3.0: "@types/node" "*" graceful-fs "^4.2.4" +jest-snapshot@^25.5.1: + version "25.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f" + integrity sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^25.5.0" + "@types/prettier" "^1.19.0" + chalk "^3.0.0" + expect "^25.5.0" + graceful-fs "^4.2.4" + jest-diff "^25.5.0" + jest-get-type "^25.2.6" + jest-matcher-utils "^25.5.0" + jest-message-util "^25.5.0" + jest-resolve "^25.5.1" + make-dir "^3.0.0" + natural-compare "^1.4.0" + pretty-format "^25.5.0" + semver "^6.3.0" + jest-snapshot@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6" @@ -8374,6 +8895,29 @@ jest-util@26.x, jest-util@^26.3.0: is-ci "^2.0.0" micromatch "^4.0.2" +jest-util@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" + integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== + dependencies: + "@jest/types" "^25.5.0" + chalk "^3.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + make-dir "^3.0.0" + +jest-validate@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.5.0.tgz#fb4c93f332c2e4cf70151a628e58a35e459a413a" + integrity sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ== + dependencies: + "@jest/types" "^25.5.0" + camelcase "^5.3.1" + chalk "^3.0.0" + jest-get-type "^25.2.6" + leven "^3.1.0" + pretty-format "^25.5.0" + jest-validate@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c" @@ -8386,6 +8930,18 @@ jest-validate@^26.4.2: leven "^3.1.0" pretty-format "^26.4.2" +jest-watcher@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.5.0.tgz#d6110d101df98badebe435003956fd4a465e8456" + integrity sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q== + dependencies: + "@jest/test-result" "^25.5.0" + "@jest/types" "^25.5.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + jest-util "^25.5.0" + string-length "^3.1.0" + jest-watcher@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08" @@ -8399,6 +8955,14 @@ jest-watcher@^26.3.0: jest-util "^26.3.0" string-length "^4.0.1" +jest-worker@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" + integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== + dependencies: + merge-stream "^2.0.0" + supports-color "^7.0.0" + jest-worker@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f" @@ -8408,6 +8972,15 @@ jest-worker@^26.3.0: merge-stream "^2.0.0" supports-color "^7.0.0" +jest@^25.5.4: + version "25.5.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" + integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ== + dependencies: + "@jest/core" "^25.5.4" + import-local "^3.0.2" + jest-cli "^25.5.4" + jest@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312" @@ -8482,6 +9055,38 @@ jsdom@^14.1.0: ws "^6.1.2" xml-name-validator "^3.0.0" +jsdom@^15.2.1: + version "15.2.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" + integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== + dependencies: + abab "^2.0.0" + acorn "^7.1.0" + acorn-globals "^4.3.2" + array-equal "^1.0.0" + cssom "^0.4.1" + cssstyle "^2.0.0" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.1" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.2.0" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.7" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^7.0.0" + xml-name-validator "^3.0.0" + jsdom@^16.2.2: version "16.4.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" @@ -9074,6 +9679,13 @@ log4js@^6.3.0: rfdc "^1.1.4" streamroller "^2.2.4" +lolex@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -9669,6 +10281,17 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= +node-notifier@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" + integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== + dependencies: + growly "^1.3.0" + is-wsl "^2.1.1" + semver "^6.3.0" + shellwords "^0.1.1" + which "^1.3.1" + node-notifier@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" @@ -10131,6 +10754,11 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -11390,6 +12018,11 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" +realpath-native@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" + integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -11526,7 +12159,7 @@ request-promise-core@1.1.4: dependencies: lodash "^4.17.19" -request-promise-native@^1.0.5, request-promise-native@^1.0.8: +request-promise-native@^1.0.5, request-promise-native@^1.0.7, request-promise-native@^1.0.8: version "1.0.9" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== @@ -11610,6 +12243,11 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" @@ -12203,7 +12841,7 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-utils@^1.0.2: +stack-utils@^1.0.1, stack-utils@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== @@ -12291,6 +12929,14 @@ string-argv@0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== + dependencies: + astral-regex "^1.0.0" + strip-ansi "^5.2.0" + string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -13352,6 +13998,15 @@ v8-compile-cache@^2.0.0, v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== +v8-to-istanbul@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" + integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + v8-to-istanbul@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5" @@ -13655,7 +14310,7 @@ ws@^6.1.2, ws@^6.2.0: dependencies: async-limiter "~1.0.0" -ws@^7.2.3: +ws@^7.0.0, ws@^7.2.3: version "7.3.1" resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== From 3b882562ccecb35c33a28d4ac84d424691cdac6b Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 29 Sep 2020 14:33:17 -0700 Subject: [PATCH 32/46] feat(rds): add the ability to exclude characters when generating passwords for Cluster, Instance, DatabaseSecret Change the default excludeCharacters for Cluster, Instance and DatabaseSecret to the character set ``" %+~`#$&*()|[]{}:;<>?!'/@\"\\"``, as the previous set (`'"@/\\'`) had a tendency to generate problematic passwords that wouldn't work in the shell, or with services like DMS. Do the same for single- and multi-user rotations in Cluster and Instance as well. Also allow passing a custom excludeCharacters for Credentials and SnapshotCredentials, and also in addSingleUserRotation and addMultiUserRotation. Fixes #4144 BREAKING CHANGE: the default generated password exclude characters set for Instance, Cluster and `DatabaseSecret` is now ``" %+~`#$&*()|[]{}:;<>?!'/@\"\\"`` * **rds**: the default generated password exclude characters for `addSingleUserRotation()` and `addMultiUserRotation()` in Cluster and Instance is now ``" %+~`#$&*()|[]{}:;<>?!'/@\"\\"`` * **rds**: `Instance.addSingleUserRotation()` now takes options object as the first argument, instead of just `Duration` * **rds**: `Cluster.addSingleUserRotation()` now takes options object as the first argument, instead of just `Duration` * **rds**: `SnapshotCredentials.fromGeneratedPassword()` now takes an option object as the second argument, instead of just `IKey` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/README.md | 6 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 17 ++-- .../@aws-cdk/aws-rds/lib/database-secret.ts | 10 +- packages/@aws-cdk/aws-rds/lib/instance.ts | 19 ++-- packages/@aws-cdk/aws-rds/lib/private/util.ts | 10 ++ packages/@aws-cdk/aws-rds/lib/props.ts | 92 ++++++++++++++++--- .../integ.cluster-rotation.lit.expected.json | 5 +- .../test/integ.instance-s3.expected.json | 2 +- .../test/integ.instance.lit.expected.json | 3 +- packages/@aws-cdk/aws-rds/test/integ.proxy.ts | 4 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 1 + .../aws-rds/test/test.database-secret.ts | 4 +- .../@aws-cdk/aws-rds/test/test.instance.ts | 8 +- .../@aws-cdk/aws-secretsmanager/README.md | 4 +- .../aws-secretsmanager/lib/secret-rotation.ts | 2 +- .../test/test.secret-rotation.ts | 83 +++++++++-------- 16 files changed, 187 insertions(+), 83 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 88a2d001107de..8244f22618a0e 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -194,7 +194,10 @@ const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: ```ts -instance.addRotationSingleUser(); // Will rotate automatically after 30 days +instance.addRotationSingleUser({ + automaticallyAfter: cdk.Duration.days(7), // defaults to 30 days + excludeCharacters: '!@#$%^&*', // defaults to the set " %+~`#$&*()|[]{}:;<>?!'/@\"\\" +}); ``` [example of setting up master password rotation for a cluster](test/integ.cluster-rotation.lit.ts) @@ -213,6 +216,7 @@ It's also possible to create user credentials together with the instance/cluster const myUserSecret = new rds.DatabaseSecret(this, 'MyUserSecret', { username: 'myuser', masterSecret: instance.secret, + excludeCharacters: '{}[]()\'"/\\', // defaults to the set " %+~`#$&*()|[]{}:;<>?!'/@\"\\" }); const myUserSecretAttached = myUserSecret.attach(instance); // Adds DB connections information in the secret diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 6ed9b4da06b68..dda50403436f2 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -10,8 +10,8 @@ import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IParameterGroup } from './parameter-group'; -import { applyRemovalPolicy, defaultDeletionProtection, setupS3ImportExport } from './private/util'; -import { BackupProps, Credentials, InstanceProps, PerformanceInsightRetention, RotationMultiUserOptions } from './props'; +import { applyRemovalPolicy, DEFAULT_PASSWORD_EXCLUDE_CHARS, defaultDeletionProtection, setupS3ImportExport } from './private/util'; +import { BackupProps, Credentials, InstanceProps, PerformanceInsightRetention, RotationSingleUserOptions, RotationMultiUserOptions } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBCluster, CfnDBClusterProps, CfnDBInstance } from './rds.generated'; import { ISubnetGroup, SubnetGroup } from './subnet-group'; @@ -493,6 +493,7 @@ export class DatabaseCluster extends DatabaseClusterNew { credentials = Credentials.fromSecret(new DatabaseSecret(this, 'Secret', { username: credentials.username, encryptionKey: credentials.encryptionKey, + excludeCharacters: credentials.excludeCharacters, })); } const secret = credentials.secret; @@ -530,11 +531,8 @@ export class DatabaseCluster extends DatabaseClusterNew { /** * Adds the single user rotation of the master password to this cluster. - * - * @param [automaticallyAfter=Duration.days(30)] Specifies the number of days after the previous rotation - * before Secrets Manager triggers the next automatic rotation. */ - public addRotationSingleUser(automaticallyAfter?: Duration): secretsmanager.SecretRotation { + public addRotationSingleUser(options: RotationSingleUserOptions = {}): secretsmanager.SecretRotation { if (!this.secret) { throw new Error('Cannot add single user rotation for a cluster without secret.'); } @@ -547,11 +545,12 @@ export class DatabaseCluster extends DatabaseClusterNew { return new secretsmanager.SecretRotation(this, id, { secret: this.secret, - automaticallyAfter, application: this.singleUserRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcSubnets, target: this, + ...options, + excludeCharacters: options.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS, }); } @@ -563,9 +562,9 @@ export class DatabaseCluster extends DatabaseClusterNew { throw new Error('Cannot add multi user rotation for a cluster without secret.'); } return new secretsmanager.SecretRotation(this, id, { - secret: options.secret, + ...options, + excludeCharacters: options.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS, masterSecret: this.secret, - automaticallyAfter: options.automaticallyAfter, application: this.multiUserRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcSubnets, diff --git a/packages/@aws-cdk/aws-rds/lib/database-secret.ts b/packages/@aws-cdk/aws-rds/lib/database-secret.ts index e0d2d24f3e969..69e8ee3b4ece9 100644 --- a/packages/@aws-cdk/aws-rds/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-rds/lib/database-secret.ts @@ -1,6 +1,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Aws, Construct } from '@aws-cdk/core'; +import { DEFAULT_PASSWORD_EXCLUDE_CHARS } from './private/util'; /** * Construction properties for a DatabaseSecret. @@ -24,6 +25,13 @@ export interface DatabaseSecretProps { * @default - no master secret information will be included */ readonly masterSecret?: secretsmanager.ISecret; + + /** + * Characters to not include in the generated password. + * + * @default " %+~`#$&*()|[]{}:;<>?!'/@\"\\" + */ + readonly excludeCharacters?: string; } /** @@ -43,7 +51,7 @@ export class DatabaseSecret extends secretsmanager.Secret { masterarn: props.masterSecret?.secretArn, }), generateStringKey: 'password', - excludeCharacters: '"@/\\', + excludeCharacters: props.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS, }, }); } diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 1479fa2fafe40..caf0b7ae45d35 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -11,8 +11,8 @@ import { Endpoint } from './endpoint'; import { IInstanceEngine } from './instance-engine'; import { IOptionGroup } from './option-group'; import { IParameterGroup } from './parameter-group'; -import { applyRemovalPolicy, defaultDeletionProtection, engineDescription, setupS3ImportExport } from './private/util'; -import { Credentials, PerformanceInsightRetention, RotationMultiUserOptions, SnapshotCredentials } from './props'; +import { applyRemovalPolicy, DEFAULT_PASSWORD_EXCLUDE_CHARS, defaultDeletionProtection, engineDescription, setupS3ImportExport } from './private/util'; +import { Credentials, PerformanceInsightRetention, RotationMultiUserOptions, RotationSingleUserOptions, SnapshotCredentials } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBInstance, CfnDBInstanceProps } from './rds.generated'; import { ISubnetGroup, SubnetGroup } from './subnet-group'; @@ -851,10 +851,10 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa /** * Adds the single user rotation of the master password to this instance. * - * @param [automaticallyAfter=Duration.days(30)] Specifies the number of days after the previous rotation - * before Secrets Manager triggers the next automatic rotation. + * @param options the options for the rotation, + * if you want to override the defaults */ - public addRotationSingleUser(automaticallyAfter?: Duration): secretsmanager.SecretRotation { + public addRotationSingleUser(options: RotationSingleUserOptions = {}): secretsmanager.SecretRotation { if (!this.secret) { throw new Error('Cannot add single user rotation for an instance without secret.'); } @@ -867,11 +867,12 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa return new secretsmanager.SecretRotation(this, id, { secret: this.secret, - automaticallyAfter, application: this.singleUserRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcPlacement, target: this, + ...options, + excludeCharacters: options.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS, }); } @@ -883,9 +884,9 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa throw new Error('Cannot add multi user rotation for an instance without secret.'); } return new secretsmanager.SecretRotation(this, id, { - secret: options.secret, + ...options, + excludeCharacters: options.excludeCharacters ?? DEFAULT_PASSWORD_EXCLUDE_CHARS, masterSecret: this.secret, - automaticallyAfter: options.automaticallyAfter, application: this.multiUserRotationApplication, vpc: this.vpc, vpcSubnets: this.vpcPlacement, @@ -948,6 +949,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas credentials = Credentials.fromSecret(new DatabaseSecret(this, 'Secret', { username: credentials.username, encryptionKey: credentials.encryptionKey, + excludeCharacters: credentials.excludeCharacters, })); } const secret = credentials.secret; @@ -1026,6 +1028,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme secret = new DatabaseSecret(this, 'Secret', { username: credentials.username, encryptionKey: credentials.encryptionKey, + excludeCharacters: credentials.excludeCharacters, }); } diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index a8439b652abc9..361e0228c62e4 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -3,6 +3,16 @@ import * as s3 from '@aws-cdk/aws-s3'; import { Construct, CfnDeletionPolicy, CfnResource, RemovalPolicy } from '@aws-cdk/core'; import { IEngine } from '../engine'; +/** + * The default set of characters we exclude from generated passwords for database users. + * It's a combination of characters that have a tendency to cause problems in shell scripts, + * some engine-specific characters (for example, Oracle doesn't like '@' in its passwords), + * and some that trip up other services, like DMS. + * + * This constant is private to the RDS module. + */ +export const DEFAULT_PASSWORD_EXCLUDE_CHARS = " %+~`#$&*()|[]{}:;<>?!'/@\"\\"; + /** Common base of `DatabaseInstanceProps` and `DatabaseClusterBaseProps` that has only the S3 props */ export interface DatabaseS3ImportExportProps { readonly s3ImportRole?: iam.IRole; diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index e9be6be583af9..943726a256303 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -134,6 +134,14 @@ export interface CredentialsFromUsernameOptions { * @default - default master key */ readonly encryptionKey?: kms.IKey; + + /** + * The characters to exclude from the generated password. + * Has no effect if {@link password} has been provided. + * + * @default - the DatabaseSecret default exclude character set (" %+~`#$&*()|[]{}:;<>?!'/@\"\\") + */ + readonly excludeCharacters?: string; } /** @@ -146,7 +154,10 @@ export abstract class Credentials { * If no password is provided, one will be generated and stored in SecretsManager. */ public static fromUsername(username: string, options: CredentialsFromUsernameOptions = {}): Credentials { - return { username, password: options.password, encryptionKey: options.encryptionKey }; + return { + ...options, + username, + }; } /** @@ -197,6 +208,33 @@ export abstract class Credentials { * @default - none */ public abstract readonly secret?: secretsmanager.Secret; + + /** + * The characters to exclude from the generated password. + * Only used if {@link password} has not been set. + * + * @default - the DatabaseSecret default exclude character set (" %+~`#$&*()|[]{}:;<>?!'/@\"\\") + */ + public abstract readonly excludeCharacters?: string; +} + +/** + * Options used in the {@link SnapshotCredentials.fromGeneratedPassword} method. + */ +export interface SnapshotCredentialsFromGeneratedPasswordOptions { + /** + * KMS encryption key to encrypt the generated secret. + * + * @default - default master key + */ + readonly encryptionKey?: kms.IKey; + + /** + * The characters to exclude from the generated password. + * + * @default - the DatabaseSecret default exclude character set (" %+~`#$&*()|[]{}:;<>?!'/@\"\\") + */ + readonly excludeCharacters?: string; } /** @@ -208,8 +246,12 @@ export abstract class SnapshotCredentials { * * Note - The username must match the existing master username of the snapshot. */ - public static fromGeneratedPassword(username: string, encryptionKey?: kms.IKey): SnapshotCredentials { - return { generatePassword: true, username, encryptionKey }; + public static fromGeneratedPassword(username: string, options: SnapshotCredentialsFromGeneratedPasswordOptions = {}): SnapshotCredentials { + return { + ...options, + generatePassword: true, + username, + }; } /** @@ -275,12 +317,46 @@ export abstract class SnapshotCredentials { * @default - none */ public abstract readonly secret?: secretsmanager.Secret; + + /** + * The characters to exclude from the generated password. + * Only used if {@link generatePassword} if true. + * + * @default - the DatabaseSecret default exclude character set (" %+~`#$&*()|[]{}:;<>?!'/@\"\\") + */ + public abstract readonly excludeCharacters?: string; +} + +/** + * Properties common to single-user and multi-user rotation options. + */ +interface CommonRotationUserOptions { + /** + * Specifies the number of days after the previous rotation + * before Secrets Manager triggers the next automatic rotation. + * + * @default - 30 days + */ + readonly automaticallyAfter?: Duration; + + /** + * Specifies characters to not include in generated passwords. + * + * @default " %+~`#$&*()|[]{}:;<>?!'/@\"\\" + */ + readonly excludeCharacters?: string; } /** * Options to add the multi user rotation */ -export interface RotationMultiUserOptions { +export interface RotationSingleUserOptions extends CommonRotationUserOptions { +} + +/** + * Options to add the multi user rotation + */ +export interface RotationMultiUserOptions extends CommonRotationUserOptions { /** * The secret to rotate. It must be a JSON string with the following format: * ``` @@ -296,14 +372,6 @@ export interface RotationMultiUserOptions { * ``` */ readonly secret: secretsmanager.ISecret; - - /** - * Specifies the number of days after the previous rotation before - * Secrets Manager triggers the next automatic rotation. - * - * @default Duration.days(30) - */ - readonly automaticallyAfter?: Duration; } /** diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json index e58745e098767..05293187edb33 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json @@ -594,7 +594,7 @@ ] }, "GenerateSecretString": { - "ExcludeCharacters": "\"@/\\", + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", "GenerateStringKey": "password", "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"admin\"}" @@ -807,7 +807,8 @@ "DatabaseRotationSingleUserSecurityGroupAC6E0E73", "GroupId" ] - } + }, + "excludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\" } } } diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json index 8b2b425303bec..cd2478b15b818 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json @@ -530,7 +530,7 @@ ] }, "GenerateSecretString": { - "ExcludeCharacters": "\"@/\\", + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", "GenerateStringKey": "password", "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"admin\"}" diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index ef71c890eaf4b..27c2eed74ca17 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -549,7 +549,7 @@ ] }, "GenerateSecretString": { - "ExcludeCharacters": "\"@/\\", + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", "GenerateStringKey": "password", "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"syscdk\"}" @@ -832,6 +832,7 @@ ] }, "functionName": "awscdkrdsinstanceInstanceRotationSingleUserAFE3C214", + "excludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", "vpcSubnetIds": { "Fn::Join": [ "", diff --git a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts index e45da0950ed2a..d3945cf4a5b99 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts @@ -10,7 +10,9 @@ const vpc = new ec2.Vpc(stack, 'vpc', { maxAzs: 2 }); const dbInstance = new rds.DatabaseInstance(stack, 'dbInstance', { engine: rds.DatabaseInstanceEngine.POSTGRES, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), - credentials: rds.Credentials.fromUsername('master'), + credentials: rds.Credentials.fromUsername('master', { + excludeCharacters: '"@/\\', + }), vpc, }); diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 47b9ce619fe17..151137bd86d4b 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -184,6 +184,7 @@ export = { engine: DatabaseClusterEngine.AURORA_MYSQL, credentials: { username: 'admin', + excludeCharacters: '"@/\\', }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), diff --git a/packages/@aws-cdk/aws-rds/test/test.database-secret.ts b/packages/@aws-cdk/aws-rds/test/test.database-secret.ts index eabdf4aea8298..bc215c6da3487 100644 --- a/packages/@aws-cdk/aws-rds/test/test.database-secret.ts +++ b/packages/@aws-cdk/aws-rds/test/test.database-secret.ts @@ -2,6 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { DatabaseSecret } from '../lib'; +import { DEFAULT_PASSWORD_EXCLUDE_CHARS } from '../lib/private/util'; export = { 'create a database secret'(test: Test) { @@ -27,7 +28,7 @@ export = { ], }, GenerateSecretString: { - ExcludeCharacters: '"@/\\', + ExcludeCharacters: DEFAULT_PASSWORD_EXCLUDE_CHARS, GenerateStringKey: 'password', PasswordLength: 30, SecretStringTemplate: '{"username":"admin-username"}', @@ -48,6 +49,7 @@ export = { new DatabaseSecret(stack, 'UserSecret', { username: 'user-username', masterSecret, + excludeCharacters: '"@/\\', }); // THEN diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 6dad43e8ec7f3..f913c6b858cf0 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -28,7 +28,9 @@ export = { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, - credentials: rds.Credentials.fromUsername('syscdk'), + credentials: rds.Credentials.fromUsername('syscdk', { + excludeCharacters: '"@/\\', + }), vpc, databaseName: 'ORCL', storageEncrypted: true, @@ -291,7 +293,9 @@ export = { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), vpc, - credentials: rds.SnapshotCredentials.fromGeneratedPassword('admin'), + credentials: rds.SnapshotCredentials.fromGeneratedPassword('admin', { + excludeCharacters: '"@/\\', + }), }); expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index 8b50ab77cf991..4904814a1c4e9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -95,7 +95,9 @@ new secretsmanager.SecretRotation(this, 'SecretRotation', { secret: mySecret, target: myDatabase, // a Connectable vpc: myVpc, // The VPC where the secret rotation application will be deployed - excludeCharacters: ` ;+%{}` + `@'"\`/\\#`, // A string of characters to never use when generating new passwords. Example is a superset of the characters which will break DMS endpoints and characters which cause problems in BASH scripts. + excludeCharacters: ' %+:;{}', // characters to never use when generating new passwords; + // by default, no characters are excluded, + // which might cause problems with some services, like DMS }); ``` diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index f4f24cd2b05e5..cdd51ff5cedbb 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -238,7 +238,7 @@ export class SecretRotation extends CoreConstruct { vpcSecurityGroupIds: securityGroup.securityGroupId, }; - if (props.excludeCharacters) { + if (props.excludeCharacters !== undefined) { parameters.excludeCharacters = props.excludeCharacters; } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts index 79351afc059f3..76ab7930d7dce 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts @@ -1,19 +1,31 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { ICallbackFunction, Test } from 'nodeunit'; import * as secretsmanager from '../lib'; +let stack: cdk.Stack; +let vpc: ec2.IVpc; +let secret: secretsmanager.ISecret; +let securityGroup: ec2.SecurityGroup; +let target: ec2.Connections; + export = { - 'secret rotation single user'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const target = new ec2.Connections({ + 'setUp'(cb: ICallbackFunction) { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'VPC'); + secret = new secretsmanager.Secret(stack, 'Secret'); + securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); + target = new ec2.Connections({ defaultPort: ec2.Port.tcp(3306), - securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc })], + securityGroups: [securityGroup], }); + + cb(); + }, + + 'secret rotation single user'(test: Test) { + // GIVEN const excludeCharacters = ' ;+%{}' + '@\'"`/\\#'; // DMS and BASH problem chars // WHEN @@ -149,14 +161,7 @@ export = { 'secret rotation multi user'(test: Test) { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const secret = new secretsmanager.Secret(stack, 'Secret'); const masterSecret = new secretsmanager.Secret(stack, 'MasterSecret'); - const target = new ec2.Connections({ - defaultPort: ec2.Port.tcp(3306), - securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc })], - }); // WHEN new secretsmanager.SecretRotation(stack, 'SecretRotation', { @@ -249,17 +254,29 @@ export = { test.done(); }, - 'throws when connections object has no default port range'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + 'secret rotation allows passing an empty string for excludeCharacters'(test: Test) { + // WHEN + new secretsmanager.SecretRotation(stack, 'SecretRotation', { + application: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, + secret, + target, vpc, + excludeCharacters: '', }); + // THEN + expect(stack).to(haveResourceLike('AWS::Serverless::Application', { + Parameters: { + excludeCharacters: '', + }, + })); + + test.done(); + }, + + 'throws when connections object has no default port range'(test: Test) { // WHEN - const target = new ec2.Connections({ + const targetWithoutDefaultPort = new ec2.Connections({ securityGroups: [securityGroup], }); @@ -268,22 +285,13 @@ export = { secret, application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, vpc, - target, + target: targetWithoutDefaultPort, }), /`target`.+default port range/); test.done(); }, 'throws when master secret is missing for a multi user application'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const target = new ec2.Connections({ - defaultPort: ec2.Port.tcp(3306), - securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc })], - }); - // THEN test.throws(() => new secretsmanager.SecretRotation(stack, 'Rotation', { secret, @@ -296,15 +304,6 @@ export = { }, 'rotation function name does not exceed 64 chars'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const target = new ec2.Connections({ - defaultPort: ec2.Port.tcp(3306), - securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc })], - }); - // WHEN const id = 'SecretRotation'.repeat(5); new secretsmanager.SecretRotation(stack, id, { From f63db88cc31758ad8eb67663d4c7879d9ba40c1f Mon Sep 17 00:00:00 2001 From: Max Duval Date: Tue, 29 Sep 2020 22:59:17 +0100 Subject: [PATCH 33/46] docs(synthetics): fix small typo in README (#10593) Removing an extra closing parenthesis `)`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-synthetics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index 6b62b87db495e..928ff8cf05976 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -34,7 +34,7 @@ import * as synthetics from '@aws-cdk/aws-synthetics'; const canary = new synthetics.Canary(this, 'MyCanary', { schedule: synthetics.Schedule.rate(Duration.minutes(5)), test: Test.custom({ - code: Code.fromAsset(path.join(__dirname, 'canary'))), + code: Code.fromAsset(path.join(__dirname, 'canary')), handler: 'index.handler', }), }); From b2ce3aaf3fcf3405c04b145cc6288e074da4f4fe Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 30 Sep 2020 01:24:40 +0300 Subject: [PATCH 34/46] feat(eks): EKS is now in Developer Preview (#10518) Graduate the module to *Developer Preview*. This PR also includes some last minute API changes we feel make it a little more semantically clear. In addition, the README was restructured to be more coherent. See [rendered version](https://github.com/aws/aws-cdk/blob/epolon/10364-eks-dev-preview/packages/%40aws-cdk/aws-eks/README.md). Happy to break this down to multiple PR's if you find it hard to review. BREAKING CHANGE: `cluster.addChart` renamed to `cluster.addHelmChart` * **eks:** `NodegroupOptions.launchTemplate` renamed to `NodegroupOptions.launchTemplateSpec` * **eks:** `cluster.addAutoScalingGroup` renamed to `cluster.connectAutoScalingGroupCapacity` * **eks:** `cluster.addNodegroup` renamed to `cluster.addNoedgroupCapacity` * **eks:** `cluster.addCapacity` renamed to `cluster.addAutoScalingGroupCapacity` * **eks:** `CapacityOptions` renamed to `AutoScalingGroupCapacityOptions`. Resolves https://github.com/aws/aws-cdk/issues/10364 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 955 ++++++++++-------- packages/@aws-cdk/aws-eks/lib/cluster.ts | 26 +- .../@aws-cdk/aws-eks/lib/legacy-cluster.ts | 8 +- .../@aws-cdk/aws-eks/lib/managed-nodegroup.ts | 12 +- packages/@aws-cdk/aws-eks/package.json | 2 +- .../test/example.ssh-into-nodes.lit.ts | 2 +- .../aws-eks/test/integ.eks-cluster.ts | 22 +- .../@aws-cdk/aws-eks/test/test.awsauth.ts | 2 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 46 +- .../aws-eks/test/test.k8s-manifest.ts | 2 +- .../@aws-cdk/aws-eks/test/test.nodegroup.ts | 18 +- 11 files changed, 615 insertions(+), 480 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 28f03e0d9644a..a49a11aab2624 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -1,5 +1,4 @@ ## Amazon EKS Construct Library - --- @@ -7,25 +6,50 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.svg?style=for-the-badge) -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> The APIs of higher level constructs in this module are in **developer preview** before they become stable. We will only make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. --- -This construct library allows you to define [Amazon Elastic Container Service -for Kubernetes (EKS)](https://aws.amazon.com/eks/) clusters programmatically. -This library also supports programmatically defining Kubernetes resource -manifests within EKS clusters. +This construct library allows you to define [Amazon Elastic Container Service for Kubernetes (EKS)](https://aws.amazon.com/eks/) clusters. +In addition, the library also supports defining Kubernetes resource manifests within EKS clusters. + +Table Of Contents +================= + +* [Quick Start](#quick-start) +* [API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-eks-readme.html) +* [Architectural Overview](#architectural-overview) +* [Provisioning clusters](#provisioning-clusters) + * [Managed node groups](#managed-node-groups) + * [Fargate Profiles](#fargate-profiles) + * [Self-managed nodes](#self-managed-nodes) + * [Endpoint Access](#endpoint-access) + * [VPC Support](#vpc-support) + * [Kubectl Support](#kubectl-support) + * [ARM64 Support](#arm64-support) + * [Masters Role](#masters-role) + * [Encryption](#encryption) +* [Permissions and Security](#permissions-and-security) +* [Applying Kubernetes Resources](#applying-kubernetes-resources) + * [Kubernetes Manifests](#kubernetes-manifests) + * [Helm Charts](#helm-charts) +* [Patching Kuberentes Resources](#patching-kubernetes-resources) +* [Querying Kubernetes Resources](#querying-kubernetes-resources) +* [Using existing clusters](#using-existing-clusters) +* [Known Issues and Limitations](#known-issues-and-limitations) + +## Quick Start This example defines an Amazon EKS cluster with the following configuration: -- Managed nodegroup with 2x **m5.large** instances (this instance type suits most common use-cases, and is good value for money) -- Dedicated VPC with default configuration (see [ec2.Vpc](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#vpc)) +- Dedicated VPC with default configuration (Implicitly created using [ec2.Vpc](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#vpc)) - A Kubernetes pod with a container based on the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) image. ```ts +// provisiong a cluster const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_16, }); @@ -47,30 +71,15 @@ cluster.addManifest('mypod', { }); ``` -> **NOTE: You can only create 1 cluster per stack.** If you have a use-case for multiple clusters per stack, > or would like to understand more about this limitation, see https://github.com/aws/aws-cdk/issues/10073. - -In order to interact with your cluster through `kubectl`, you can use the `aws -eks update-kubeconfig` [AWS CLI command](https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html) -to configure your local kubeconfig. - -The EKS module will define a CloudFormation output in your stack which contains -the command to run. For example: +In order to interact with your cluster through `kubectl`, you can use the `aws eks update-kubeconfig` [AWS CLI command](https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html) +to configure your local kubeconfig. The EKS module will define a CloudFormation output in your stack which contains the command to run. For example: ``` Outputs: ClusterConfigCommand43AAE40F = aws eks update-kubeconfig --name cluster-xxxxx --role-arn arn:aws:iam::112233445566:role/yyyyy ``` -> The IAM role specified in this command is called the "**masters role**". This is -> an IAM role that is associated with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) -> group and has super-user access to the cluster. -> -> You can specify this role using the `mastersRole` option, or otherwise a role will be -> automatically created for you. This role can be assumed by anyone in the account with -> `sts:AssumeRole` permissions for this role. - -Execute the `aws eks update-kubeconfig ...` command in your terminal to create a -local kubeconfig: +Execute the `aws eks update-kubeconfig ... ` command in your terminal to create or update a local kubeconfig context: ```console $ aws eks update-kubeconfig --name cluster-xxxxx --role-arn arn:aws:iam::112233445566:role/yyyyy @@ -89,146 +98,139 @@ pod/coredns-5cb4fb54c7-v9nxx 1/1 Running 0 23m ... ``` -### Endpoint Access +## Architectural Overview -You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html) by using the `endpointAccess` property: +The following is a qualitative diagram of the various possible components involved in the cluster deployment. -```typescript -const cluster = new eks.Cluster(this, 'hello-eks', { - version: eks.KubernetesVersion.V1_16, - endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC. -}); +```text + +-----------------------------------------------+ +-----------------+ + | EKS Cluster | kubectl | | + |-----------------------------------------------|<-------------+| Kubectl Handler | + | | | | + | | +-----------------+ + | +--------------------+ +-----------------+ | + | | | | | | + | | Managed Node Group | | Fargate Profile | | +-----------------+ + | | | | | | | | + | +--------------------+ +-----------------+ | | Cluster Handler | + | | | | + +-----------------------------------------------+ +-----------------+ + ^ ^ + + | | | + | connect self managed capacity | | aws-sdk + | | create/update/delete | + + | v + +--------------------+ + +-------------------+ + | | --------------+| eks.amazonaws.com | + | Auto Scaling Group | +-------------------+ + | | + +--------------------+ ``` -The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cluster endpoint is accessible from outside of your VPC, but worker node traffic as well as `kubectl` commands -to the endpoint will stay within your VPC. +In a nutshell: -### Capacity +- `EKS Cluster` - The cluster endpoint created by EKS. +- `Managed Node Group` - EC2 worker nodes managed by EKS. +- `Fargate Profile` - Fargate worker nodes managed by EKS. +- `Auto Scaling Group` - EC2 worker nodes managed by the user. +- `KubectlHandler` - Lambda function for invoking `kubectl` commands on the cluster - created by CDK. +- `ClusterHandler` - Lambda function for interacting with EKS API to manage the cluster lifecycle - created by CDK. -By default, `eks.Cluster` is created with a managed nodegroup with x2 `m5.large` instances. You must specify the kubernetes version for the cluster with the `version` property. +A more detailed breakdown of each is provided further down this README. -```ts -new eks.Cluster(this, 'cluster-two-m5-large', { - version: eks.KubernetesVersion.V1_16, -}); -``` +## Provisioning clusters -To use the traditional self-managed Amazon EC2 instances instead, set `defaultCapacityType` to `DefaultCapacityType.EC2` +Creating a new cluster is done using the `Cluster` or `FargateCluster` constructs. The only required property is the kubernetes `version`. -```ts -const cluster = new eks.Cluster(this, 'cluster-self-managed-ec2', { - defaultCapacityType: eks.DefaultCapacityType.EC2, - version: eks.KubernetesVersion.V1_16, +```typescript +new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, }); ``` -The quantity and instance type for the default capacity can be specified through -the `defaultCapacity` and `defaultCapacityInstance` props: +You can also use `FargateCluster` to provision a cluster that uses only fargate workers. -```ts -new eks.Cluster(this, 'cluster', { - defaultCapacity: 10, - defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), - version: eks.KubernetesVersion.V1_16, +```typescript +new eks.FargateCluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, }); ``` -To disable the default capacity, simply set `defaultCapacity` to `0`: +> **NOTE: Only 1 cluster per stack is supported.** If you have a use-case for multiple clusters per stack, or would like to understand more about this limitation, see https://github.com/aws/aws-cdk/issues/10073. -```ts -new eks.Cluster(this, 'cluster-with-no-capacity', { - defaultCapacity: 0, - version: eks.KubernetesVersion.V1_16, -}); -``` +Below you'll find a few important cluster configuration options. First of which is Capacity. +Capacity is the amount and the type of worker nodes that are available to the cluster for deploying resources. Amazon EKS offers 3 ways of configuring capacity, which you can combine as you like: -When creating a cluster with default capacity (i.e `defaultCapacity !== 0` or is undefined), you can access the allocated capacity using: +### Managed node groups -- `cluster.defaultCapacity` will reference the `AutoScalingGroup` resource in case `defaultCapacityType` is set to `EC2` or is undefined. -- `cluster.defaultNodegroup` will reference the `Nodegroup` resource in case `defaultCapacityType` is set to `NODEGROUP`. +Amazon EKS managed node groups automate the provisioning and lifecycle management of nodes (Amazon EC2 instances) for Amazon EKS Kubernetes clusters. +With Amazon EKS managed node groups, you don’t need to separately provision or register the Amazon EC2 instances that provide compute capacity to run your Kubernetes applications. You can create, update, or terminate nodes for your cluster with a single operation. Nodes run using the latest Amazon EKS optimized AMIs in your AWS account while node updates and terminations gracefully drain nodes to ensure that your applications stay available. -You can add customized capacity in the form of an `AutoScalingGroup` resource through `cluster.addCapacity()` or `cluster.addAutoScalingGroup()`: +> For more details visit [Amazon EKS Managed Node Groups](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html). -```ts -cluster.addCapacity('frontend-nodes', { - instanceType: new ec2.InstanceType('t2.medium'), - minCapacity: 3, - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } -}); -``` +**Managed Node Groups are the recommended way to allocate cluster capacity.** -### Managed Node Groups +By default, this library will allocate a managed node group with 2 *m5.large* instances (this instance type suits most common use-cases, and is good value for money). -Amazon EKS managed node groups automate the provisioning and lifecycle management of nodes (Amazon EC2 instances) -for Amazon EKS Kubernetes clusters. By default, `eks.Nodegroup` create a nodegroup with x2 `t3.medium` instances. +At cluster instantiation time, you can customize the number of instances and their type: -```ts -new eks.Nodegroup(stack, 'nodegroup', { cluster }); +```typescript +new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, + defaultCapacity: 5, + defaultCapacityInstance: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.SMALL), +}); ``` -You can add customized node groups through `cluster.addNodegroup()`: +To access the node group that was created on your behalf, you can use `cluster.defaultNodegroup`. -```ts -cluster.addNodegroup('nodegroup', { +Additional customizations are available post instantiation. To apply them, set the default capacity to 0, and use the `cluster.addNodegroupCapacity` method: + +```typescript +const cluster = new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, + defaultCapacity: 0, +}); + +cluster.addNodegroupCapacity('custom-node-group', { instanceType: new ec2.InstanceType('m5.large'), minSize: 4, + diskSize: 100, + amiType: eks.NodegroupAmiType.AL2_X86_64_GPU, + ... }); ``` -#### Custom AMI and Launch Template support +#### Launch Template Support -Specify the launch template for the nodegroup with your custom AMI. When using a custom AMI, -Amazon EKS doesn't merge any user data. Rather, You are responsible for supplying the required -bootstrap commands for nodes to join the cluster. In the following sample, `/ect/eks/bootstrap.sh` from the AMI will be used to bootstrap the node. See [Using a custom AMI](https://docs.aws.amazon.com/en_ca/eks/latest/userguide/launch-templates.html) for more details. +You can specify a launch template that the node group will use. Note that when using a custom AMI, Amazon EKS doesn't merge any user data. +Rather, You are responsible for supplying the required bootstrap commands for nodes to join the cluster. +In the following example, `/ect/eks/bootstrap.sh` from the AMI will be used to bootstrap the node. ```ts const userData = ec2.UserData.forLinux(); userData.addCommands( 'set -o xtrace', - `/etc/eks/bootstrap.sh ${this.cluster.clusterName}`, + `/etc/eks/bootstrap.sh ${cluster.clusterName}`, ); const lt = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { launchTemplateData: { - // specify your custom AMI below - imageId, + imageId: 'some-ami-id', // custom AMI instanceType: new ec2.InstanceType('t3.small').toString(), userData: Fn.base64(userData.render()), }, }); -this.cluster.addNodegroup('extra-ng', { - launchTemplate: { +cluster.addNodegroupCapacity('extra-ng', { + launchTemplateSpec: { id: lt.ref, version: lt.attrDefaultVersionNumber, }, }); ``` -### ARM64 Support - -Instance types with `ARM64` architecture are supported in both managed nodegroup and self-managed capacity. Simply specify an ARM64 `instanceType` (such as `m6g.medium`), and the latest -Amazon Linux 2 AMI for ARM64 will be automatically selected. - -```ts -// create a cluster with a default managed nodegroup -cluster = new eks.Cluster(this, 'Cluster', { - vpc, - version: eks.KubernetesVersion.V1_17, -}); - -// add a managed ARM64 nodegroup -cluster.addNodegroup('extra-ng-arm', { - instanceType: new ec2.InstanceType('m6g.medium'), - minSize: 2, -}); - -// add a self-managed ARM64 nodegroup -cluster.addCapacity('self-ng-arm', { - instanceType: new ec2.InstanceType('m6g.medium'), - minCapacity: 2, -}) -``` +> For more details visit [Launch Template Support](https://docs.aws.amazon.com/en_ca/eks/latest/userguide/launch-templates.html). -### Fargate +### Fargate profiles AWS Fargate is a technology that provides on-demand, right-sized compute capacity for containers. With AWS Fargate, you no longer have to provision, @@ -239,9 +241,7 @@ optimize cluster packing. You can control which pods start on Fargate and how they run with Fargate Profiles, which are defined as part of your Amazon EKS cluster. -See [Fargate -Considerations](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html#fargate-considerations) -in the AWS EKS User Guide. +See [Fargate Considerations](https://docs.aws.amazon.com/eks/latest/userguide/fargate.html#fargate-considerations) in the AWS EKS User Guide. You can add Fargate Profiles to any EKS cluster defined in your CDK app through the `addFargateProfile()` method. The following example adds a profile @@ -253,19 +253,22 @@ cluster.addFargateProfile('MyProfile', { }); ``` -To create an EKS cluster that **only** uses Fargate capacity, you can use -`FargateCluster`. +You can also directly use the `FargateProfile` construct to create profiles under different scopes: + +```ts +new eks.FargateProfile(scope, 'MyProfile', { + cluster, + ... +}); +``` -The following code defines an Amazon EKS cluster without EC2 capacity and a default -Fargate Profile that matches all pods from the "kube-system" and "default" namespaces. It is also configured to [run CoreDNS on Fargate](https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html#fargate-gs-coredns) through the `coreDnsComputeType` cluster option. +To create an EKS cluster that **only** uses Fargate capacity, you can use `FargateCluster`. +The following code defines an Amazon EKS cluster with a default Fargate Profile that matches all pods from the "kube-system" and "default" namespaces. It is also configured to [run CoreDNS on Fargate](https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html#fargate-gs-coredns). ```ts const cluster = new eks.FargateCluster(this, 'MyCluster', { version: eks.KubernetesVersion.V1_16, }); - - // apply k8s resources on this cluster -cluster.addManifest(...); ``` **NOTE**: Classic Load Balancers and Network Load Balancers are not supported on @@ -273,24 +276,76 @@ pods running on Fargate. For ingress, we recommend that you use the [ALB Ingress Controller](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) on Amazon EKS (minimum version v1.1.4). -### Spot Capacity +### Self-managed nodes + +Another way of allocating capacity to an EKS cluster is by using self-managed nodes. +EC2 instances that are part of the auto-scaling group will serve as worker nodes for the cluster. +This type of capacity is also commonly referred to as *EC2 Capacity** or *EC2 Nodes*. + +For a detailed overview please visit [Self Managed Nodes](https://docs.aws.amazon.com/eks/latest/userguide/worker.html). + +Creating an auto-scaling group and connecting it to the cluster is done using the `cluster.addAutoScalingGroupCapacity` method: + +```ts +cluster.addAutoScalingGroupCapacity('frontend-nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + minCapacity: 3, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } +}); +``` + +You can customize the [/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh) script, which is responsible +for bootstrapping the node to the EKS cluster. For example, you can use `kubeletExtraArgs` to add custom node labels or taints. + +```ts +cluster.addAutoScalingGroupCapacity('spot', { + instanceType: new ec2.InstanceType('t3.large'), + minCapacity: 2, + bootstrapOptions: { + kubeletExtraArgs: '--node-labels foo=bar,goo=far', + awsApiRetryAttempts: 5 + } +}); +``` + +To disable bootstrapping altogether (i.e. to fully customize user-data), set `bootstrapEnabled` to `false`. +You can also configure the cluster to use an auto-scaling group as the default capacity: + +```ts +cluster = new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, + defaultCapacityType: eks.DefaultCapacityType.EC2, +}); +``` + +This will allocate an auto-scaling group with 2 *m5.large* instances (this instance type suits most common use-cases, and is good value for money). +To access the `AutoScalingGroup` that was created on your behalf, you can use `cluster.defaultCapacity`. +You can also independently create an `AutoScalingGroup` and connect it to the cluster using the `cluster.connectAutoScalingGroupCapacity` method: -If `spotPrice` is specified, the capacity will be purchased from spot instances: +```ts +const asg = new ec2.AutoScalingGroup(...) +cluster.connectAutoScalingGroupCapacity(asg); +``` + +This will add the necessary user-data and configure all connections, roles, and tags needed for the instances in the auto-scaling group to properly join the cluster. + +#### Spot Instances + +When using self-managed nodes, you can configure the capacity to use spot instances, greatly reducing capacity cost. +To enable spot capacity, use the `spotPrice` property: ```ts -cluster.addCapacity('spot', { +cluster.addAutoScalingGroupCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), maxCapacity: 10 }); ``` -Spot instance nodes will be labeled with `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. +> Spot instance nodes will be labeled with `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. -The [AWS Node Termination Handler](https://github.com/aws/aws-node-termination-handler) -DaemonSet will be installed from [ -Amazon EKS Helm chart repository -](https://github.com/aws/eks-charts/tree/master/stable/aws-node-termination-handler) on these nodes. +The [AWS Node Termination Handler](https://github.com/aws/aws-node-termination-handler) `DaemonSet` will be +installed from [Amazon EKS Helm chart repository](https://github.com/aws/eks-charts/tree/master/stable/aws-node-termination-handler) on these nodes. The termination handler ensures that the Kubernetes control plane responds appropriately to events that can cause your EC2 instance to become unavailable, such as [EC2 maintenance events](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instances-status-check_sched.html) and [EC2 Spot interruptions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-interruptions.html) and helps gracefully stop all pods running on spot nodes that are about to be @@ -300,87 +355,90 @@ terminated. > > Chart Version: [0.9.5](https://github.com/aws/eks-charts/blob/v0.0.28/stable/aws-node-termination-handler/Chart.yaml) -### Bootstrapping +#### Bottlerocket + +[Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open-source operating system that is purpose-built by Amazon Web Services for running containers on virtual machines or bare metal hosts. +At this moment, `Bottlerocket` is only supported when using self-managed auto-scaling groups. + +> **NOTICE**: Bottlerocket is only available in [some supported AWS regions](https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-EKS.md#finding-an-ami). -When adding capacity, you can specify options for -[/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh) -which is responsible for associating the node to the EKS cluster. For example, -you can use `kubeletExtraArgs` to add custom node labels or taints. +The following example will create an auto-scaling group of 2 `t3.small` Linux instances running with the `Bottlerocket` AMI. ```ts -// up to ten spot instances -cluster.addCapacity('spot', { - instanceType: new ec2.InstanceType('t3.large'), - minCapacity: 2, - bootstrapOptions: { - kubeletExtraArgs: '--node-labels foo=bar,goo=far', - awsApiRetryAttempts: 5 - } +cluster.addAutoScalingGroupCapacity('BottlerocketNodes', { + instanceType: new ec2.InstanceType('t3.small'), + minCapacity: 2, + machineImageType: eks.MachineImageType.BOTTLEROCKET }); ``` -To disable bootstrapping altogether (i.e. to fully customize user-data), set `bootstrapEnabled` to `false` when you add -the capacity. +The specific Bottlerocket AMI variant will be auto selected according to the k8s version for the `x86_64` architecture. +For example, if the Amazon EKS cluster version is `1.17`, the Bottlerocket AMI variant will be auto selected as +`aws-k8s-1.17` behind the scene. -### Kubernetes Manifests +> See [Variants](https://github.com/bottlerocket-os/bottlerocket/blob/develop/README.md#variants) for more details. -The `KubernetesManifest` construct or `cluster.addManifest` method can be used -to apply Kubernetes resource manifests to this cluster. +Please note Bottlerocket does not allow to customize bootstrap options and `bootstrapOptions` properties is not supported when you create the `Bottlerocket` capacity. -> When using `cluster.addManifest`, the manifest construct is defined within the cluster's stack scope. If the manifest contains -> attributes from a different stack which depend on the cluster stack, a circular dependency will be created and you will get a synth time error. -> To avoid this, directly use `new KubernetesManifest` to create the manifest in the scope of the other stack. +### Endpoint Access -The following examples will deploy the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) -service on the cluster: +When you create a new cluster, Amazon EKS creates an endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using Kubernetes management tools such as `kubectl`) -```ts -const appLabel = { app: "hello-kubernetes" }; +By default, this API server endpoint is public to the internet, and access to the API server is secured using a combination of +AWS Identity and Access Management (IAM) and native Kubernetes [Role Based Access Control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) (RBAC). -const deployment = { - apiVersion: "apps/v1", - kind: "Deployment", - metadata: { name: "hello-kubernetes" }, - spec: { - replicas: 3, - selector: { matchLabels: appLabel }, - template: { - metadata: { labels: appLabel }, - spec: { - containers: [ - { - name: "hello-kubernetes", - image: "paulbouwer/hello-kubernetes:1.5", - ports: [ { containerPort: 8080 } ] - } - ] - } - } - } -}; +You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html) by using the `endpointAccess` property: -const service = { - apiVersion: "v1", - kind: "Service", - metadata: { name: "hello-kubernetes" }, - spec: { - type: "LoadBalancer", - ports: [ { port: 80, targetPort: 8080 } ], - selector: appLabel - } -}; +```typescript +const cluster = new eks.Cluster(this, 'hello-eks', { + version: eks.KubernetesVersion.V1_16, + endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC. +}); +``` -// option 1: use a construct -new KubernetesManifest(this, 'hello-kub', { - cluster, - manifest: [ deployment, service ] +### VPC Support + +You can specify the VPC of the cluster using the `vpc` and `vpcSubnets` properties: + +```ts +const vpc = new ec2.Vpc(this, 'Vpc'); + +new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, + vpc, + vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }] }); +``` -// or, option2: use `addManifest` -cluster.addManifest('hello-kub', service, deployment); +If you do not specify a VPC, one will be created on your behalf, which you can then access via `cluster.vpc`. The cluster VPC will be associated to any EKS managed capacity (i.e Managed Node Groups and Fargate Profiles). + +If you allocate self managed capacity, you can specify which subnets should the auto-scaling group use: + +```ts +const vpc = new ec2.Vpc(this, 'Vpc'); +cluster.addAutoScalingGroupCapacity('nodes', { + vpcSubnets: { subnets: vpc.privateSubnets } +}); ``` -#### Kubectl Layer and Environment +In addition to the cluster and the capacity, there are two additional components you might want to +provision within a VPC. + +#### Kubectl Handler + +The `KubectlHandler` is a Lambda function responsible to issuing `kubectl` and `helm` commands against the cluster when you add resource manifests to the cluster. + +The handler association to the VPC is derived from the `endpointAccess` configuration. The rule of thumb is: *If the cluster VPC can be associated, it will be*. + +Breaking this down, it means that if the endpoint exposes private access (via `EndpointAccess.PRIVATE` or `EndpointAccess.PUBLIC_AND_PRIVATE`), and the VPC contains **private** subnets, the Lambda function will be provisioned inside the VPC and use the private subnets to interact with the cluster. This is the common use-case. + +If the endpoint does not expose private access (via `EndpointAccess.PUBLIC`) **or** the VPC does not contain private subnets, the function will not be provisioned within the VPC. + +#### Cluster Handler + +The `ClusterHandler` is a Lambda function responsible to interact the EKS API in order to control the cluster lifecycle. At the moment, this function cannot be provisioned inside the VPC. See [Attach all Lambda Function to a VPC](https://github.com/aws/aws-cdk/issues/9509) for more details. + +### Kubectl Support The resources are created in the cluster by running `kubectl apply` from a python lambda function. You can configure the environment of this function by specifying it at cluster instantiation. For example, this can be useful in order to configure an http proxy: @@ -393,9 +451,7 @@ const cluster = new eks.Cluster(this, 'hello-eks', { }); ``` -By default, the `kubectl`, `helm` and `aws` commands used to operate the cluster -are provided by an AWS Lambda Layer from the AWS Serverless Application -in [aws-lambda-layer-kubectl]. In most cases this should be sufficient. +By default, the `kubectl`, `helm` and `aws` commands used to operate the cluster are provided by an AWS Lambda Layer from the AWS Serverless Application in [aws-lambda-layer-kubectl](https://github.com/aws-samples/aws-lambda-layer-kubectl). In most cases this should be sufficient. You can provide a custom layer in case the default layer does not meet your needs or if the SAR app is not available in your region. @@ -430,177 +486,86 @@ const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { > Instructions on how to build `layer.zip` can be found > [here](https://github.com/aws-samples/aws-lambda-layer-kubectl/blob/master/cdk/README.md). -[aws-lambda-layer-kubectl]: https://github.com/aws-samples/aws-lambda-layer-kubectl - -#### Adding resources from a URL +### ARM64 Support -The following example will deploy the resource manifest hosting on remote server: +Instance types with `ARM64` architecture are supported in both managed nodegroup and self-managed capacity. Simply specify an ARM64 `instanceType` (such as `m6g.medium`), and the latest +Amazon Linux 2 AMI for ARM64 will be automatically selected. ```ts -import * as yaml from 'js-yaml'; -import * as request from 'sync-request'; +// add a managed ARM64 nodegroup +cluster.addNodegroupCapacity('extra-ng-arm', { + instanceType: new ec2.InstanceType('m6g.medium'), + minSize: 2, +}); -const manifestUrl = 'https://url/of/manifest.yaml'; -const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); -cluster.addManifest('my-resource', ...manifest); +// add a self-managed ARM64 nodegroup +cluster.addAutoScalingGroupCapacity('self-ng-arm', { + instanceType: new ec2.InstanceType('m6g.medium'), + minCapacity: 2, +}) ``` -Since Kubernetes manifests are implemented as CloudFormation resources in the -CDK. This means that if the manifest is deleted from your code (or the stack is -deleted), the next `cdk deploy` will issue a `kubectl delete` command and the -Kubernetes resources in that manifest will be deleted. - -#### Caveat - -If you have multiple resources in a single `KubernetesManifest`, and one of those **resources** is removed from the manifest, it will not be deleted and will remain orphan. See [Support Object pruning](https://github.com/aws/aws-cdk/issues/10495) for more details. - -#### Dependencies - -There are cases where Kubernetes resources must be deployed in a specific order. -For example, you cannot define a resource in a Kubernetes namespace before the -namespace was created. +### Masters Role -You can represent dependencies between `KubernetesManifest`s using -`resource.node.addDependency()`: +When you create a cluster, you can specify a `mastersRole`. The `Cluster` construct will associate this role with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) group, giving it super-user access to the cluster. ```ts -const namespace = cluster.addManifest('my-namespace', { - apiVersion: 'v1', - kind: 'Namespace', - metadata: { name: 'my-app' } +const role = new iam.Role(...); +new eks.Cluster(this, 'HelloEKS', { + version: eks.KubernetesVersion.V1_17, + mastersRole: role, }); +``` -const service = cluster.addManifest('my-service', { - metadata: { - name: 'myservice', - namespace: 'my-app' - }, - spec: // ... -}); +If you do not specify it, a default role will be created on your behalf, that can be assumed by anyone in the account with `sts:AssumeRole` permissions for this role. -service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. +This is the role you see as part of the stack outputs mentioned in the [Quick Start](#quick-start). + +```console +$ aws eks update-kubeconfig --name cluster-xxxxx --role-arn arn:aws:iam::112233445566:role/yyyyy +Added new context arn:aws:eks:rrrrr:112233445566:cluster/cluster-xxxxx to /home/boom/.kube/config ``` -**NOTE:** when a `KubernetesManifest` includes multiple resources (either directly -or through `cluster.addManifest()`) (e.g. `cluster.addManifest('foo', r1, r2, -r3,...)`), these resources will be applied as a single manifest via `kubectl` -and will be applied sequentially (the standard behavior in `kubectl`). +The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cluster endpoint is accessible from outside of your VPC, but worker node traffic and `kubectl` commands issued by this library stay within your VPC. -### Patching Kubernetes Resources +### Encryption -The `KubernetesPatch` construct can be used to update existing kubernetes -resources. The following example can be used to patch the `hello-kubernetes` -deployment from the example above with 5 replicas. - -```ts -new KubernetesPatch(this, 'hello-kub-deployment-label', { - cluster, - resourceName: "deployment/hello-kubernetes", - applyPatch: { spec: { replicas: 5 } }, - restorePatch: { spec: { replicas: 3 } } -}) -``` - -### Querying Kubernetes Object Values - -The `KubernetesObjectValue` construct can be used to query for information about kubernetes objects, -and use that as part of your CDK application. - -For example, you can fetch the address of a [`LoadBalancer`](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) type service: - -```typescript -// query the load balancer address -const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute', { - cluster: cluster, - resourceType: 'service', - resourceName: 'my-service', - jsonPath: '.status.loadBalancer.ingress[0].hostname', // https://kubernetes.io/docs/reference/kubectl/jsonpath/ -}); - -// pass the address to a lambda function -const proxyFunction = new lambda.Function(this, 'ProxyFunction', { - ... - environment: { - myServiceAddress: myServiceAddress.value - }, -}) -``` - -Specifically, since the above use-case is quite common, there is an easier way to access that information: - -```typescript -const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('my-service'); -``` - -### Kubernetes Resources in Existing Clusters +When you create an Amazon EKS cluster, envelope encryption of Kubernetes secrets using the AWS Key Management Service (AWS KMS) can be enabled. +The documentation on [creating a cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) +can provide more details about the customer master key (CMK) that can be used for the encryption. -The Amazon EKS library allows defining Kubernetes resources such as [Kubernetes -manifests](#kubernetes-resources) and [Helm charts](#helm-charts) on clusters -that are not defined as part of your CDK app. +You can use the `secretsEncryptionKey` to configure which key the cluster will use to encrypt Kubernetes secrets. By default, an AWS Managed key will be used. -First, you'll need to "import" a cluster to your CDK app. To do that, use the -`eks.Cluster.fromClusterAttributes()` static method: +> This setting can only be specified when the cluster is created and cannot be updated. ```ts -const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { - clusterName: 'my-cluster-name', - kubectlRoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', +const secretsKey = new kms.Key(this, 'SecretsKey'); +const cluster = new eks.Cluster(this, 'MyCluster', { + secretsEncryptionKey: secretsKey, + // ... }); ``` -Then, you can use `addManifest` or `addHelmChart` to define resources inside -your Kubernetes cluster. For example: +The Amazon Resource Name (ARN) for that CMK can be retrieved. ```ts -cluster.addManifest('Test', { - apiVersion: 'v1', - kind: 'ConfigMap', - metadata: { - name: 'myconfigmap', - }, - data: { - Key: 'value', - Another: '123454', - }, -}); +const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; ``` -At the minimum, when importing clusters for `kubectl` management, you will need -to specify: - -- `clusterName` - the name of the cluster. -- `kubectlRoleArn` - the ARN of an IAM role mapped to the `system:masters` RBAC - role. If the cluster you are importing was created using the AWS CDK, the - CloudFormation stack has an output that includes an IAM role that can be used. - Otherwise, you can create an IAM role and map it to `system:masters` manually. - The trust policy of this role should include the the - `arn:aws::iam::${accountId}:root` principal in order to allow the execution - role of the kubectl resource to assume it. - -If the cluster is configured with private-only or private and restricted public -Kubernetes [endpoint access](#endpoint-access), you must also specify: +## Permissions and Security -- `kubectlSecurityGroupId` - the ID of an EC2 security group that is allowed - connections to the cluster's control security group. For example, the EKS managed [cluster security group](#cluster-security-group). -- `kubectlPrivateSubnetIds` - a list of private VPC subnets IDs that will be used - to access the Kubernetes endpoint. +Amazon EKS provides several mechanism of securing the cluster and granting permissions to specific IAM users and roles. ### AWS IAM Mapping -As described in the [Amazon EKS User Guide](https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html), -you can map AWS IAM users and roles to [Kubernetes Role-based access control (RBAC)](https://kubernetes.io/docs/reference/access-authn-authz/rbac). +As described in the [Amazon EKS User Guide](https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html), you can map AWS IAM users and roles to [Kubernetes Role-based access control (RBAC)](https://kubernetes.io/docs/reference/access-authn-authz/rbac). -The Amazon EKS construct manages the **aws-auth ConfigMap** Kubernetes resource -on your behalf and exposes an API through the `cluster.awsAuth` for mapping +The Amazon EKS construct manages the *aws-auth* `ConfigMap` Kubernetes resource on your behalf and exposes an API through the `cluster.awsAuth` for mapping users, roles and accounts. -Furthermore, when auto-scaling capacity is added to the cluster (through -`cluster.addCapacity` or `cluster.addAutoScalingGroup`), the IAM instance role -of the auto-scaling group will be automatically mapped to RBAC so nodes can -connect to the cluster. No manual mapping is required. +Furthermore, when auto-scaling group capacity is added to the cluster, the IAM instance role of the auto-scaling group will be automatically mapped to RBAC so nodes can connect to the cluster. No manual mapping is required. -For example, let's say you want to grant an IAM user administrative privileges -on your cluster: +For example, let's say you want to grant an IAM user administrative privileges on your cluster: ```ts const adminUser = new iam.User(this, 'Admin'); @@ -615,10 +580,8 @@ cluster.awsAuth.addMastersRole(role) ### Cluster Security Group -When you create an Amazon EKS cluster, a -[cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) -is automatically created as well. This security group is designed to allow -all traffic from the control plane and managed node groups to flow freely +When you create an Amazon EKS cluster, a [cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) +is automatically created as well. This security group is designed to allow all traffic from the control plane and managed node groups to flow freely between each other. The ID for that security group can be retrieved after creating the cluster. @@ -627,55 +590,183 @@ The ID for that security group can be retrieved after creating the cluster. const clusterSecurityGroupId = cluster.clusterSecurityGroupId; ``` -### Cluster Encryption Configuration +### Node SSH Access -When you create an Amazon EKS cluster, envelope encryption of -Kubernetes secrets using the AWS Key Management Service (AWS KMS) can be enabled. The documentation -on [creating a cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html) -can provide more details about the customer master key (CMK) that can be used for the encryption. +If you want to be able to SSH into your worker nodes, you must already have an SSH key in the region you're connecting to and pass it when +you add capacity to the cluster. You must also be able to connect to the hosts (meaning they must have a public IP and you +should be allowed to connect to them on port 22): -You can use the `secretsEncryptionKey` to configure which key the cluster will use to encrypt Kubernetes secrets. By default, an AWS Managed key will be used. +See [SSH into nodes](test/example.ssh-into-nodes.lit.ts) for a code example. -> This setting can only be specified when the cluster is created and cannot be updated. +If you want to SSH into nodes in a private subnet, you should set up a bastion host in a public subnet. That setup is recommended, but is +unfortunately beyond the scope of this documentation. +### Service Accounts + +With services account you can provide Kubernetes Pods access to AWS resources. ```ts -const secretsKey = new kms.Key(this, 'SecretsKey'); -const cluster = new eks.Cluster(this, 'MyCluster', { - secretsEncryptionKey: secretsKey, - // ... +// add service account +const sa = cluster.addServiceAccount('MyServiceAccount'); + +const bucket = new Bucket(this, 'Bucket'); +bucket.grantReadWrite(serviceAccount); + +const mypod = cluster.addManifest('mypod', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'mypod' }, + spec: { + serviceAccountName: sa.serviceAccountName + containers: [ + { + name: 'hello', + image: 'paulbouwer/hello-kubernetes:1.5', + ports: [ { containerPort: 8080 } ], + + } + ] + } }); + +// create the resource after the service account. +mypod.node.addDependency(sa); + +// print the IAM role arn for this service account +new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: sa.role.roleArn }) ``` -The Amazon Resource Name (ARN) for that CMK can be retrieved. +Note that using `sa.serviceAccountName` above **does not** translate into a resource dependency. +This is why an explicit dependency is needed. See https://github.com/aws/aws-cdk/issues/9910 for more details. + +## Applying Kubernetes Resources + +The library supports several popular resource deployment mechanisms, among which are: + +### Kubernetes Manifests + +The `KubernetesManifest` construct or `cluster.addManifest` method can be used +to apply Kubernetes resource manifests to this cluster. + +> When using `cluster.addManifest`, the manifest construct is defined within the cluster's stack scope. If the manifest contains +> attributes from a different stack which depend on the cluster stack, a circular dependency will be created and you will get a synth time error. +> To avoid this, directly use `new KubernetesManifest` to create the manifest in the scope of the other stack. + +The following examples will deploy the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) +service on the cluster: ```ts -const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; +const appLabel = { app: "hello-kubernetes" }; + +const deployment = { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { name: "hello-kubernetes" }, + spec: { + replicas: 3, + selector: { matchLabels: appLabel }, + template: { + metadata: { labels: appLabel }, + spec: { + containers: [ + { + name: "hello-kubernetes", + image: "paulbouwer/hello-kubernetes:1.5", + ports: [ { containerPort: 8080 } ] + } + ] + } + } + } +}; + +const service = { + apiVersion: "v1", + kind: "Service", + metadata: { name: "hello-kubernetes" }, + spec: { + type: "LoadBalancer", + ports: [ { port: 80, targetPort: 8080 } ], + selector: appLabel + } +}; + +// option 1: use a construct +new KubernetesManifest(this, 'hello-kub', { + cluster, + manifest: [ deployment, service ] +}); + +// or, option2: use `addManifest` +cluster.addManifest('hello-kub', service, deployment); ``` -### Node ssh Access +#### Adding resources from a URL -If you want to be able to SSH into your worker nodes, you must already -have an SSH key in the region you're connecting to and pass it when you add capacity to the cluster. You must also be able to connect to the hosts (meaning they must have a public IP and you -should be allowed to connect to them on port 22): +The following example will deploy the resource manifest hosting on remote server: -See [SSH into nodes](test/example.ssh-into-nodes.lit.ts) for a code example. +```ts +import * as yaml from 'js-yaml'; +import * as request from 'sync-request'; -If you want to SSH into nodes in a private subnet, you should set up a -bastion host in a public subnet. That setup is recommended, but is -unfortunately beyond the scope of this documentation. +const manifestUrl = 'https://url/of/manifest.yaml'; +const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); +cluster.addManifest('my-resource', ...manifest); +``` + +#### Dependencies + +There are cases where Kubernetes resources must be deployed in a specific order. +For example, you cannot define a resource in a Kubernetes namespace before the +namespace was created. + +You can represent dependencies between `KubernetesManifest`s using +`resource.node.addDependency()`: + +```ts +const namespace = cluster.addManifest('my-namespace', { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { name: 'my-app' } +}); + +const service = cluster.addManifest('my-service', { + metadata: { + name: 'myservice', + namespace: 'my-app' + }, + spec: // ... +}); + +service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. +``` + +**NOTE:** when a `KubernetesManifest` includes multiple resources (either directly +or through `cluster.addManifest()`) (e.g. `cluster.addManifest('foo', r1, r2, +r3,...)`), these resources will be applied as a single manifest via `kubectl` +and will be applied sequentially (the standard behavior in `kubectl`). + +---------------------- + +Since Kubernetes manifests are implemented as CloudFormation resources in the +CDK. This means that if the manifest is deleted from your code (or the stack is +deleted), the next `cdk deploy` will issue a `kubectl delete` command and the +Kubernetes resources in that manifest will be deleted. + +#### Caveat + +If you have multiple resources in a single `KubernetesManifest`, and one of those **resources** is removed from the manifest, it will not be deleted and will remain orphan. See [Support Object pruning](https://github.com/aws/aws-cdk/issues/10495) for more details. ### Helm Charts -The `HelmChart` construct or `cluster.addChart` method can be used +The `HelmChart` construct or `cluster.addHelmChart` method can be used to add Kubernetes resources to this cluster using Helm. -> When using `cluster.addChart`, the manifest construct is defined within the cluster's stack scope. If the manifest contains +> When using `cluster.addHelmChart`, the manifest construct is defined within the cluster's stack scope. If the manifest contains > attributes from a different stack which depend on the cluster stack, a circular dependency will be created and you will get a synth time error. > To avoid this, directly use `new HelmChart` to create the chart in the scope of the other stack. -The following example will install the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/) -to your cluster using Helm. +The following example will install the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/) to your cluster using Helm. ```ts // option 1: use a construct @@ -686,8 +777,8 @@ new HelmChart(this, 'NginxIngress', { namespace: 'kube-system' }); -// or, option2: use `addChart` -cluster.addChart('NginxIngress', { +// or, option2: use `addHelmChart` +cluster.addHelmChart('NginxIngress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', namespace: 'kube-system' @@ -714,70 +805,114 @@ resource or if Helm charts depend on each other. You can use charts: ```ts -const chart1 = cluster.addChart(...); -const chart2 = cluster.addChart(...); +const chart1 = cluster.addHelmChart(...); +const chart2 = cluster.addHelmChart(...); chart2.node.addDependency(chart1); ``` -### Bottlerocket +## Patching Kubernetes Resources -[Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open-source operating system that is purpose-built by Amazon Web Services for running containers on virtual machines or bare metal hosts. At this moment the managed nodegroup only supports Amazon EKS-optimized AMI but it's possible to create a capacity of self-managed `AutoScalingGroup` running with bottlerocket Linux AMI. +The `KubernetesPatch` construct can be used to update existing kubernetes +resources. The following example can be used to patch the `hello-kubernetes` +deployment from the example above with 5 replicas. -> **NOTICE**: Bottlerocket is only available in [some supported AWS regions](https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-EKS.md#finding-an-ami). +```ts +new KubernetesPatch(this, 'hello-kub-deployment-label', { + cluster, + resourceName: "deployment/hello-kubernetes", + applyPatch: { spec: { replicas: 5 } }, + restorePatch: { spec: { replicas: 3 } } +}) +``` -The following example will create a capacity with self-managed Amazon EC2 capacity of 2 `t3.small` Linux instances running with `Bottlerocket` AMI. +## Querying Kubernetes Resources -```ts -// add bottlerocket nodes -cluster.addCapacity('BottlerocketNodes', { - instanceType: new ec2.InstanceType('t3.small'), - minCapacity: 2, - machineImageType: eks.MachineImageType.BOTTLEROCKET +The `KubernetesObjectValue` construct can be used to query for information about kubernetes objects, +and use that as part of your CDK application. + +For example, you can fetch the address of a [`LoadBalancer`](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) type service: + +```typescript +// query the load balancer address +const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute', { + cluster: cluster, + resourceType: 'service', + resourceName: 'my-service', + jsonPath: '.status.loadBalancer.ingress[0].hostname', // https://kubernetes.io/docs/reference/kubectl/jsonpath/ }); + +// pass the address to a lambda function +const proxyFunction = new lambda.Function(this, 'ProxyFunction', { + ... + environment: { + myServiceAddress: myServiceAddress.value + }, +}) ``` -The Bottlerocket AMI will be auto selected with the variant of different k8s version for the `x86_64` architecture. -For example, if the Amazon EKS cluster version is `1.17`, the Bottlerocket AMI variant will be auto selected as -`aws-k8s-1.17` behind the scene. See [Variants](https://github.com/bottlerocket-os/bottlerocket/blob/develop/README.md#variants) for more details. +Specifically, since the above use-case is quite common, there is an easier way to access that information: -To define only Bottlerocket capacity in your cluster, set `defaultCapacity` to `0` when you define the cluster as described above. +```typescript +const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('my-service'); +``` -Please note Bottlerocket does not allow to customize bootstrap options and `bootstrapOptions` properties is not supported when you create the `Bottlerocket` capacity. +## Using existing clusters -### Service Accounts +The Amazon EKS library allows defining Kubernetes resources such as [Kubernetes +manifests](#kubernetes-resources) and [Helm charts](#helm-charts) on clusters +that are not defined as part of your CDK app. -With services account you can provide Kubernetes Pods access to AWS resources. +First, you'll need to "import" a cluster to your CDK app. To do that, use the +`eks.Cluster.fromClusterAttributes()` static method: ```ts -// add service account -const sa = cluster.addServiceAccount('MyServiceAccount'); +const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { + clusterName: 'my-cluster-name', + kubectlRoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', +}); +``` -const bucket = new Bucket(this, 'Bucket'); -bucket.grantReadWrite(serviceAccount); +Then, you can use `addManifest` or `addHelmChart` to define resources inside +your Kubernetes cluster. For example: -const mypod = cluster.addManifest('mypod', { +```ts +cluster.addManifest('Test', { apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'mypod' }, - spec: { - serviceAccountName: sa.serviceAccountName - containers: [ - { - name: 'hello', - image: 'paulbouwer/hello-kubernetes:1.5', - ports: [ { containerPort: 8080 } ], - - } - ] - } + kind: 'ConfigMap', + metadata: { + name: 'myconfigmap', + }, + data: { + Key: 'value', + Another: '123454', + }, }); +``` -// create the resource after the service account. -// note that using `sa.serviceAccountName` above **does not** translate into a dependency. -// this is why an explicit dependency is needed. See https://github.com/aws/aws-cdk/issues/9910 for more details. -mypod.node.addDependency(sa); +At the minimum, when importing clusters for `kubectl` management, you will need +to specify: -// print the IAM role arn for this service account -new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: sa.role.roleArn }) -``` \ No newline at end of file +- `clusterName` - the name of the cluster. +- `kubectlRoleArn` - the ARN of an IAM role mapped to the `system:masters` RBAC + role. If the cluster you are importing was created using the AWS CDK, the + CloudFormation stack has an output that includes an IAM role that can be used. + Otherwise, you can create an IAM role and map it to `system:masters` manually. + The trust policy of this role should include the the + `arn:aws::iam::${accountId}:root` principal in order to allow the execution + role of the kubectl resource to assume it. + +If the cluster is configured with private-only or private and restricted public +Kubernetes [endpoint access](#endpoint-access), you must also specify: + +- `kubectlSecurityGroupId` - the ID of an EC2 security group that is allowed + connections to the cluster's control security group. For example, the EKS managed [cluster security group](#cluster-security-group). +- `kubectlPrivateSubnetIds` - a list of private VPC subnets IDs that will be used + to access the Kubernetes endpoint. + +## Known Issues and Limitations + +- [One cluster per stack](https://github.com/aws/aws-cdk/issues/10073) +- [Object pruning](https://github.com/aws/aws-cdk/issues/10495) +- [Service Account dependencies](https://github.com/aws/aws-cdk/issues/9910) +- [Attach all Lambda Functions to VPC](https://github.com/aws/aws-cdk/issues/9509) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index c753b7b81f784..979e863372ec8 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -126,7 +126,7 @@ export interface ICluster extends IResource, ec2.IConnectable { * @param options options of this chart. * @returns a `HelmChart` construct */ - addChart(id: string, options: HelmChartOptions): HelmChart; + addHelmChart(id: string, options: HelmChartOptions): HelmChart; } /** @@ -507,7 +507,7 @@ export interface ClusterProps extends ClusterOptions { * Instance type can be configured through `defaultCapacityInstanceType`, * which defaults to `m5.large`. * - * Use `cluster.addCapacity` to add additional customized capacity. Set this + * Use `cluster.addAutoScalingGroupCapacity` to add additional customized capacity. Set this * to `0` is you wish to avoid the initial capacity allocation. * * @default 2 @@ -609,7 +609,7 @@ abstract class ClusterBase extends Resource implements ICluster { * @param options options of this chart. * @returns a `HelmChart` construct */ - public addChart(id: string, options: HelmChartOptions): HelmChart { + public addHelmChart(id: string, options: HelmChartOptions): HelmChart { return new HelmChart(this, `chart-${id}`, { cluster: this, ...options }); } } @@ -984,10 +984,10 @@ export class Cluster extends ClusterBase { if (minCapacity > 0) { const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE; this.defaultCapacity = props.defaultCapacityType === DefaultCapacityType.EC2 ? - this.addCapacity('DefaultCapacity', { instanceType, minCapacity }) : undefined; + this.addAutoScalingGroupCapacity('DefaultCapacity', { instanceType, minCapacity }) : undefined; this.defaultNodegroup = props.defaultCapacityType !== DefaultCapacityType.EC2 ? - this.addNodegroup('DefaultCapacity', { instanceType, minSize: minCapacity }) : undefined; + this.addNodegroupCapacity('DefaultCapacity', { instanceType, minSize: minCapacity }) : undefined; } const outputConfigCommand = props.outputConfigCommand === undefined ? true : props.outputConfigCommand; @@ -1036,7 +1036,7 @@ export class Cluster extends ClusterBase { * daemon will be installed on all spot instances to handle * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). */ - public addCapacity(id: string, options: CapacityOptions): autoscaling.AutoScalingGroup { + public addAutoScalingGroupCapacity(id: string, options: AutoScalingGroupCapacityOptions): autoscaling.AutoScalingGroup { if (options.machineImageType === MachineImageType.BOTTLEROCKET && options.bootstrapOptions !== undefined ) { throw new Error('bootstrapOptions is not supported for Bottlerocket'); } @@ -1056,7 +1056,7 @@ export class Cluster extends ClusterBase { instanceType: options.instanceType, }); - this.addAutoScalingGroup(asg, { + this.connectAutoScalingGroupCapacity(asg, { mapRole: options.mapRole, bootstrapOptions: options.bootstrapOptions, bootstrapEnabled: options.bootstrapEnabled, @@ -1079,7 +1079,7 @@ export class Cluster extends ClusterBase { * @param id The ID of the nodegroup * @param options options for creating a new nodegroup */ - public addNodegroup(id: string, options?: NodegroupOptions): Nodegroup { + public addNodegroupCapacity(id: string, options?: NodegroupOptions): Nodegroup { return new Nodegroup(this, `Nodegroup${id}`, { cluster: this, ...options, @@ -1087,7 +1087,7 @@ export class Cluster extends ClusterBase { } /** - * Add compute capacity to this EKS cluster in the form of an AutoScalingGroup + * Connect capacity in the form of an existing AutoScalingGroup to the EKS cluster. * * The AutoScalingGroup must be running an EKS-optimized AMI containing the * /etc/eks/bootstrap.sh script. This method will configure Security Groups, @@ -1100,13 +1100,13 @@ export class Cluster extends ClusterBase { * daemon will be installed on all spot instances to handle * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). * - * Prefer to use `addCapacity` if possible. + * Prefer to use `addAutoScalingGroupCapacity` if possible. * * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html * @param autoScalingGroup [disable-awslint:ref-via-interface] * @param options options for adding auto scaling groups, like customizing the bootstrap script */ - public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions) { + public connectAutoScalingGroupCapacity(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions) { // self rules autoScalingGroup.connections.allowInternally(ec2.Port.allTraffic()); @@ -1333,7 +1333,7 @@ export class Cluster extends ClusterBase { */ private addSpotInterruptHandler() { if (!this._spotInterruptHandler) { - this._spotInterruptHandler = this.addChart('spot-interrupt-handler', { + this._spotInterruptHandler = this.addHelmChart('spot-interrupt-handler', { chart: 'aws-node-termination-handler', version: '0.9.5', repository: 'https://aws.github.io/eks-charts', @@ -1430,7 +1430,7 @@ export class Cluster extends ClusterBase { /** * Options for adding worker nodes */ -export interface CapacityOptions extends autoscaling.CommonAutoScalingGroupProps { +export interface AutoScalingGroupCapacityOptions extends autoscaling.CommonAutoScalingGroupProps { /** * Instance type of the instances to start */ diff --git a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts index 348adff45f6df..840f4f0c1ec6d 100644 --- a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts @@ -4,7 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as ssm from '@aws-cdk/aws-ssm'; import { Annotations, CfnOutput, Construct, Resource, Stack, Token, Tags } from '@aws-cdk/core'; -import { ICluster, ClusterAttributes, KubernetesVersion, NodeType, DefaultCapacityType, EksOptimizedImage, CapacityOptions, MachineImageType, AutoScalingGroupOptions, CommonClusterOptions } from './cluster'; +import { ICluster, ClusterAttributes, KubernetesVersion, NodeType, DefaultCapacityType, EksOptimizedImage, AutoScalingGroupCapacityOptions, MachineImageType, AutoScalingGroupOptions, CommonClusterOptions } from './cluster'; import { clusterArnComponents } from './cluster-resource'; import { CfnCluster, CfnClusterProps } from './eks.generated'; import { HelmChartOptions, HelmChart } from './helm-chart'; @@ -251,7 +251,7 @@ export class LegacyCluster extends Resource implements ICluster { * * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. */ - public addCapacity(id: string, options: CapacityOptions): autoscaling.AutoScalingGroup { + public addCapacity(id: string, options: AutoScalingGroupCapacityOptions): autoscaling.AutoScalingGroup { if (options.machineImageType === MachineImageType.BOTTLEROCKET && options.bootstrapOptions !== undefined ) { throw new Error('bootstrapOptions is not supported for Bottlerocket'); } @@ -366,7 +366,7 @@ export class LegacyCluster extends Resource implements ICluster { throw new Error('legacy cluster does not support adding kubernetes manifests'); } - public addChart(_id: string, _options: HelmChartOptions): HelmChart { + public addHelmChart(_id: string, _options: HelmChartOptions): HelmChart { throw new Error('legacy cluster does not support adding helm charts'); } @@ -424,7 +424,7 @@ class ImportedCluster extends Resource implements ICluster { throw new Error('legacy cluster does not support adding kubernetes manifests'); } - public addChart(_id: string, _options: HelmChartOptions): HelmChart { + public addHelmChart(_id: string, _options: HelmChartOptions): HelmChart { throw new Error('legacy cluster does not support adding helm charts'); } diff --git a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts index f65f4f844c2c8..9d94eef7d55fe 100644 --- a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts @@ -59,7 +59,7 @@ export interface NodegroupRemoteAccess { /** * Launch template property specification */ -export interface LaunchTemplate { +export interface LaunchTemplateSpec { /** * The Launch template ID */ @@ -177,11 +177,11 @@ export interface NodegroupOptions { */ readonly tags?: { [name: string]: string }; /** - * Launch template used for the nodegroup + * Launch template specification used for the nodegroup * @see - https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html * @default - no launch template */ - readonly launchTemplate?: LaunchTemplate; + readonly launchTemplateSpec?: LaunchTemplateSpec; } /** @@ -290,7 +290,7 @@ export class Nodegroup extends Resource implements INodegroup { tags: props.tags, }); - if (props.launchTemplate) { + if (props.launchTemplateSpec) { if (props.diskSize) { // see - https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html // and https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-nodegroup.html#cfn-eks-nodegroup-disksize @@ -303,8 +303,8 @@ export class Nodegroup extends Resource implements INodegroup { } // TODO: update this when the L1 resource spec is updated. resource.addPropertyOverride('LaunchTemplate', { - Id: props.launchTemplate.id, - Version: props.launchTemplate.version, + Id: props.launchTemplateSpec.id, + Version: props.launchTemplateSpec.version, }); } diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 913aaeb024388..57aee0952b133 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -114,7 +114,7 @@ ] }, "stability": "experimental", - "maturity": "experimental", + "maturity": "developer-preview", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index 3e33c697c1c45..9b4e03ad40ebe 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -14,7 +14,7 @@ class EksClusterStack extends cdk.Stack { }); /// !show - const asg = cluster.addCapacity('Nodes', { + const asg = cluster.addAutoScalingGroupCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, keyName: 'my-key-name', diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index 7f399939344f9..689d6cee92068 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -87,7 +87,7 @@ class EksClusterStack extends TestStack { }, }); - const nginxIngress = this.cluster.addChart('nginx-ingress', { + const nginxIngress = this.cluster.addHelmChart('nginx-ingress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', namespace: 'nginx', @@ -103,7 +103,7 @@ class EksClusterStack extends TestStack { } private assertSimpleHelmChart() { // deploy the Kubernetes dashboard through a helm chart - this.cluster.addChart('dashboard', { + this.cluster.addHelmChart('dashboard', { chart: 'kubernetes-dashboard', repository: 'https://kubernetes.github.io/dashboard/', }); @@ -114,7 +114,7 @@ class EksClusterStack extends TestStack { } private assertNodeGroupX86() { // add a extra nodegroup - this.cluster.addNodegroup('extra-ng', { + this.cluster.addNodegroupCapacity('extra-ng', { instanceType: new ec2.InstanceType('t3.small'), minSize: 1, // reusing the default capacity nodegroup instance role when available @@ -135,11 +135,11 @@ class EksClusterStack extends TestStack { userData: Fn.base64(userData.render()), }, }); - this.cluster.addNodegroup('extra-ng2', { + this.cluster.addNodegroupCapacity('extra-ng2', { minSize: 1, // reusing the default capacity nodegroup instance role when available nodeRole: this.cluster.defaultNodegroup?.role || this.cluster.defaultCapacity?.role, - launchTemplate: { + launchTemplateSpec: { id: lt.ref, version: lt.attrDefaultVersionNumber, }, @@ -147,7 +147,7 @@ class EksClusterStack extends TestStack { } private assertNodeGroupArm() { // add a extra nodegroup - this.cluster.addNodegroup('extra-ng-arm', { + this.cluster.addNodegroupCapacity('extra-ng-arm', { instanceType: new ec2.InstanceType('m6g.medium'), minSize: 1, // reusing the default capacity nodegroup instance role when available @@ -156,14 +156,14 @@ class EksClusterStack extends TestStack { } private assertInferenceInstances() { // inference instances - this.cluster.addCapacity('InferenceInstances', { + this.cluster.addAutoScalingGroupCapacity('InferenceInstances', { instanceType: new ec2.InstanceType('inf1.2xlarge'), minCapacity: 1, }); } private assertSpotCapacity() { // spot instances (up to 10) - this.cluster.addCapacity('spot', { + this.cluster.addAutoScalingGroupCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), maxCapacity: 10, @@ -175,7 +175,7 @@ class EksClusterStack extends TestStack { } private assertBottlerocket() { // add bottlerocket nodes - this.cluster.addCapacity('BottlerocketNodes', { + this.cluster.addAutoScalingGroupCapacity('BottlerocketNodes', { instanceType: new ec2.InstanceType('t3.small'), minCapacity: 2, machineImageType: eks.MachineImageType.BOTTLEROCKET, @@ -185,7 +185,7 @@ class EksClusterStack extends TestStack { private assertCapacityX86() { // add some x86_64 capacity to the cluster. The IAM instance role will // automatically be mapped via aws-auth to allow nodes to join the cluster. - this.cluster.addCapacity('Nodes', { + this.cluster.addAutoScalingGroupCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), minCapacity: 3, }); @@ -194,7 +194,7 @@ class EksClusterStack extends TestStack { private assertCapacityArm() { // add some arm64 capacity to the cluster. The IAM instance role will // automatically be mapped via aws-auth to allow nodes to join the cluster. - this.cluster.addCapacity('NodesArm', { + this.cluster.addAutoScalingGroupCapacity('NodesArm', { instanceType: new ec2.InstanceType('m6g.medium'), minCapacity: 1, }); diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index 0ae28ba00b2c7..519f2c1415b06 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -234,7 +234,7 @@ export = { // GIVEN const { stack } = testFixtureNoVpc(); const cluster = new Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); - cluster.addNodegroup('NG'); + cluster.addNodegroupCapacity('NG'); const role = iam.Role.fromRoleArn(stack, 'imported-role', 'arn:aws:iam::123456789012:role/S3Access'); // WHEN diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index cd32b5ce42566..c8b646b059012 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -239,7 +239,7 @@ export = { const capacityStack = new CapacityStack(app, 'CapacityStack', { cluster: clusterStack.eksCluster }); try { - clusterStack.eksCluster.addAutoScalingGroup(capacityStack.group, {}); + clusterStack.eksCluster.connectAutoScalingGroupCapacity(capacityStack.group, {}); test.ok(false, 'expected error'); } catch (err) { test.equal(err.message, 'CapacityStackautoScalingInstanceRoleF041EB53 should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); @@ -472,7 +472,7 @@ export = { }); // WHEN - cluster.addCapacity('Default', { + cluster.addAutoScalingGroupCapacity('Default', { instanceType: new ec2.InstanceType('t2.medium'), }); @@ -490,7 +490,7 @@ export = { }); // WHEN - cluster.addCapacity('Default', { + cluster.addAutoScalingGroupCapacity('Default', { instanceType: new ec2.InstanceType('t2.medium'), }); @@ -555,7 +555,7 @@ export = { }); // WHEN - cluster.addCapacity('Bottlerocket', { + cluster.addAutoScalingGroupCapacity('Bottlerocket', { instanceType: new ec2.InstanceType('t2.medium'), machineImageType: eks.MachineImageType.BOTTLEROCKET, }); @@ -587,7 +587,7 @@ export = { version: CLUSTER_VERSION, }); - test.throws(() => cluster.addCapacity('Bottlerocket', { + test.throws(() => cluster.addAutoScalingGroupCapacity('Bottlerocket', { instanceType: new ec2.InstanceType('t2.medium'), machineImageType: eks.MachineImageType.BOTTLEROCKET, bootstrapOptions: {}, @@ -785,7 +785,7 @@ export = { }); // WHEN - cluster.addCapacity('default', { + cluster.addAutoScalingGroupCapacity('default', { instanceType: new ec2.InstanceType('t2.nano'), }); @@ -825,7 +825,7 @@ export = { test.done(); }, - 'addCapacity will *not* map the IAM role if mapRole is false'(test: Test) { + 'addAutoScalingGroupCapacity will *not* map the IAM role if mapRole is false'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { @@ -835,7 +835,7 @@ export = { }); // WHEN - cluster.addCapacity('default', { + cluster.addAutoScalingGroupCapacity('default', { instanceType: new ec2.InstanceType('t2.nano'), mapRole: false, }); @@ -975,7 +975,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs') }); + cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs') }); // THEN const template = app.synth().getStackByName(stack.stackName).template; @@ -990,7 +990,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('MyCapcity', { + cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs'), bootstrapEnabled: false, }); @@ -1009,7 +1009,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('MyCapcity', { + cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs'), bootstrapOptions: { kubeletExtraArgs: '--node-labels FOO=42', @@ -1031,7 +1031,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('MyCapcity', { + cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs'), spotPrice: '0.01', }); @@ -1049,7 +1049,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('MyCapcity', { + cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlarge'), spotPrice: '0.01', }); @@ -1071,12 +1071,12 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('Spot1', { + cluster.addAutoScalingGroupCapacity('Spot1', { instanceType: new ec2.InstanceType('m3.xlarge'), spotPrice: '0.01', }); - cluster.addCapacity('Spot2', { + cluster.addAutoScalingGroupCapacity('Spot2', { instanceType: new ec2.InstanceType('m4.xlarge'), spotPrice: '0.01', }); @@ -1096,7 +1096,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN - test.throws(() => cluster.addCapacity('MyCapcity', { + test.throws(() => cluster.addAutoScalingGroupCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs'), bootstrapEnabled: false, bootstrapOptions: { awsApiRetryAttempts: 10 }, @@ -1174,7 +1174,7 @@ export = { defaultCapacity: 0, version: CLUSTER_VERSION, defaultCapacityInstance: new ec2.InstanceType('m6g.medium'), - }).addNodegroup('ng', { + }).addNodegroupCapacity('ng', { instanceType: new ec2.InstanceType('m6g.medium'), }); @@ -1185,7 +1185,7 @@ export = { test.done(); }, - 'EKS-Optimized AMI with GPU support when addCapacity'(test: Test) { + 'EKS-Optimized AMI with GPU support when addAutoScalingGroupCapacity'(test: Test) { // GIVEN const { app, stack } = testFixtureNoVpc(); @@ -1193,7 +1193,7 @@ export = { new eks.Cluster(stack, 'cluster', { defaultCapacity: 0, version: CLUSTER_VERSION, - }).addCapacity('GPUCapacity', { + }).addAutoScalingGroupCapacity('GPUCapacity', { instanceType: new ec2.InstanceType('g4dn.xlarge'), }); @@ -1206,7 +1206,7 @@ export = { test.done(); }, - 'EKS-Optimized AMI with ARM64 when addCapacity'(test: Test) { + 'EKS-Optimized AMI with ARM64 when addAutoScalingGroupCapacity'(test: Test) { // GIVEN const { app, stack } = testFixtureNoVpc(); @@ -1214,7 +1214,7 @@ export = { new eks.Cluster(stack, 'cluster', { defaultCapacity: 0, version: CLUSTER_VERSION, - }).addCapacity('ARMCapacity', { + }).addAutoScalingGroupCapacity('ARMCapacity', { instanceType: new ec2.InstanceType('m6g.medium'), }); @@ -1532,7 +1532,7 @@ export = { }); // WHEN - cluster.addChart('MyChart', { + cluster.addHelmChart('MyChart', { chart: 'foo', }); @@ -1636,7 +1636,7 @@ export = { const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN - cluster.addCapacity('InferenceInstances', { + cluster.addAutoScalingGroupCapacity('InferenceInstances', { instanceType: new ec2.InstanceType('inf1.2xlarge'), minCapacity: 1, }); diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts index 900fc852d4b94..b00a33012c5ae 100644 --- a/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts @@ -88,7 +88,7 @@ export = { // WHEN cluster.addManifest('foo', { bar: 2334 }); - cluster.addChart('helm', { chart: 'hello-world' }); + cluster.addHelmChart('helm', { chart: 'hello-world' }); // THEN expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index 35f1d7e6ce3fd..8f9409331d8a6 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -271,7 +271,7 @@ export = { }); // WHEN - cluster.addNodegroup('ng'); + cluster.addNodegroupCapacity('ng'); // THEN expect(stack).to(haveResourceLike('AWS::EKS::Nodegroup', { @@ -312,7 +312,7 @@ export = { version: CLUSTER_VERSION, }); // THEN - test.throws(() => cluster.addNodegroup('ng', { desiredSize: 3, maxSize: 2 }), /Desired capacity 3 can't be greater than max size 2/); + test.throws(() => cluster.addNodegroupCapacity('ng', { desiredSize: 3, maxSize: 2 }), /Desired capacity 3 can't be greater than max size 2/); test.done(); }, 'throws when desiredSize < minSize'(test: Test) { @@ -325,7 +325,7 @@ export = { version: CLUSTER_VERSION, }); // THEN - test.throws(() => cluster.addNodegroup('ng', { desiredSize: 2, minSize: 3 }), /Minimum capacity 3 can't be greater than desired size 2/); + test.throws(() => cluster.addNodegroupCapacity('ng', { desiredSize: 2, minSize: 3 }), /Minimum capacity 3 can't be greater than desired size 2/); test.done(); }, 'create nodegroup correctly with launch template'(test: Test) { @@ -351,8 +351,8 @@ export = { userData: cdk.Fn.base64(userData.render()), }, }); - cluster.addNodegroup('ng-lt', { - launchTemplate: { + cluster.addNodegroupCapacity('ng-lt', { + launchTemplateSpec: { id: lt.ref, version: lt.attrDefaultVersionNumber, }, @@ -400,9 +400,9 @@ export = { }); // THEN test.throws(() => - cluster.addNodegroup('ng-lt', { + cluster.addNodegroupCapacity('ng-lt', { diskSize: 100, - launchTemplate: { + launchTemplateSpec: { id: lt.ref, version: lt.attrDefaultVersionNumber, }, @@ -434,9 +434,9 @@ export = { }); // THEN test.throws(() => - cluster.addNodegroup('ng-lt', { + cluster.addNodegroupCapacity('ng-lt', { instanceType: new ec2.InstanceType('c5.large'), - launchTemplate: { + launchTemplateSpec: { id: lt.ref, version: lt.attrDefaultVersionNumber, }, From a3c41ae64893fb19da30cbd400de9e114cec0b5a Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 30 Sep 2020 00:50:02 +0200 Subject: [PATCH 35/46] feat(synthetics): syn-nodejs-2.0 runtime (#10574) `syn-nodejs-2.0` is the latest runtime. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html BREAKING CHANGE: the default runtime is now `syn-nodejs-2.0` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-synthetics/README.md | 6 ++++-- .../@aws-cdk/aws-synthetics/lib/canary.ts | 20 ++++++++++++++++--- .../test/integ.asset.expected.json | 4 ++-- .../test/integ.canary.expected.json | 2 +- .../aws-synthetics/test/integ.canary.ts | 3 +-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index 928ff8cf05976..655b9b0178253 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -76,9 +76,11 @@ The canary will automatically produce a CloudWatch Dashboard: ![UI Screenshot](images/ui-screenshot.png) -The Canary code will be executed in a lambda function created by Synthetics on your behalf. The Lambda function includes a custom [runtime](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html) provided by Synthetics. The provided runtime includes a variety of handy tools such as [Puppeteer](https://www.npmjs.com/package/puppeteer-core) and Chromium. To learn more about Synthetics capabilities, check out the [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html). +The Canary code will be executed in a lambda function created by Synthetics on your behalf. The Lambda function includes a custom [runtime](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html) provided by Synthetics. The provided runtime includes a variety of handy tools such as [Puppeteer](https://www.npmjs.com/package/puppeteer-core) and Chromium. The default runtime is `syn-nodejs-2.0`. -### Configuring the Canary Script +To learn more about Synthetics capabilities, check out the [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html). + +### Configuring the Canary Script To configure the script the canary executes, use the `test` property. The `test` property accepts a `Test` instance that can be initialized by the `Test` class static methods. Currently, the only implemented method is `Test.custom()`, which allows you to bring your own code. In the future, other methods will be added. `Test.custom()` accepts `code` and `handler` properties -- both are required by Synthetics to create a lambda function on your behalf. diff --git a/packages/@aws-cdk/aws-synthetics/lib/canary.ts b/packages/@aws-cdk/aws-synthetics/lib/canary.ts index d3f3d8ee16b43..23ab4a3bd8fc7 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/canary.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/canary.ts @@ -75,9 +75,21 @@ export class Runtime { * - Lambda runtime Node.js 10.x * - Puppeteer-core version 1.14.0 * - The Chromium version that matches Puppeteer-core 1.14.0 + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html#CloudWatch_Synthetics_runtimeversion-1.0 */ public static readonly SYNTHETICS_1_0 = new Runtime('syn-1.0'); + /** + * `syn-nodejs-2.0` includes the following: + * - Lambda runtime Node.js 10.x + * - Puppeteer-core version 3.3.0 + * - Chromium version 81.0.4044.0 + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html#CloudWatch_Synthetics_runtimeversion-2.0 + */ + public static readonly SYNTHETICS_NODEJS_2_0 = new Runtime('syn-nodejs-2.0'); + /** * @param name The name of the runtime version */ @@ -180,9 +192,11 @@ export interface CanaryProps { readonly canaryName?: string; /** - * Specify the runtime version to use for the canary. Currently, the only valid value is `Runtime.SYNTHETICS_1.0`. + * Specify the runtime version to use for the canary. + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html * - * @default Runtime.SYNTHETICS_1_0 + * @default Runtime.SYNTHETICS_NODEJS_2_0 */ readonly runtime?: Runtime; @@ -246,7 +260,7 @@ export class Canary extends cdk.Resource { artifactS3Location: this.artifactsBucket.s3UrlForObject(props.artifactsBucketLocation?.prefix), executionRoleArn: this.role.roleArn, startCanaryAfterCreation: props.startAfterCreation ?? true, - runtimeVersion: props.runtime?.name ?? Runtime.SYNTHETICS_1_0.name, + runtimeVersion: props.runtime?.name ?? Runtime.SYNTHETICS_NODEJS_2_0.name, name: this.physicalName, schedule: this.createSchedule(props), failureRetentionPeriod: props.failureRetentionPeriod?.toDays(), diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json index 62cee1d8660c7..7d614f08201b7 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json +++ b/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json @@ -148,7 +148,7 @@ ] }, "Name": "assetcanary-one", - "RuntimeVersion": "syn-1.0", + "RuntimeVersion": "syn-nodejs-2.0", "Schedule": { "DurationInSeconds": "0", "Expression": "rate(5 minutes)" @@ -304,7 +304,7 @@ ] }, "Name": "assetcanary-two", - "RuntimeVersion": "syn-1.0", + "RuntimeVersion": "syn-nodejs-2.0", "Schedule": { "DurationInSeconds": "0", "Expression": "rate(5 minutes)" diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json index fb21d4808e26e..7e722a0bdb74c 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json @@ -103,7 +103,7 @@ ] }, "Name": "canary-integ", - "RuntimeVersion": "syn-1.0", + "RuntimeVersion": "syn-nodejs-2.0", "Schedule": { "DurationInSeconds": "0", "Expression": "rate(1 minute)" diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts index 846361d82d7d7..35e7dd1cc44f4 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts @@ -24,9 +24,8 @@ new synthetics.Canary(stack, 'MyCanary', { console.log(\'hello world\'); };`), }), - runtime: synthetics.Runtime.SYNTHETICS_1_0, schedule: synthetics.Schedule.rate(cdk.Duration.minutes(1)), artifactsBucketLocation: { bucket, prefix }, }); -app.synth(); \ No newline at end of file +app.synth(); From ee0db39109824d3a511372c3337e0812e7a5fee9 Mon Sep 17 00:00:00 2001 From: Ross Date: Wed, 30 Sep 2020 00:16:01 +0100 Subject: [PATCH 36/46] feat(s3): support replication and restore s3 notification event types (#10552) Adding support for [additional S3 notification event types](https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#supported-notification-event-types), including 's3:ObjectRestore:Completed' recently raised by @mauricioharley as a feature request. Closes #10498 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-s3/lib/bucket.ts | 51 ++++++++++++++++++- .../@aws-cdk/aws-s3/test/test.notification.ts | 15 +++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 99ecca8402211..1e2357c4d840c 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1322,7 +1322,7 @@ export class Bucket extends BucketBase { } /** - * Subscribes a destination to receive notificatins when an object is + * Subscribes a destination to receive notifications when an object is * created in the bucket. This is identical to calling * `onEvent(EventType.ObjectCreated)`. * @@ -1334,7 +1334,7 @@ export class Bucket extends BucketBase { } /** - * Subscribes a destination to receive notificatins when an object is + * Subscribes a destination to receive notifications when an object is * removed from the bucket. This is identical to calling * `onEvent(EventType.ObjectRemoved)`. * @@ -1782,12 +1782,59 @@ export enum EventType { */ OBJECT_REMOVED_DELETE_MARKER_CREATED = 's3:ObjectRemoved:DeleteMarkerCreated', + /** + * Using restore object event types you can receive notifications for + * initiation and completion when restoring objects from the S3 Glacier + * storage class. + * + * You use s3:ObjectRestore:Post to request notification of object restoration + * initiation. + */ + OBJECT_RESTORE_POST = 's3:ObjectRestore:Post', + + /** + * Using restore object event types you can receive notifications for + * initiation and completion when restoring objects from the S3 Glacier + * storage class. + * + * You use s3:ObjectRestore:Completed to request notification of + * restoration completion. + */ + OBJECT_RESTORE_COMPLETED = 's3:ObjectRestore:Completed', + /** * You can use this event type to request Amazon S3 to send a notification * message when Amazon S3 detects that an object of the RRS storage class is * lost. */ REDUCED_REDUNDANCY_LOST_OBJECT = 's3:ReducedRedundancyLostObject', + + /** + * You receive this notification event when an object that was eligible for + * replication using Amazon S3 Replication Time Control failed to replicate. + */ + REPLICATION_OPERATION_FAILED_REPLICATION = 's3:Replication:OperationFailedReplication', + + /** + * You receive this notification event when an object that was eligible for + * replication using Amazon S3 Replication Time Control exceeded the 15-minute + * threshold for replication. + */ + REPLICATION_OPERATION_MISSED_THRESHOLD = 's3:Replication:OperationMissedThreshold', + + /** + * You receive this notification event for an object that was eligible for + * replication using the Amazon S3 Replication Time Control feature replicated + * after the 15-minute threshold. + */ + REPLICATION_OPERATION_REPLICATED_AFTER_THRESHOLD = 's3:Replication:OperationReplicatedAfterThreshold', + + /** + * You receive this notification event for an object that was eligible for + * replication using Amazon S3 Replication Time Control but is no longer tracked + * by replication metrics. + */ + REPLICATION_OPERATION_NOT_TRACKED = 's3:Replication:OperationNotTracked', } export interface NotificationKeyFilter { diff --git a/packages/@aws-cdk/aws-s3/test/test.notification.ts b/packages/@aws-cdk/aws-s3/test/test.notification.ts index 90107f917f375..1eb3708dc10c5 100644 --- a/packages/@aws-cdk/aws-s3/test/test.notification.ts +++ b/packages/@aws-cdk/aws-s3/test/test.notification.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import * as s3 from '../lib'; export = { - 'when notification are added, a custom resource is provisioned + a lambda handler for it'(test: Test) { + 'when notification is added a custom s3 bucket notification resource is provisioned'(test: Test) { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket'); @@ -17,7 +17,18 @@ export = { }); expect(stack).to(haveResource('AWS::S3::Bucket')); - expect(stack).to(haveResource('Custom::S3BucketNotifications')); + expect(stack).to(haveResource('Custom::S3BucketNotifications', { + NotificationConfiguration: { + TopicConfigurations: [ + { + Events: [ + 's3:ObjectCreated:*', + ], + TopicArn: 'ARN', + }, + ], + }, + })); test.done(); }, From 19dd454c79b1be670b7bd7a4a99fcbde154f658a Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 30 Sep 2020 00:41:52 +0100 Subject: [PATCH 37/46] fix(cloudfront): logging bucket uses regional domain name (#10570) According to the CloudFront docs, the logging bucket should be specified as the bucket domain name. #2554 updated origin buckets to use the regional bucket domain names -- which is correct -- but also incorrectly updated the logging bucket specifications as well. This has a minor impact of being unable to navigate to the logging bucket from the CloudFront console, but otherwise the logs are stored correctly. fixes #10512 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cloudfront/lib/distribution.ts | 2 +- packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts | 2 +- .../@aws-cdk/aws-cloudfront/test/distribution.test.ts | 8 ++++---- .../test/integ.cloudfront-bucket-logging.expected.json | 6 +++--- .../test/integ.distribution-extensive.expected.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 39624ab1c1da8..04e0de7784e81 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -417,7 +417,7 @@ export class Distribution extends Resource implements IDistribution { const bucket = props.logBucket ?? new s3.Bucket(this, 'LoggingBucket'); return { - bucket: bucket.bucketRegionalDomainName, + bucket: bucket.bucketDomainName, includeCookies: props.logIncludesCookies, prefix: props.logFilePrefix, }; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 6889e50739f7e..3edc8a914563e 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -899,7 +899,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu distributionConfig = { ...distributionConfig, logging: { - bucket: this.loggingBucket.bucketRegionalDomainName, + bucket: this.loggingBucket.bucketDomainName, includeCookies: props.loggingConfig.includeCookies || false, prefix: props.loggingConfig.prefix, }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 4a0bd425c2cc2..c36204b22df11 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -77,7 +77,7 @@ test('exhaustive example of props renders correctly', () => { HttpVersion: 'http1.1', IPV6Enabled: false, Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, IncludeCookies: true, Prefix: 'logs/', }, @@ -402,7 +402,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, }, }, }); @@ -419,7 +419,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyLoggingBucket4382CD04', 'RegionalDomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyLoggingBucket4382CD04', 'DomainName'] }, }, }, }); @@ -437,7 +437,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, IncludeCookies: true, Prefix: 'logs/', }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json index 36a334898a57f..5b6c0af340e28 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json @@ -36,7 +36,7 @@ "Bucket": { "Fn::GetAtt": [ "Bucket83908E77", - "RegionalDomainName" + "DomainName" ] }, "IncludeCookies": true, @@ -109,7 +109,7 @@ "Bucket": { "Fn::GetAtt": [ "AnAmazingWebsiteProbably2LoggingBucket222F7CE9", - "RegionalDomainName" + "DomainName" ] }, "IncludeCookies": false @@ -146,4 +146,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json index 587b65b5f98f9..150d936cd5565 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json @@ -25,7 +25,7 @@ "Bucket": { "Fn::GetAtt": [ "MyDistLoggingBucket9B8976BC", - "RegionalDomainName" + "DomainName" ] }, "IncludeCookies": true, From 9aea4ae2ceffbfd8b7f4a4944c37703dc0244e3e Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Wed, 30 Sep 2020 10:59:05 +0200 Subject: [PATCH 38/46] chore: ignore parcel updates in yarn-upgrade (#10591) We are currently on a pre-release version, and `ncu` will "upgrade" to the nightly builds, which is undesirable. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .github/workflows/yarn-upgrade.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index 214f38b73f988..0b7ddb189622a 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -43,17 +43,18 @@ jobs: - name: Run "ncu -u" # We special-case @types/node because we want to stay on the current major (minimum supported node release) # We special-case @types/fs-extra because the current major (9.x) is broken with @types/node >= 10 + # We special-case parcel because we are currently on a pre-release and don't want to move to nightlies # We special-case aws-sdk because of breaking changes with TS interface exports in recent minor versions - https://github.com/aws/aws-sdk-js/issues/3453 # We special-case typescript because it's not semantically versionned run: |- # Upgrade dependencies at repository root ncu --upgrade --filter=@types/node,@types/fs-extra --target=minor ncu --upgrade --filter=typescript --target=patch - ncu --upgrade --reject=@types/node,@types/fs-extra,typescript --target=minor + ncu --upgrade --reject=@types/node,@types/fs-extra,parcel,typescript --target=minor # Upgrade all the packages lerna exec --parallel ncu -- --upgrade --filter=@types/node,@types/fs-extra --target=minor lerna exec --parallel ncu -- --upgrade --filter=typescript --target=patch - lerna exec --parallel ncu -- --upgrade --reject='@types/node,@types/fs-extra,typescript,aws-sdk,${{ steps.list-packages.outputs.list }}' --target=minor + lerna exec --parallel ncu -- --upgrade --reject='@types/node,@types/fs-extra,parcel,typescript,aws-sdk,${{ steps.list-packages.outputs.list }}' --target=minor # This will create a brand new `yarn.lock` file (this is more efficient than `yarn install && yarn upgrade`) - name: Run "yarn install --force" run: yarn install --force From b42d4e9e8ef6fd5dc20d48ee0bb82482a312cd7c Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 30 Sep 2020 12:34:23 +0100 Subject: [PATCH 39/46] fix(core): properties set to false are not rendered in the template (#10539) Any CloudFormation resource that defines a single boolean property set to false is not rendered to the CloudFormation template. The bug is in implementation of `_toCloudFormation()` API in `CfnResource`. It treated `false` and `undefined` the same way. fixes #10455 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/cfn-resource.ts | 5 +++- .../@aws-cdk/core/test/test.cfn-resource.ts | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index f5049bdcd2326..4cb8934617c2b 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -306,7 +306,10 @@ export class CfnResource extends CfnRefElement { Condition: this.cfnOptions.condition && this.cfnOptions.condition.logicalId, }, props => { const renderedProps = this.renderProperties(props.Properties || {}); - props.Properties = renderedProps && (Object.values(renderedProps).find(v => !!v) ? renderedProps : undefined); + if (renderedProps) { + const hasDefined = Object.values(renderedProps).find(v => v !== undefined); + props.Properties = hasDefined !== undefined ? renderedProps : undefined; + } return deepMerge(props, this.rawOverrides); }), }, diff --git a/packages/@aws-cdk/core/test/test.cfn-resource.ts b/packages/@aws-cdk/core/test/test.cfn-resource.ts index 19bd494b485d5..03b615c464171 100644 --- a/packages/@aws-cdk/core/test/test.cfn-resource.ts +++ b/packages/@aws-cdk/core/test/test.cfn-resource.ts @@ -25,6 +25,30 @@ export = nodeunit.testCase({ test.done(); }, + + 'renders "Properties" for a resource that has only properties set to "false"'(test: nodeunit.Test) { + const app = new core.App(); + const stack = new core.Stack(app, 'TestStack'); + new core.CfnResource(stack, 'Resource', { + type: 'Test::Resource::Fake', + properties: { + FakeProperty: false, + }, + }); + + test.deepEqual(app.synth().getStackByName(stack.stackName).template, { + Resources: { + Resource: { + Type: 'Test::Resource::Fake', + Properties: { + FakeProperty: false, + }, + }, + }, + }); + + test.done(); + }, }, 'applyRemovalPolicy default includes Update policy'(test: nodeunit.Test) { From 0d4d44f01fc67bfc60e0b0994c7130ffbc4b65e5 Mon Sep 17 00:00:00 2001 From: Ross Date: Wed, 30 Sep 2020 13:35:49 +0100 Subject: [PATCH 40/46] feat(ec2): c6 graviton2 instance classes (#10558) Adding support for [EC2 C6g](https://aws.amazon.com/ec2/instance-types/c6/) instance classes. Closes #10372 --- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/README.md | 6 ++--- .../@aws-cdk/aws-ec2/lib/instance-types.ts | 22 +++++++++++++++++++ .../@aws-cdk/aws-ec2/test/instance.test.ts | 4 ++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 27dcabd8b69cd..2704685007a0e 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -281,8 +281,8 @@ const igwId = vpc.internetGatewayId; For a VPC with only `ISOLATED` subnets, this value will be undefined. -This is only supported for VPC's created in the stack - currently you're -unable to get the ID for imported VPC's. To do that you'd have to specifically +This is only supported for VPCs created in the stack - currently you're +unable to get the ID for imported VPCs. To do that you'd have to specifically look up the Internet Gateway by name, which would require knowing the name beforehand. @@ -700,7 +700,7 @@ ec2.CloudFormationInit.fromElements( ### Bastion Hosts A bastion host functions as an instance used to access servers and resources in a VPC without open up the complete VPC on a network level. -You can use bastion hosts using a standard SSH connection targetting port 22 on the host. As an alternative, you can connect the SSH connection +You can use bastion hosts using a standard SSH connection targeting port 22 on the host. As an alternative, you can connect the SSH connection feature of AWS Systems Manager Session Manager, which does not need an opened security group. (https://aws.amazon.com/about-aws/whats-new/2019/07/session-manager-launches-tunneling-support-for-ssh-and-scp/) A default bastion host for use via SSM can be configured like: diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 28b8c9ee46554..7ad1d2420aa26 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -198,6 +198,28 @@ export enum InstanceClass { */ C5N = 'c5n', + /** + * Compute optimized instances for high performance computing, 6th generation with Graviton2 processors + */ + COMPUTE6_GRAVITON2 = 'c6g', + + /** + * Compute optimized instances for high performance computing, 6th generation with Graviton2 processors + */ + C6G = 'c6g', + + /** + * Compute optimized instances for high performance computing, 6th generation with Graviton2 processors + * and local NVME drive + */ + COMPUTE6_GRAVITON2_NVME_DRIVE = 'c6gd', + + /** + * Compute optimized instances for high performance computing, 6th generation with Graviton2 processors + * and local NVME drive + */ + C6GD = 'c6gd', + /** * Storage-optimized instances, 2nd generation */ diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 18e84a910c4f4..f1a0da3ba62f9 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -19,12 +19,12 @@ nodeunitShim({ new Instance(stack, 'Instance', { vpc, machineImage: new AmazonLinuxImage(), - instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.LARGE), + instanceType: InstanceType.of(InstanceClass.COMPUTE6_GRAVITON2, InstanceSize.LARGE), }); // THEN cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { - InstanceType: 't3.large', + InstanceType: 'c6g.large', })); test.done(); From 44c1f208d4f284102bd5ce60025ab15c4af6081f Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 30 Sep 2020 18:32:37 +0530 Subject: [PATCH 41/46] fix(cli): listing same stack multiple times fails (#10554) Fixes listing same stack multiple times fails for cdk commands closes #9667 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts | 1 + packages/aws-cdk/test/api/cloud-assembly.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts index ca8bf10d96375..2ce9fef9b29ce 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts @@ -69,6 +69,7 @@ export class CloudAssembly { public async selectStacks(selectors: string[], options: SelectStacksOptions): Promise { selectors = selectors.filter(s => s != null); // filter null/undefined + selectors = [...new Set(selectors)]; // make them unique const stacks = this.assembly.stacks; if (stacks.length === 0) { diff --git a/packages/aws-cdk/test/api/cloud-assembly.test.ts b/packages/aws-cdk/test/api/cloud-assembly.test.ts index 3e192e3514c23..e0882882cc55e 100644 --- a/packages/aws-cdk/test/api/cloud-assembly.test.ts +++ b/packages/aws-cdk/test/api/cloud-assembly.test.ts @@ -60,6 +60,19 @@ test('select behavior: single', async () => { .rejects.toThrow('Since this app includes more than a single stack, specify which stacks to use (wildcards are supported)'); }); +test('select behavior: repeat', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const x = await cxasm.selectStacks(['withouterrors', 'withouterrors'], { + defaultBehavior: DefaultSelection.AllStacks, + }); + + // THEN + expect(x.stackCount).toBe(1); +}); + async function testCloudAssembly({ env }: { env?: string, versionReporting?: boolean } = {}) { const cloudExec = new MockCloudExecutable({ stacks: [{ From 7ce91e85b3eab19c0fbaf9f31f3b6f79827ff08f Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 30 Sep 2020 18:58:43 +0530 Subject: [PATCH 42/46] feat(batch): Importing a `JobDefinition` from name using `fromJobDefinitionName` (#10448) Add `fromJobDefinitionName` for `JobDefinition` construct. Ref: https://stackoverflow.com/a/55031183 closes #7172 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-batch/README.md | 19 +++++++++++++- .../@aws-cdk/aws-batch/lib/job-definition.ts | 25 +++++++++++++++++++ .../aws-batch/test/job-definition.test.ts | 10 ++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 314d647dec76e..c4a818e7abc6a 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -242,10 +242,27 @@ new batch.JobDefinition(stack, 'batch-job-def-from-local', { ### Importing an existing Job Definition -To import an existing batch job definition, call `JobDefinition.fromJobDefinitionArn()`. +#### From ARN + +To import an existing batch job definition from its ARN, call `JobDefinition.fromJobDefinitionArn()`. Below is an example: ```ts const job = batch.JobDefinition.fromJobDefinitionArn(this, 'imported-job-definition', 'arn:aws:batch:us-east-1:555555555555:job-definition/my-job-definition'); ``` + +#### From Name + +To import an existing batch job definition from its name, call `JobDefinition.fromJobDefinitionName()`. +If name is specified without a revision then the latest active revision is used. + +Below is an example: + +```ts +// Without revision +const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition'); + +// With revision +const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition:3'); +``` diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 4de1549bc86e0..a8d2259c08a55 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -269,6 +269,31 @@ export class JobDefinition extends Resource implements IJobDefinition { return new Import(scope, id); } + /** + * Imports an existing batch job definition by its name. + * If name is specified without a revision then the latest active revision is used. + * + * @param scope + * @param id + * @param jobDefinitionName + */ + public static fromJobDefinitionName(scope: Construct, id: string, jobDefinitionName: string): IJobDefinition { + const stack = Stack.of(scope); + const jobDefArn = stack.formatArn({ + service: 'batch', + resource: 'job-definition', + sep: '/', + resourceName: jobDefinitionName, + }); + + class Import extends Resource implements IJobDefinition { + public readonly jobDefinitionArn = jobDefArn; + public readonly jobDefinitionName = jobDefinitionName; + } + + return new Import(scope, id); + } + public readonly jobDefinitionArn: string; public readonly jobDefinitionName: string; private readonly imageConfig: JobDefinitionImageConfig; diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index de7dc08aea71d..71129c4906c9e 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -200,4 +200,14 @@ describe('Batch Job Definition', () => { expect(importedJob.jobDefinitionName).toEqual('job-def-name:1'); expect(importedJob.jobDefinitionArn).toEqual('arn:aws:batch:us-east-1:123456789012:job-definition/job-def-name:1'); }); + + test('can be imported from a name', () => { + // WHEN + const importedJob = batch.JobDefinition.fromJobDefinitionName(stack, 'job-def-clone', 'job-def-name'); + + // THEN + expect(importedJob.jobDefinitionName).toEqual('job-def-name'); + expect(importedJob.jobDefinitionArn) + .toEqual('arn:${Token[AWS.Partition.3]}:batch:${Token[AWS.Region.4]}:${Token[AWS.AccountId.0]}:job-definition/job-def-name'); + }); }); From 37e2c3580fdadc83edc57f077b8d2119189358c5 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 30 Sep 2020 19:23:41 +0530 Subject: [PATCH 43/46] feat(cognito): import existing user pool domain (#10550) Add fromDomainName import helper to UserPoolDomain construct closes #9988 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cognito/README.md | 7 +++++++ .../@aws-cdk/aws-cognito/lib/user-pool-domain.ts | 11 +++++++++++ .../aws-cognito/test/user-pool-domain.test.ts | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 4a2b87840ecbb..41eb58d9b0a22 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -566,3 +566,10 @@ const signInUrl = domain.signInUrl(client, { }) ``` +Exisiting domains can be imported into CDK apps using `UserPoolDomain.fromDomainName()` API + +```ts +const stack = new Stack(app, 'my-stack'); + +const myUserPoolDomain = UserPoolDomain.fromDomainName(stack, 'my-user-pool-domain', 'domain-name'); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts index 548ba52079fd9..a50f80fbf2804 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts @@ -80,6 +80,17 @@ export interface UserPoolDomainProps extends UserPoolDomainOptions { * Define a user pool domain */ export class UserPoolDomain extends Resource implements IUserPoolDomain { + /** + * Import a UserPoolDomain given its domain name + */ + public static fromDomainName(scope: Construct, id: string, userPoolDomainName: string): IUserPoolDomain { + class Import extends Resource implements IUserPoolDomain { + public readonly domainName = userPoolDomainName; + } + + return new Import(scope, id); + } + public readonly domainName: string; private isCognitoDomain: boolean; diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts index c7ec0d98ea75a..b981707a90ce6 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-domain.test.ts @@ -152,6 +152,18 @@ describe('User Pool Client', () => { expect(cfDomainNameSecond).toEqual(cfDomainNameFirst); }); + test('import', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const client = UserPoolDomain.fromDomainName(stack, 'Domain', 'domain-name-1'); + + // THEN + expect(client.domainName).toEqual('domain-name-1'); + expect(stack).not.toHaveResource('AWS::Cognito::UserPoolDomain'); + }); + describe('signInUrl', () => { test('returns the expected URL', () => { // GIVEN From d70808b42701d7bc24861e4c848b50def68c0b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20=C3=85str=C3=B6m?= Date: Wed, 30 Sep 2020 16:19:17 +0200 Subject: [PATCH 44/46] feat(lambda-event-sources): dead letter queue and filter policy for sns event source (#10567) This change allows `SnsEventSource` to be configured in the same way as its reverse construct, `LambdaSubscription`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda-event-sources/README.md | 6 +- .../aws-lambda-event-sources/lib/sns.ts | 13 +++- .../aws-lambda-event-sources/test/test.sns.ts | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 5d1287d0ace7f..452fba4bed43a 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -113,8 +113,12 @@ import * as sns from '@aws-cdk/aws-sns'; import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources'; const topic = new sns.Topic(...); +const deadLetterQueue = new sqs.Queue(this, 'deadLetterQueue'); -lambda.addEventSource(new SnsEventSource(topic)); +lambda.addEventSource(new SnsEventSource(topic, { + filterPolicy: { ... }, + deadLetterQueue: deadLetterQueue +})); ``` When a user calls the SNS Publish API on a topic that your Lambda function is diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts index cefd7fc20c286..e2f03ca2da4da 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts @@ -2,14 +2,23 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as subs from '@aws-cdk/aws-sns-subscriptions'; +/** + * Properties forwarded to the Lambda Subscription. + */ +export interface SnsEventSourceProps extends subs.LambdaSubscriptionProps { +} + /** * Use an Amazon SNS topic as an event source for AWS Lambda. */ export class SnsEventSource implements lambda.IEventSource { - constructor(readonly topic: sns.ITopic) { + private readonly props?: SnsEventSourceProps; + + constructor(readonly topic: sns.ITopic, props?: SnsEventSourceProps) { + this.props = props; } public bind(target: lambda.IFunction) { - this.topic.addSubscription(new subs.LambdaSubscription(target)); + this.topic.addSubscription(new subs.LambdaSubscription(target, this.props)); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts index 12224e95b6040..2c82ea19c42e0 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts @@ -1,5 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as sources from '../lib'; @@ -47,4 +48,67 @@ export = { test.done(); }, + + 'props are passed to subscription'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const topic = new sns.Topic(stack, 'T'); + const queue = new sqs.Queue(stack, 'Q'); + const props: sources.SnsEventSourceProps = { + deadLetterQueue: queue, + filterPolicy: { + Field: sns.SubscriptionFilter.stringFilter({ + whitelist: ['A', 'B'], + }), + }, + }; + + // WHEN + fn.addEventSource(new sources.SnsEventSource(topic, props)); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Permission', { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'Fn9270CBC0', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Ref': 'TD925BC7E', + }, + })); + + expect(stack).to(haveResource('AWS::SNS::Subscription', { + 'Endpoint': { + 'Fn::GetAtt': [ + 'Fn9270CBC0', + 'Arn', + ], + }, + 'Protocol': 'lambda', + 'TopicArn': { + 'Ref': 'TD925BC7E', + }, + 'FilterPolicy': { + 'Field': [ + 'A', + 'B', + ], + }, + 'RedrivePolicy': { + 'deadLetterTargetArn': { + 'Fn::GetAtt': [ + 'Q63C6E3AB', + 'Arn', + ], + }, + }, + })); + + test.done(); + }, }; From fbd48b2bf6fa9054d17bc6dc1b4cc57f237810a7 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Wed, 30 Sep 2020 07:45:09 -0700 Subject: [PATCH 45/46] fix(cognito): callback URLs are specified when OAuth is disabled for user pool clients (#10588) When the `UserPoolClient` property `disableOAuth` is set, callback URLs should not be rendered as it represents a list of allowed redirects for identity providers. Added in a condition that only renders the callback URL default of `https://example.com` if OAuth is enabled. Closes #10311 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cognito/lib/user-pool-client.ts | 2 +- .../aws-cognito/test/user-pool-client.test.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index b799eb2035fa1..2b785db4ce4e2 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -320,7 +320,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { explicitAuthFlows: this.configureAuthFlows(props), allowedOAuthFlows: props.disableOAuth ? undefined : this.configureOAuthFlows(), allowedOAuthScopes: props.disableOAuth ? undefined : this.configureOAuthScopes(props.oAuth), - callbackUrLs: callbackUrls && callbackUrls.length > 0 ? callbackUrls : undefined, + callbackUrLs: callbackUrls && callbackUrls.length > 0 && !props.disableOAuth ? callbackUrls : undefined, logoutUrLs: props.oAuth?.logoutUrls, allowedOAuthFlowsUserPoolClient: !props.disableOAuth, preventUserExistenceErrors: this.configurePreventUserExistenceErrors(props.preventUserExistenceErrors), diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 8266b73e61fbb..10e4dc2711f31 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -175,6 +175,23 @@ describe('User Pool Client', () => { }); }); + test('callbackUrls are not rendered if OAuth is disabled ', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolClient(stack, 'PoolClient', { + userPool: pool, + disableOAuth: true, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + CallbackURLs: ABSENT, + }); + }); + test('fails when callbackUrls is empty for codeGrant or implicitGrant', () => { const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); From c0a3cb4e28604d1e78a1020279cc6f2c9e888f2a Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 30 Sep 2020 20:40:33 +0530 Subject: [PATCH 46/46] fix(cognito): authFlows require refreshToken in all scenarios (#10561) refreshToken always allowed when authFlows present in UserPoolClient BREAKING CHANGE: `refreshToken` property is now removed from UserPoolClient. It will be included if any other `authFlow` is enabled. closes #7625 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cognito/lib/user-pool-client.ts | 22 +++++++++---------- .../integ.user-pool-client-explicit-props.ts | 1 - .../aws-cognito/test/user-pool-client.test.ts | 21 +++++++++++++++++- .../test/integ.cognito.lit.ts | 1 - 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 2b785db4ce4e2..ac67257b6a315 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -30,12 +30,6 @@ export interface AuthFlow { * @default false */ readonly userSrp?: boolean; - - /** - * Enable authflow to refresh tokens - * @default false - */ - readonly refreshToken?: boolean; } /** @@ -343,12 +337,18 @@ export class UserPoolClient extends Resource implements IUserPoolClient { } private configureAuthFlows(props: UserPoolClientProps): string[] | undefined { + if (!props.authFlows) return undefined; + const authFlows: string[] = []; - if (props.authFlows?.userPassword) { authFlows.push('ALLOW_USER_PASSWORD_AUTH'); } - if (props.authFlows?.adminUserPassword) { authFlows.push('ALLOW_ADMIN_USER_PASSWORD_AUTH'); } - if (props.authFlows?.custom) { authFlows.push('ALLOW_CUSTOM_AUTH'); } - if (props.authFlows?.userSrp) { authFlows.push('ALLOW_USER_SRP_AUTH'); } - if (props.authFlows?.refreshToken) { authFlows.push('ALLOW_REFRESH_TOKEN_AUTH'); } + if (props.authFlows.userPassword) { authFlows.push('ALLOW_USER_PASSWORD_AUTH'); } + if (props.authFlows.adminUserPassword) { authFlows.push('ALLOW_ADMIN_USER_PASSWORD_AUTH'); } + if (props.authFlows.custom) { authFlows.push('ALLOW_CUSTOM_AUTH'); } + if (props.authFlows.userSrp) { authFlows.push('ALLOW_USER_SRP_AUTH'); } + + // refreshToken should always be allowed if authFlows are present + if (authFlows.length > 0) { + authFlows.push('ALLOW_REFRESH_TOKEN_AUTH'); + } if (authFlows.length === 0) { return undefined; diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts index 06fbe5b373c81..dbad2591fc1bc 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.ts @@ -11,7 +11,6 @@ userpool.addClient('myuserpoolclient', { authFlows: { adminUserPassword: true, custom: true, - refreshToken: true, userPassword: true, userSrp: true, }, diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 10e4dc2711f31..081d9d9e6526b 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -78,7 +78,6 @@ describe('User Pool Client', () => { authFlows: { adminUserPassword: true, custom: true, - refreshToken: true, userPassword: true, userSrp: true, }, @@ -95,6 +94,26 @@ describe('User Pool Client', () => { }); }); + test('ExplicitAuthFlows makes refreshToken true by default', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + pool.addClient('Client', { + authFlows: { + userSrp: true, + }, + }); + + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolClient', { + ExplicitAuthFlows: [ + 'ALLOW_USER_SRP_AUTH', + 'ALLOW_REFRESH_TOKEN_AUTH', + ], + }); + }); + test('AllowedOAuthFlows are correctly named', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts index f9ab2c015b382..c68562ee6ffcb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts @@ -32,7 +32,6 @@ class CognitoStack extends Stack { generateSecret: true, authFlows: { userPassword: true, - refreshToken: true, }, oAuth: { flows: {