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/29] 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 635f0eda89ae0ae61fc2a562f7c097e61d647f29 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 22 Sep 2020 13:48:20 +0200 Subject: [PATCH 02/29] fix(cli): OS usernames cannot have Unicode characters (#10451) When assuming a role for uploading assets in the new-style synthesized stacks, the OS username was used to build the session name out of. OS usernames have a character set that is wider than the allowed characters in `RoleSessionName` though, so we needed to sanitize them. Fixes #10401. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 11 +++++- packages/aws-cdk/lib/api/aws-auth/sdk.ts | 10 +++++ .../aws-cdk/test/api/sdk-provider.test.ts | 39 +++++++++++++++++++ packages/cdk-assets/bin/publish.ts | 11 +++++- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 6b7522e4e09f2..d88d69406ca65 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -148,7 +148,7 @@ export class SdkProvider { params: { RoleArn: roleArn, ...externalId ? { ExternalId: externalId } : {}, - RoleSessionName: `aws-cdk-${os.userInfo().username}`, + RoleSessionName: `aws-cdk-${safeUsername()}`, }, stsConfig: { region, @@ -362,4 +362,13 @@ function readIfPossible(filename: string): string | undefined { debug(e); return undefined; } +} + +/** + * Return the username with characters invalid for a RoleSessionName removed + * + * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + */ +function safeUsername() { + return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); } \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index d735231836e27..28e24f19e988c 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -106,6 +106,16 @@ export class SDK implements ISDK { return { accountId, partition }; })); } + + /** + * Return the current credentials + * + * Don't use -- only used to write tests around assuming roles. + */ + public async currentCredentials(): Promise { + await this.credentials.getPromise(); + return this.credentials; + } } /** diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 83eb525d54771..70319a113f829 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -1,3 +1,4 @@ +import * as os from 'os'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import * as SDKMock from 'aws-sdk-mock'; @@ -271,6 +272,44 @@ describe('with default config files', () => { await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); }); + + test('assuming a role sanitizes the username into the session name', async () => { + // GIVEN + SDKMock.restore(); + + await withMocked(os, 'userInfo', async (userInfo) => { + userInfo.mockReturnValue({ username: 'skål', uid: 1, gid: 1, homedir: '/here', shell: '/bin/sh' }); + + await withMocked((new AWS.STS()).constructor.prototype, 'assumeRole', async (assumeRole) => { + let assumeRoleRequest; + + assumeRole.mockImplementation(function ( + this: any, + request: AWS.STS.Types.AssumeRoleRequest, + cb?: (err: Error | null, x: AWS.STS.Types.AssumeRoleResponse) => void) { + + // Part of the request is stored on "this" + assumeRoleRequest = { ...this.config.params, ...request }; + + const response = { + Credentials: { AccessKeyId: `${uid}aid`, Expiration: new Date(), SecretAccessKey: 's', SessionToken: '' }, + }; + if (cb) { cb(null, response); } + return { promise: () => Promise.resolve(response) }; + }); + + // WHEN + const provider = new SdkProvider(new AWS.CredentialProviderChain([() => new AWS.Credentials({ accessKeyId: 'a', secretAccessKey: 's' })]), 'eu-somewhere'); + const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); + + await sdk.currentCredentials(); + + expect(assumeRoleRequest).toEqual(expect.objectContaining({ + RoleSessionName: 'aws-cdk-sk@l', + })); + }); + }); + }); }); describe('Plugins', () => { diff --git a/packages/cdk-assets/bin/publish.ts b/packages/cdk-assets/bin/publish.ts index 12a0e318d0f78..20b7a609bfdd0 100644 --- a/packages/cdk-assets/bin/publish.ts +++ b/packages/cdk-assets/bin/publish.ts @@ -140,7 +140,7 @@ class DefaultAwsClient implements IAws { params: { RoleArn: roleArn, ExternalId: externalId, - RoleSessionName: `cdk-assets-${os.userInfo().username}`, + RoleSessionName: `cdk-assets-${safeUsername()}`, }, stsConfig: { region, @@ -149,3 +149,12 @@ class DefaultAwsClient implements IAws { }); } } + +/** + * Return the username with characters invalid for a RoleSessionName removed + * + * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + */ +function safeUsername() { + return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); +} \ No newline at end of file From 272363aed1a0056a490f105bc99677f7fdd8f2ca Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 22 Sep 2020 15:49:09 +0200 Subject: [PATCH 03/29] chore(core): AssetHashType.OUTPUT and improved JSDoc (#10473) Deprecate `AssetHashType.BUNDLE` in favor of `AssetHashType.OUTPUT`. Improve JSDoc for `AssetHashType`. Closes #9861 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda-nodejs/lib/bundling.ts | 2 +- .../aws-lambda-nodejs/test/bundling.test.ts | 12 +++--- packages/@aws-cdk/core/lib/asset-staging.ts | 5 ++- packages/@aws-cdk/core/lib/assets.ts | 17 ++++++++ packages/@aws-cdk/core/test/test.staging.ts | 40 ++++++++++++++++++- 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index f68123c4d36be..5160904878e11 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -212,7 +212,7 @@ export class Bundling { }); return lambda.Code.fromAsset(projectRoot, { - assetHashType: cdk.AssetHashType.BUNDLE, + assetHashType: cdk.AssetHashType.OUTPUT, bundling: { local: localBundler, ...dockerBundler.bundlingOptions, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 40fdb21ac60a8..654e3b49f9029 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -40,7 +40,7 @@ test('Parcel bundling', () => { // Correctly bundles with parcel expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ local: { props: expect.objectContaining({ @@ -93,7 +93,7 @@ test('Parcel bundling with handler named index.ts', () => { // Correctly bundles with parcel expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: [ 'bash', '-c', @@ -112,7 +112,7 @@ test('Parcel bundling with tsx handler', () => { // Correctly bundles with parcel expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: [ 'bash', '-c', @@ -152,7 +152,7 @@ test('Parcel bundling with externals and dependencies', () => { // Correctly bundles with parcel expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: [ 'bash', '-c', @@ -199,7 +199,7 @@ test('Detects yarn.lock', () => { // Correctly bundles with parcel expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: expect.arrayContaining([ expect.stringMatching(/yarn\.lock.+yarn install/), @@ -316,7 +316,7 @@ test('Custom bundling docker image', () => { }); expect(Code.fromAsset).toHaveBeenCalledWith('/project', { - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ image: { image: 'my-custom-image' }, }), diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index dc49750a46f2f..e5878c2a31365 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -113,7 +113,7 @@ export class AssetStaging extends Construct { this.relativePath = renderAssetFilename(this.assetHash); this.stagedPath = this.relativePath; } else { // Bundling is skipped - this.assetHash = props.assetHashType === AssetHashType.BUNDLE + this.assetHash = props.assetHashType === AssetHashType.BUNDLE || props.assetHashType === AssetHashType.OUTPUT ? this.calculateHash(AssetHashType.CUSTOM, this.node.path) // Use node path as dummy hash because we're not bundling : this.calculateHash(hashType, props.assetHash); this.stagedPath = this.sourcePath; @@ -295,8 +295,9 @@ export class AssetStaging extends Construct { case AssetHashType.SOURCE: return FileSystem.fingerprint(this.sourcePath, this.fingerprintOptions); case AssetHashType.BUNDLE: + case AssetHashType.OUTPUT: if (!this.bundleDir) { - throw new Error('Cannot use `AssetHashType.BUNDLE` when `bundling` is not specified.'); + throw new Error(`Cannot use \`${hashType}\` hash type when \`bundling\` is not specified.`); } return FileSystem.fingerprint(this.bundleDir, this.fingerprintOptions); default: diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index 012b7da4d489c..17d3b9d93e53f 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -60,18 +60,35 @@ export interface AssetOptions { /** * The type of asset hash + * + * NOTE: the hash is used in order to identify a specific revision of the asset, and + * used for optimizing and caching deployment activities related to this asset such as + * packaging, uploading to Amazon S3, etc. */ export enum AssetHashType { /** * Based on the content of the source path + * + * When bundling, use `SOURCE` when the content of the bundling output is not + * stable across repeated bundling operations. */ SOURCE = 'source', /** * Based on the content of the bundled path + * + * @deprecated use `OUTPUT` instead */ BUNDLE = 'bundle', + /** + * Based on the content of the bundling output + * + * Use `OUTPUT` when the source of the asset is a top level folder containing + * code and/or dependencies that are not directly linked to the asset. + */ + OUTPUT = 'output', + /** * Use a custom hash */ diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index ff7279172bb00..e652c884935f4 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -425,6 +425,28 @@ export = { test.done(); }, + 'bundling with OUTPUT asset hash type'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const asset = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + bundling: { + image: BundlingDockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + assetHashType: AssetHashType.OUTPUT, + }); + + // THEN + test.equal(asset.assetHash, '33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); + + test.done(); + }, + 'custom hash'(test: Test) { // GIVEN const app = new App(); @@ -474,7 +496,23 @@ export = { test.throws(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, assetHashType: AssetHashType.BUNDLE, - }), /Cannot use `AssetHashType.BUNDLE` when `bundling` is not specified/); + }), /Cannot use `bundle` hash type when `bundling` is not specified/); + test.equal(fs.existsSync(STUB_INPUT_FILE), false); + + test.done(); + }, + + 'throws with OUTPUT hash type and no bundling'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // THEN + test.throws(() => new AssetStaging(stack, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + }), /Cannot use `output` hash type when `bundling` is not specified/); test.equal(fs.existsSync(STUB_INPUT_FILE), false); test.done(); From dac1e12196c84b84820052f74b14c86a7d4dddc3 Mon Sep 17 00:00:00 2001 From: Daniel <3842788+dscpinheiro@users.noreply.github.com> Date: Tue, 22 Sep 2020 14:14:52 +0000 Subject: [PATCH 04/29] feat(lambda): kafka topic as an event source (#10445) Lambda recently added support for MSK as an event source (https://aws.amazon.com/about-aws/whats-new/2020/08/aws-lambda-now-supports-amazon-managed-streaming-for-apache-kafka-as-an-event-source/), and there's now a "Topics" property on the CloudFormation resource definition (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-topics). Closes https://github.com/aws/aws-cdk/issues/10138 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda/lib/event-source-mapping.ts | 16 ++++++++--- .../test/test.event-source-mapping.ts | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) 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 760aca4f71e4d..a9fbc97bdea0e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -17,7 +17,7 @@ export interface EventSourceMappingOptions { * * Valid Range: Minimum value of 1. Maximum value of 10000. * - * @default - Amazon Kinesis and Amazon DynamoDB is 100 records. + * @default - Amazon Kinesis, Amazon DynamoDB, and Amazon MSK is 100 records. * Both the default and maximum for Amazon SQS are 10 messages. */ readonly batchSize?: number; @@ -44,12 +44,12 @@ export interface EventSourceMappingOptions { readonly enabled?: boolean; /** - * The position in the DynamoDB or Kinesis stream where AWS Lambda should + * The position in the DynamoDB, Kinesis or MSK stream where AWS Lambda should * start reading. * * @see https://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html#Kinesis-GetShardIterator-request-ShardIteratorType * - * @default - Required for Amazon Kinesis and Amazon DynamoDB Streams sources. + * @default - Required for Amazon Kinesis, Amazon DynamoDB, and Amazon MSK Streams sources. */ readonly startingPosition?: StartingPosition; @@ -91,6 +91,13 @@ export interface EventSourceMappingOptions { * @default 1 */ readonly parallelizationFactor?: number; + + /** + * The name of the Kafka topic. + * + * @default - no topic + */ + readonly kafkaTopic?: string; } /** @@ -185,13 +192,14 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp maximumRecordAgeInSeconds: props.maxRecordAge?.toSeconds(), maximumRetryAttempts: props.retryAttempts, parallelizationFactor: props.parallelizationFactor, + topics: props.kafkaTopic !== undefined ? [props.kafkaTopic] : undefined, }); this.eventSourceMappingId = cfnEventSourceMapping.ref; } } /** - * The position in the DynamoDB or Kinesis stream where AWS Lambda should start + * The position in the DynamoDB, Kinesis or MSK stream where AWS Lambda should start * reading. */ export enum StartingPosition { diff --git a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts index 4de8108b609d2..bc9492f1d2f7e 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts @@ -1,3 +1,4 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { Code, EventSourceMapping, Function, Runtime } from '../lib'; @@ -185,4 +186,31 @@ export = { test.equals(imported.stack.stackName, 'test-stack'); test.done(); }, + + 'accepts if kafkaTopic is a parameter'(test: Test) { + const stack = new cdk.Stack(); + const topicNameParam = new cdk.CfnParameter(stack, 'TopicNameParam', { + type: 'String', + }); + + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + kafkaTopic: topicNameParam.valueAsString, + }); + + expect(stack).to(haveResourceLike('AWS::Lambda::EventSourceMapping', { + Topics: [{ + Ref: 'TopicNameParam', + }], + })); + + test.done(); + }, }; From bf3cc21c2d19cf344b706a4da2de939daded89a7 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Tue, 22 Sep 2020 21:02:55 +0530 Subject: [PATCH 05/29] feat: add configuration for GitHub CodeSpaces (#10470) Add `.devcontainer.json` referencing the existing `.gitpod.yml` for supporting GitHub codespaces closes #10447 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .devcontainer.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .devcontainer.json diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000000000..c40157671ce76 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "name": "Dev Container Definition - AWS CDK", + "image": "jsii/superchain", + "postCreateCommand": "yarn build --skip-test --no-bail --skip-prereqs --skip-compat", + "extensions": [ + "dbaeumer.vscode-eslint@2.1.5" + ] +} \ No newline at end of file From 2e93863e03a668dd3afd1d1ce7612be1b96c5514 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Wed, 23 Sep 2020 01:40:45 +0800 Subject: [PATCH 06/29] chore(rds): add additional aurora mysql engine versions (#10477) chore(rds): add additional aurora mysql engine versions Closes: #10476 ---- *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-engine.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index 76a542078958a..a25200ac22205 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -322,6 +322,10 @@ export class AuroraMysqlEngineVersion { public static readonly VER_2_08_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.08.0'); /** Version "5.7.mysql_aurora.2.08.1". */ public static readonly VER_2_08_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.08.1'); + /** Version "5.7.mysql_aurora.2.08.2". */ + public static readonly VER_2_08_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.08.2'); + /** Version "5.7.mysql_aurora.2.09.0". */ + public static readonly VER_2_09_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.0'); /** * Create a new AuroraMysqlEngineVersion with an arbitrary version. From 94bbabfff8b785656a40e85539f97ae8cb83c818 Mon Sep 17 00:00:00 2001 From: Alan Raison Date: Tue, 22 Sep 2020 20:28:43 +0100 Subject: [PATCH 07/29] fix(codepipeline-actions): use token as CodeCommitSourceAction branch (#10463) When using the EVENTS trigger, an event is created based on the branch name of the event, however this is not possible if the branch name is an unresolved value. Therefore generate a unique event name if this is the case. Fixes #10263 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/codecommit/source-action.ts | 26 +++++++++-- .../test.codecommit-source-action.ts | 45 ++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 2fa7a67b29b93..8f33d16ac8c41 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -2,7 +2,7 @@ import * as codecommit from '@aws-cdk/aws-codecommit'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; +import { Construct, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; @@ -122,8 +122,8 @@ export class CodeCommitSourceAction extends Action { const createEvent = this.props.trigger === undefined || this.props.trigger === CodeCommitTrigger.EVENTS; if (createEvent) { - const branchIdDisambiguator = this.branch === 'master' ? '' : `-${this.branch}-`; - this.props.repository.onCommit(`${stage.pipeline.node.uniqueId}${branchIdDisambiguator}EventRule`, { + const eventId = this.generateEventId(stage); + this.props.repository.onCommit(eventId, { target: new targets.CodePipeline(stage.pipeline), branches: [this.branch], }); @@ -153,4 +153,24 @@ export class CodeCommitSourceAction extends Action { }, }; } + + private generateEventId(stage: codepipeline.IStage): string { + const baseId = stage.pipeline.node.uniqueId; + if (Token.isUnresolved(this.branch)) { + let candidate = ''; + let counter = 0; + do { + candidate = this.eventIdFromPrefix(`${baseId}${counter}`); + counter += 1; + } while (this.props.repository.node.tryFindChild(candidate) !== undefined); + return candidate; + } else { + const branchIdDisambiguator = this.branch === 'master' ? '' : '-${this.branch}-'; + return this.eventIdFromPrefix(`${baseId}${branchIdDisambiguator}`); + } + } + + private eventIdFromPrefix(eventIdPrefix: string) { + return `${eventIdPrefix}EventRule`; + } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts index e62e301168fa1..fda7c79dc1800 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts @@ -2,7 +2,7 @@ import { countResources, expect, haveResourceLike, not } from '@aws-cdk/assert'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codecommit from '@aws-cdk/aws-codecommit'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import { Stack } from '@aws-cdk/core'; +import { Stack, Lazy } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; @@ -224,6 +224,49 @@ export = { test.done(); }, + + 'allows using a Token for the branch name'(test: Test) { + const stack = new Stack(); + + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(stack, 'P', { + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: new codecommit.Repository(stack, 'R', { + repositoryName: 'repository', + }), + branch: Lazy.stringValue({ produce: () => 'my-branch' }), + output: sourceOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'Build', + project: new codebuild.PipelineProject(stack, 'CodeBuild'), + input: sourceOutput, + }), + ], + }, + ], + }); + + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + EventPattern: { + detail: { + referenceName: ['my-branch'], + }, + }, + })); + + test.done(); + }, }, }; From 2e7cb95e644cc61f36314c35ac91648d23c499ec Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Tue, 22 Sep 2020 15:22:45 -0700 Subject: [PATCH 08/29] 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 09/29] 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 e8e350b737b73586506b2dd1d4b9fccbabb700f3 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 23 Sep 2020 14:44:25 +0300 Subject: [PATCH 10/29] chore(eks): readme touchups (#10496) A few readme touchups and clarifications. ---- *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 | 68 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index bf9200ce865ad..28f03e0d9644a 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -47,6 +47,8 @@ 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. @@ -98,7 +100,8 @@ const cluster = new eks.Cluster(this, 'hello-eks', { }); ``` -The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cluster endpoint is accessible from outside of your VPC, and worker node traffic to the endpoint will stay within your VPC. +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. ### Capacity @@ -139,16 +142,12 @@ new eks.Cluster(this, 'cluster-with-no-capacity', { }); ``` -The `cluster.defaultCapacity` property will reference the `AutoScalingGroup` -resource for the default capacity. It will be `undefined` if `defaultCapacity` -is set to `0` or `defaultCapacityType` is either `NODEGROUP` or undefined. +When creating a cluster with default capacity (i.e `defaultCapacity !== 0` or is undefined), you can access the allocated capacity using: -And the `cluster.defaultNodegroup` property will reference the `Nodegroup` -resource for the default capacity. It will be `undefined` if `defaultCapacity` -is set to `0` or `defaultCapacityType` is `EC2`. +- `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`. -You can add `AutoScalingGroup` resource as customized capacity through `cluster.addCapacity()` or -`cluster.addAutoScalingGroup()`: +You can add customized capacity in the form of an `AutoScalingGroup` resource through `cluster.addCapacity()` or `cluster.addAutoScalingGroup()`: ```ts cluster.addCapacity('frontend-nodes', { @@ -167,7 +166,7 @@ for Amazon EKS Kubernetes clusters. By default, `eks.Nodegroup` create a nodegro new eks.Nodegroup(stack, 'nodegroup', { cluster }); ``` -You can add customized node group through `cluster.addNodegroup()`: +You can add customized node groups through `cluster.addNodegroup()`: ```ts cluster.addNodegroup('nodegroup', { @@ -206,14 +205,13 @@ this.cluster.addNodegroup('extra-ng', { ### 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 +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 +// create a cluster with a default managed nodegroup cluster = new eks.Cluster(this, 'Cluster', { vpc, - mastersRole, version: eks.KubernetesVersion.V1_17, }); @@ -298,12 +296,9 @@ can cause your EC2 instance to become unavailable, such as [EC2 maintenance even 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 terminated. -Current version: - -| name | version | -|------------|---------| -| Helm Chart | 0.9.5 | -| App | 1.7.0 | +> Handler Version: [1.7.0](https://github.com/aws/aws-node-termination-handler/releases/tag/v1.7.0) +> +> Chart Version: [0.9.5](https://github.com/aws/eks-charts/blob/v0.0.28/stable/aws-node-termination-handler/Chart.yaml) ### Bootstrapping @@ -327,7 +322,7 @@ cluster.addCapacity('spot', { To disable bootstrapping altogether (i.e. to fully customize user-data), set `bootstrapEnabled` to `false` when you add the capacity. -### Kubernetes Resources +### Kubernetes Manifests The `KubernetesManifest` construct or `cluster.addManifest` method can be used to apply Kubernetes resource manifests to this cluster. @@ -387,7 +382,7 @@ cluster.addManifest('hello-kub', service, deployment); #### Kubectl Layer and Environment -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 useful in order to configure an http proxy: +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: ```typescript const cluster = new eks.Cluster(this, 'hello-eks', { @@ -450,10 +445,14 @@ const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); cluster.addManifest('my-resource', ...manifest); ``` -Since Kubernetes resources are implemented as CloudFormation resources in the -CDK. This means that if the resource is deleted from your code (or the stack is +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 will be deleted. +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 @@ -482,9 +481,9 @@ const service = cluster.addManifest('my-service', { service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. ``` -NOTE: when a `KubernetesManifest` includes multiple resources (either directly +**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` +r3,...)`), these resources will be applied as a single manifest via `kubectl` and will be applied sequentially (the standard behavior in `kubectl`). ### Patching Kubernetes Resources @@ -582,7 +581,7 @@ 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. + 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. @@ -598,7 +597,7 @@ 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 any longer. +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: @@ -657,11 +656,10 @@ const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; ### Node ssh Access 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, and you must -be able to connect to the hosts (meaning they must have a public IP and you +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): -[ssh into nodes example](test/example.ssh-into-nodes.lit.ts) +See [SSH into nodes](test/example.ssh-into-nodes.lit.ts) for a code example. 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 @@ -699,7 +697,7 @@ cluster.addChart('NginxIngress', { Helm charts will be installed and updated using `helm upgrade --install`, where a few parameters are being passed down (such as `repo`, `values`, `version`, `namespace`, `wait`, `timeout`, etc). This means that if the chart is added to CDK with the same release name, it will try to update -the chart in the cluster. The chart will exists as CloudFormation resource. +the chart in the cluster. Helm charts are implemented as CloudFormation resources in CDK. This means that if the chart is deleted from your code (or the stack is @@ -775,9 +773,11 @@ const mypod = cluster.addManifest('mypod', { } }); -// create the resource after the service account +// 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); // print the IAM role arn for this service account new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: sa.role.roleArn }) -``` +``` \ No newline at end of file From c17969926a2644fc9607dd9b5f105450ed64309a Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 23 Sep 2020 15:26:24 +0100 Subject: [PATCH 11/29] chore(awslint): rules permit constructs to extend from 'constructs' module (#10472) Introduce an environment variable - `AWSLINT_BASE_CONSTRUCT` recognized by `awslint`. This environment variable indicates that the module has [migrated][compat-rfc] away from construct classes and interfaces from `@aws-cdk/core` module to those in `constructs` module. Specific rules in the linter recognize this variable and modify their expectations. Motivation The primary motivation is to move the code base towards [removal of the construct compat layer][compat-rfc] as part of [CDKv2]. A large number of code changes to adopt "constructs" module can already be done as part of CDKv1 without incurring breaking changes to the API. This change enables these changes to be performed module-by-module. As modules are migrated, this flag will be enabled, to ensure no regression. [CDKv2]: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0079-cdk-2.0.md [compat-rfc]: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md ---- *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-policy.ts | 3 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 3 +- packages/@aws-cdk/aws-s3/lib/util.ts | 5 ++-- packages/@aws-cdk/aws-s3/package.json | 5 +++- packages/@aws-cdk/aws-s3/test/test.aspect.ts | 3 +- .../@aws-cdk/core/lib/construct-compat.ts | 2 +- .../lib/private/physical-name-generator.ts | 7 +++-- packages/@aws-cdk/core/lib/resource.ts | 5 ++-- packages/@aws-cdk/core/lib/stack.ts | 16 ++++++----- packages/@aws-cdk/core/lib/stage.ts | 5 ++-- packages/@aws-cdk/core/package.json | 1 + packages/awslint/lib/rules/construct.ts | 20 +++++++++++-- packages/awslint/lib/rules/core-types.ts | 28 +++++++++++++++++-- packages/awslint/lib/rules/imports.ts | 19 ++++++++++--- tools/cdk-build-tools/bin/cdk-build.ts | 9 +++--- tools/cdk-build-tools/lib/compile.ts | 7 +++-- tools/cdk-build-tools/lib/lint.ts | 7 +++-- tools/cdk-build-tools/lib/package-info.ts | 5 ++++ 18 files changed, 110 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts b/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts index 10f35b5c40e3d..395ff706b5fdc 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts @@ -1,5 +1,6 @@ import { PolicyDocument } from '@aws-cdk/aws-iam'; -import { Construct, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { RemovalPolicy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { IBucket } from './bucket'; import { CfnBucketPolicy } from './s3.generated'; diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index d44f989ba267b..99ecca8402211 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -2,7 +2,8 @@ import { EOL } from 'os'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Construct, Fn, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Fn, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { BucketPolicy } from './bucket-policy'; import { IBucketNotificationDestination } from './destination'; import { BucketNotifications } from './notifications-resource'; diff --git a/packages/@aws-cdk/aws-s3/lib/util.ts b/packages/@aws-cdk/aws-s3/lib/util.ts index 643fdc7472e1a..1c45ed899be4b 100644 --- a/packages/@aws-cdk/aws-s3/lib/util.ts +++ b/packages/@aws-cdk/aws-s3/lib/util.ts @@ -1,7 +1,8 @@ import * as cdk from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; import { BucketAttributes } from './bucket'; -export function parseBucketArn(construct: cdk.IConstruct, props: BucketAttributes): string { +export function parseBucketArn(construct: IConstruct, props: BucketAttributes): string { // if we have an explicit bucket ARN, use it. if (props.bucketArn) { @@ -22,7 +23,7 @@ export function parseBucketArn(construct: cdk.IConstruct, props: BucketAttribute throw new Error('Cannot determine bucket ARN. At least `bucketArn` or `bucketName` is needed'); } -export function parseBucketName(construct: cdk.IConstruct, props: BucketAttributes): string | undefined { +export function parseBucketName(construct: IConstruct, props: BucketAttributes): string | undefined { // if we have an explicit bucket name, use it. if (props.bucketName) { diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index e9673a87c4893..83150e561020d 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -48,7 +48,10 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::S3" + "cloudformation": "AWS::S3", + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-s3/test/test.aspect.ts b/packages/@aws-cdk/aws-s3/test/test.aspect.ts index a1a94a44b0f1d..df020a5ca4698 100644 --- a/packages/@aws-cdk/aws-s3/test/test.aspect.ts +++ b/packages/@aws-cdk/aws-s3/test/test.aspect.ts @@ -1,6 +1,7 @@ // import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; import { SynthUtils } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; import { Test } from 'nodeunit'; import * as s3 from '../lib'; @@ -40,7 +41,7 @@ export = { }; class BucketVersioningChecker implements cdk.IAspect { - public visit(node: cdk.IConstruct): void { + public visit(node: IConstruct): void { if (node instanceof s3.CfnBucket) { if (!node.versioningConfiguration || (!cdk.Tokenization.isResolvable(node.versioningConfiguration) && node.versioningConfiguration.status !== 'Enabled')) { diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 934b726d92199..d781e74bb0396 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -64,7 +64,7 @@ export class Construct extends constructs.Construct implements IConstruct { */ public readonly node: ConstructNode; - constructor(scope: Construct, id: string) { + constructor(scope: constructs.Construct, id: string) { super(scope, id, { nodeFactory: { createNode: (h: constructs.Construct, s: constructs.IConstruct, i: string) => diff --git a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts index dbd0a2c8af772..7c9fae2ab15da 100644 --- a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts +++ b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts @@ -1,4 +1,5 @@ import * as crypto from 'crypto'; +import { Node } from 'constructs'; import { IResolvable, IResolveContext } from '../resolvable'; import { IResource } from '../resource'; import { Stack } from '../stack'; @@ -8,16 +9,16 @@ import { TokenMap } from './token-map'; export function generatePhysicalName(resource: IResource): string { const stack = Stack.of(resource); const stackPart = new PrefixNamePart(stack.stackName, 25); - const idPart = new SuffixNamePart(resource.node.uniqueId, 24); + const idPart = new SuffixNamePart(Node.of(resource).uniqueId, 24); const region: string = stack.region; if (Token.isUnresolved(region) || !region) { - throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the region is un-resolved or missing`); + throw new Error(`Cannot generate a physical name for ${Node.of(resource).path}, because the region is un-resolved or missing`); } const account: string = stack.account; if (Token.isUnresolved(account) || !account) { - throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the account is un-resolved or missing`); + throw new Error(`Cannot generate a physical name for ${Node.of(resource).path}, because the account is un-resolved or missing`); } const parts = [stackPart, idPart] diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index 1eb1da50f7335..c437ef98d5d42 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -1,5 +1,6 @@ +import { Construct } from 'constructs'; import { ArnComponents } from './arn'; -import { Construct, IConstruct } from './construct-compat'; +import { IConstruct, Construct as CoreConstruct } from './construct-compat'; import { Lazy } from './lazy'; import { generatePhysicalName, isGeneratedWhenNeededMarker } from './private/physical-name-generator'; import { IResolveContext } from './resolvable'; @@ -86,7 +87,7 @@ export interface ResourceProps { /** * A construct which represents an AWS resource. */ -export abstract class Resource extends Construct implements IResource { +export abstract class Resource extends CoreConstruct implements IResource { public readonly stack: Stack; public readonly env: ResourceEnvironment; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 2aad06f44c721..68b82dadfbb7d 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; +import { IConstruct, Node } from 'constructs'; import { Annotations } from './annotations'; import { App } from './app'; import { Arn, ArnComponents } from './arn'; @@ -10,7 +11,7 @@ import { CfnElement } from './cfn-element'; import { Fn } from './cfn-fn'; import { Aws, ScopedAws } from './cfn-pseudo'; import { CfnResource, TagType } from './cfn-resource'; -import { Construct, IConstruct, ISynthesisSession } from './construct-compat'; +import { Construct, ISynthesisSession } from './construct-compat'; import { ContextProvider } from './context-provider'; import { Environment } from './environment'; import { FeatureFlags } from './feature-flags'; @@ -169,11 +170,12 @@ export class Stack extends Construct implements ITaggable { return c; } - if (Stage.isStage(c) || !c.node.scope) { - throw new Error(`${construct.constructor?.name ?? 'Construct'} at '${construct.node.path}' should be created in the scope of a Stack, but no Stack found`); + const _scope = Node.of(c).scope; + if (Stage.isStage(c) || !_scope) { + throw new Error(`${construct.constructor?.name ?? 'Construct'} at '${Node.of(construct).path}' should be created in the scope of a Stack, but no Stack found`); } - return _lookup(c.node.scope); + return _lookup(_scope); } } @@ -934,7 +936,7 @@ export class Stack extends Construct implements ITaggable { */ private generateStackId(container: IConstruct | undefined) { const rootPath = rootPathTo(this, container); - const ids = rootPath.map(c => c.node.id); + const ids = rootPath.map(c => Node.of(c).id); // In unit tests our Stack (which is the only component) may not have an // id, so in that case just pretend it's "Stack". @@ -1051,7 +1053,7 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { into.push(node); } - for (const child of node.node.children) { + for (const child of Node.of(node).children) { // Don't recurse into a substack if (Stack.isStack(child)) { continue; } @@ -1067,7 +1069,7 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { * If no ancestor is given or the ancestor is not found, return the entire root path. */ export function rootPathTo(construct: IConstruct, ancestor?: IConstruct): IConstruct[] { - const scopes = construct.node.scopes; + const scopes = Node.of(construct).scopes; for (let i = scopes.length - 2; i >= 0; i--) { if (scopes[i] === ancestor) { return scopes.slice(i + 1); diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index b6f6a1f985d19..94296a6d167ee 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -1,5 +1,6 @@ import * as cxapi from '@aws-cdk/cx-api'; -import { Construct, IConstruct } from './construct-compat'; +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'; @@ -73,7 +74,7 @@ export class Stage extends Construct { * @experimental */ public static of(construct: IConstruct): Stage | undefined { - return construct.node.scopes.reverse().slice(1).find(Stage.isStage); + return Node.of(construct).scopes.reverse().slice(1).find(Stage.isStage); } /** diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 3d64a2d8f03fd..ac384aac42fb1 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -37,6 +37,7 @@ "exclude": [ "props-physical-name:@aws-cdk/aws-cloudformation.CustomResourceProps", "construct-ctor:@aws-cdk/core.App.", + "construct-ctor:@aws-cdk/core.Construct..params[0]", "props-no-cfn-types:@aws-cdk/core.CfnOutputProps.condition", "duration-prop-type:@aws-cdk/core.ResourceSignal.timeout", "props-no-any:@aws-cdk/core.CfnParameterProps.default", diff --git a/packages/awslint/lib/rules/construct.ts b/packages/awslint/lib/rules/construct.ts index ae922413828a4..0cdb50eebded6 100644 --- a/packages/awslint/lib/rules/construct.ts +++ b/packages/awslint/lib/rules/construct.ts @@ -24,6 +24,9 @@ export class ConstructReflection { return typeRef.fqn; } + /** + * @deprecated - use `CoreTypes.constructClass()` or `CoreTypes.baseConstructClass()` as appropriate + */ public readonly ROOT_CLASS: reflect.ClassType; // cdk.Construct public readonly fqn: string; @@ -79,7 +82,7 @@ export class ConstructReflection { constructLinter.add({ code: 'construct-ctor', - message: 'signature of all construct constructors should be "scope, id, props"', + message: 'signature of all construct constructors should be "scope, id, props". ' + baseConstructAddendum(), eval: e => { // only applies to non abstract classes if (e.ctx.classType.abstract) { @@ -93,9 +96,15 @@ constructLinter.add({ const expectedParams = new Array(); + let baseType; + if (process.env.AWSLINT_BASE_CONSTRUCT && !initializer.parentType.name.startsWith('Cfn')) { + baseType = e.ctx.core.baseConstructClass; + } else { + baseType = e.ctx.core.constructClass; + } expectedParams.push({ name: 'scope', - type: e.ctx.core.constructClass.fqn, + type: baseType.fqn, }); expectedParams.push({ @@ -276,3 +285,10 @@ constructLinter.add({ } }, }); + +function baseConstructAddendum(): string { + if (!process.env.AWSLINT_BASE_CONSTRUCT) { + return 'If the construct is using the "constructs" module, set the environment variable "AWSLINT_BASE_CONSTRUCT" and re-run'; + } + return ''; +} \ No newline at end of file diff --git a/packages/awslint/lib/rules/core-types.ts b/packages/awslint/lib/rules/core-types.ts index 09be91656d446..9e14eb5bb9d51 100644 --- a/packages/awslint/lib/rules/core-types.ts +++ b/packages/awslint/lib/rules/core-types.ts @@ -4,12 +4,18 @@ import { getDocTag } from './util'; const CORE_MODULE = '@aws-cdk/core'; enum CoreTypesFqn { CfnResource = '@aws-cdk/core.CfnResource', - Construct = '@aws-cdk/core.Construct', - ConstructInterface = '@aws-cdk/core.IConstruct', Resource = '@aws-cdk/core.Resource', ResourceInterface = '@aws-cdk/core.IResource', ResolvableInterface = '@aws-cdk/core.IResolvable', - PhysicalName = '@aws-cdk/core.PhysicalName' + PhysicalName = '@aws-cdk/core.PhysicalName', + + BaseConstruct = 'constructs.Construct', + BaseConstructInterface = 'constructs.Construct', + + /** @deprecated - use BaseConstruct */ + Construct = '@aws-cdk/core.Construct', + /** @deprecated - use BaseConstructInterface */ + ConstructInterface = '@aws-cdk/core.IConstruct', } export class CoreTypes { @@ -86,18 +92,34 @@ export class CoreTypes { /** * @returns `classType` for the core type Construct + * @deprecated - use `baseConstructClass()` */ public get constructClass() { return this.sys.findClass(CoreTypesFqn.Construct); } + /** + * @returns `classType` for the core type Construct + */ + public get baseConstructClass() { + return this.sys.findClass(CoreTypesFqn.BaseConstruct); + } + /** * @returns `interfacetype` for the core type Construct + * @deprecated - use `baseConstructInterface()` */ public get constructInterface() { return this.sys.findInterface(CoreTypesFqn.ConstructInterface); } + /** + * @returns `interfacetype` for the core type Construct + */ + public get baseConstructInterface() { + return this.sys.findInterface(CoreTypesFqn.BaseConstructInterface); + } + /** * @returns `classType` for the core type Construct */ diff --git a/packages/awslint/lib/rules/imports.ts b/packages/awslint/lib/rules/imports.ts index d69c5f2745d98..4940021e8fdce 100644 --- a/packages/awslint/lib/rules/imports.ts +++ b/packages/awslint/lib/rules/imports.ts @@ -63,16 +63,18 @@ importsLinter.add({ importsLinter.add({ code: 'from-signature', - message: 'invalid method signature for fromXxx method', + message: 'invalid method signature for fromXxx method. ' + baseConstructAddendum(), eval: e => { for (const method of e.ctx.fromMethods) { // "fromRoleArn" => "roleArn" const argName = e.ctx.resource.basename[0].toLocaleLowerCase() + method.name.slice('from'.length + 1); + const baseType = process.env.AWSLINT_BASE_CONSTRUCT ? e.ctx.resource.core.baseConstructClass : + e.ctx.resource.core.constructClass; e.assertSignature(method, { parameters: [ - { name: 'scope', type: e.ctx.resource.construct.ROOT_CLASS }, + { name: 'scope', type: baseType }, { name: 'id', type: 'string' }, { name: argName, type: 'string' }, ], @@ -84,15 +86,17 @@ importsLinter.add({ importsLinter.add({ code: 'from-attributes', - message: 'static fromXxxAttributes is a factory of IXxx from its primitive attributes', + message: 'static fromXxxAttributes is a factory of IXxx from its primitive attributes. ' + baseConstructAddendum(), eval: e => { if (!e.ctx.fromAttributesMethod) { return; } + const baseType = process.env.AWSLINT_BASE_CONSTRUCT ? e.ctx.resource.core.baseConstructClass + : e.ctx.resource.core.constructClass; e.assertSignature(e.ctx.fromAttributesMethod, { parameters: [ - { name: 'scope', type: e.ctx.resource.construct.ROOT_CLASS }, + { name: 'scope', type: baseType }, { name: 'id', type: 'string' }, { name: 'attrs', type: e.ctx.attributesStruct }, ], @@ -111,3 +115,10 @@ importsLinter.add({ e.assert(e.ctx.attributesStruct, e.ctx.attributesStructName); }, }); + +function baseConstructAddendum(): string { + if (!process.env.AWSLINT_BASE_CONSTRUCT) { + return 'If the construct is using the "constructs" module, set the environment variable "AWSLINT_BASE_CONSTRUCT" and re-run'; + } + return ''; +} diff --git a/tools/cdk-build-tools/bin/cdk-build.ts b/tools/cdk-build-tools/bin/cdk-build.ts index 87f269364adb8..185811c6b0658 100644 --- a/tools/cdk-build-tools/bin/cdk-build.ts +++ b/tools/cdk-build-tools/bin/cdk-build.ts @@ -27,9 +27,10 @@ async function main() { .argv; const options = cdkBuildOptions(); + const env = options.env; if (options.pre) { - await shell(options.pre, { timers }); + await shell(options.pre, { timers, env }); } // See if we need to call cfn2ts @@ -38,15 +39,15 @@ async function main() { // There can be multiple scopes, ensuring it's always an array. options.cloudformation = [options.cloudformation]; } - await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers }); + await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers, env }); } const overrides: CompilerOverrides = { eslint: args.eslint, jsii: args.jsii, tsc: args.tsc }; - await compileCurrentPackage(timers, overrides); + await compileCurrentPackage(options, timers, overrides); await lintCurrentPackage(options, overrides); if (options.post) { - await shell(options.post, { timers }); + await shell(options.post, { timers, env }); } } diff --git a/tools/cdk-build-tools/lib/compile.ts b/tools/cdk-build-tools/lib/compile.ts index 1101333113bf9..fe63d0c8f346e 100644 --- a/tools/cdk-build-tools/lib/compile.ts +++ b/tools/cdk-build-tools/lib/compile.ts @@ -1,12 +1,13 @@ import { makeExecutable, shell } from './os'; -import { CompilerOverrides, currentPackageJson, packageCompiler } from './package-info'; +import { CDKBuildOptions, CompilerOverrides, currentPackageJson, packageCompiler } from './package-info'; import { Timers } from './timer'; /** * Run the compiler on the current package */ -export async function compileCurrentPackage(timers: Timers, compilers: CompilerOverrides = {}): Promise { - await shell(packageCompiler(compilers), { timers }); +export async function compileCurrentPackage(options: CDKBuildOptions, timers: Timers, compilers: CompilerOverrides = {}): Promise { + const env = options.env; + await shell(packageCompiler(compilers), { timers, env }); // Find files in bin/ that look like they should be executable, and make them so. const scripts = currentPackageJson().bin || {}; diff --git a/tools/cdk-build-tools/lib/lint.ts b/tools/cdk-build-tools/lib/lint.ts index 77943bad48e8c..ca9128a4e5f82 100644 --- a/tools/cdk-build-tools/lib/lint.ts +++ b/tools/cdk-build-tools/lib/lint.ts @@ -3,6 +3,7 @@ import { shell } from './os'; import { CDKBuildOptions, CompilerOverrides } from './package-info'; export async function lintCurrentPackage(options: CDKBuildOptions, compilers: CompilerOverrides & { fix?: boolean } = {}): Promise { + const env = options.env; if (!options.eslint?.disable) { await shell([ compilers.eslint || require.resolve('eslint/bin/eslint'), @@ -10,12 +11,12 @@ export async function lintCurrentPackage(options: CDKBuildOptions, compilers: Co '--ext=.ts', `--resolve-plugins-relative-to=${__dirname}`, ...compilers.fix ? ['--fix'] : [], - ]); + ], { env }); } if (!options.pkglint?.disable) { - await shell(['pkglint']); + await shell(['pkglint'], { env }); } - await shell([path.join(__dirname, '..', 'bin', 'cdk-awslint')]); + await shell([path.join(__dirname, '..', 'bin', 'cdk-awslint')], { env }); } diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index afc4041776b9f..cf5b657d9a470 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -135,6 +135,11 @@ export interface CDKBuildOptions { * but we want to eventually move all of them to Jest. */ jest?: boolean; + + /** + * Environment variables to be passed to 'cdk-build' and all of its child processes. + */ + env?: NodeJS.ProcessEnv; } /** From c7c78516e712051884999a9d1801e97bafe49c5b Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 23 Sep 2020 18:07:46 +0100 Subject: [PATCH 12/29] feat(rds): support setting database master users from existing secrets (#10458) See https://github.com/aws/aws-cdk/issues/7927#issuecomment-694857345 for motivation and design. The current way of specifying master user logins for `DatabaseInstance` and `DatabaseCluster` is inconsistent between the two and introduces some awkward usage when creating a login from an existing `Secret`. This change converts the existing `Login` interface (used by the `DatabaseCluster`) into a class with factory methods for username/password or secret-based logins. This also then re-uses that same interface for `DatabaseInstance`. The one exception now will be `DatabaseInstanceFromSnapshot`, which has specific requirements that deserved its own interface (`SnapshotLogin`). As a side effect of this approach, existing `DatabaseCluster` users -- in Typescript at least -- will not be broken. For example, the following are equivalent: ```ts new rds.DatabaseCluster(this, 'Cluster1', { // Existing usage masterUser: { username: 'admin', }, // New usage masterUser: Login.fromUsername('admin'), }); ``` Lastly, this change makes the whole `masterUser` prop optional, as there's no good reason why we can't default a username. fixes #7927 BREAKING CHANGE: `DatabaseInstanceProps` and `DatabaseInstanceFromSnapshotProps` - `masterUsername`, `masterUserPassword` and `masterUserPasswordEncryptionKey` moved to `credentials` as a new `Credentials` class. * **rds:** `Login` renamed to `Credentials`. Use `Credentials.fromUsername` to replace existing usage. * **rds:** `DatabaseClusterProps` `masterUser` renamed to `credentials`. ---- *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 | 38 +++- packages/@aws-cdk/aws-rds/lib/cluster.ts | 29 ++- packages/@aws-cdk/aws-rds/lib/instance.ts | 89 +++------ packages/@aws-cdk/aws-rds/lib/props.ts | 148 +++++++++++++- .../test/integ.cluster-rotation.lit.ts | 3 - .../@aws-cdk/aws-rds/test/integ.cluster-s3.ts | 7 +- .../@aws-cdk/aws-rds/test/integ.cluster.ts | 7 +- .../aws-rds/test/integ.instance-s3.ts | 1 - .../aws-rds/test/integ.instance.lit.ts | 2 +- packages/@aws-cdk/aws-rds/test/integ.proxy.ts | 2 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 82 ++++---- .../@aws-cdk/aws-rds/test/test.instance.ts | 186 +++++++++--------- packages/@aws-cdk/aws-rds/test/test.proxy.ts | 8 - 13 files changed, 352 insertions(+), 250 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index cc8be1361bca8..88a2d001107de 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -27,9 +27,7 @@ your instances will be launched privately or publicly: ```ts const cluster = new rds.DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_08_1 }), - masterUser: { - username: 'clusteradmin' - }, + masterUser: rds.Login.fromUsername('clusteradmin'), // Optional - will default to admin instanceProps: { // optional, defaults to t3.medium instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), @@ -76,7 +74,7 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.oracleSe2({ version: rds.OracleEngineVersion.VER_19_0_0_0_2020_04_R1 }), // optional, defaults to m5.large instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', + masterUsername: rds.Login.fromUsername('syscdk'), // Optional - will default to admin vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE @@ -103,7 +101,6 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), // optional, defaults to m5.large instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', vpc, maxAllocatedStorage: 200, }); @@ -141,6 +138,35 @@ method: const rule = instance.onEvent('InstanceEvent', { target: new targets.LambdaFunction(fn) }); ``` +### Login credentials + +By default, database instances and clusters will have `admin` user with an auto-generated password. +An alternative username (and password) may be specified for the admin user instead of the default. + +The following examples use a `DatabaseInstance`, but the same usage is applicable to `DatabaseCluster`. + +```ts +const engine = rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }); +new rds.DatabaseInstance(this, 'InstanceWithUsername', { + engine, + vpc, + credentials: rds.Credentials.fromUsername('postgres'), // Creates an admin user of postgres with a generated password +}); + +new rds.DatabaseInstance(this, 'InstanceWithUsernameAndPassword', { + engine, + vpc, + credentials: rds.Credentials.fromUsername('postgres', { password: SecretValue.ssmSecure('/dbPassword', 1) }), // Use password from SSM +}); + +const mySecret = secretsmanager.Secret.fromSecretName(this, 'DBSecret', 'myDBLoginInfo'); +new rds.DatabaseInstance(this, 'InstanceWithSecretLogin', { + engine, + vpc, + credentials: rds.Credentials.fromSecret(mySecret), // Get both username and password from existing secret +}); +``` + ### Connecting To control who can access the cluster or instance, use the `.connections` attribute. RDS databases have @@ -211,7 +237,6 @@ The following example shows enabling IAM authentication for a database instance ```ts const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, iamAuthentication: true, // Optional - will be automatically set if you call grantConnect(). }); @@ -240,7 +265,6 @@ const role = new iam.Role(stack, 'RDSDirectoryServicesRole', { }); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, domain: 'd-????????', // The ID of the domain for the instance to join. domainRole: role, // Optional - will be create automatically if not provided. diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 9bd29be6e64e0..a4d01868ba5fb 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -11,7 +11,7 @@ import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; import { IParameterGroup } from './parameter-group'; import { applyRemovalPolicy, defaultDeletionProtection, setupS3ImportExport } from './private/util'; -import { BackupProps, InstanceProps, Login, PerformanceInsightRetention, RotationMultiUserOptions } from './props'; +import { BackupProps, Credentials, InstanceProps, PerformanceInsightRetention, RotationMultiUserOptions } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBCluster, CfnDBClusterProps, CfnDBInstance } from './rds.generated'; import { ISubnetGroup, SubnetGroup } from './subnet-group'; @@ -418,9 +418,11 @@ class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCl */ export interface DatabaseClusterProps extends DatabaseClusterBaseProps { /** - * Username and password for the administrative user + * Credentials for the administrative user + * + * @default - A username of 'admin' and SecretsManager-generated password */ - readonly masterUser: Login; + readonly credentials?: Credentials; /** * Whether to enable storage encryption. @@ -476,23 +478,20 @@ export class DatabaseCluster extends DatabaseClusterNew { this.singleUserRotationApplication = props.engine.singleUserRotationApplication; this.multiUserRotationApplication = props.engine.multiUserRotationApplication; - let secret: DatabaseSecret | undefined; - if (!props.masterUser.password) { - secret = new DatabaseSecret(this, 'Secret', { - username: props.masterUser.username, - encryptionKey: props.masterUser.encryptionKey, - }); + let credentials = props.credentials ?? Credentials.fromUsername('admin'); + if (!credentials.secret && !credentials.password) { + credentials = Credentials.fromSecret(new DatabaseSecret(this, 'Secret', { + username: credentials.username, + encryptionKey: credentials.encryptionKey, + })); } + const secret = credentials.secret; const cluster = new CfnDBCluster(this, 'Resource', { ...this.newCfnProps, // Admin - masterUsername: secret ? secret.secretValueFromJson('username').toString() : props.masterUser.username, - masterUserPassword: secret - ? secret.secretValueFromJson('password').toString() - : (props.masterUser.password - ? props.masterUser.password.toString() - : undefined), + masterUsername: credentials.username, + masterUserPassword: credentials.password?.toString(), // Encryption kmsKeyId: props.storageEncryptionKey?.keyArn, storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 1339306e9553f..fa08049df2f97 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,14 +5,14 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Construct, Duration, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { Construct, Duration, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { DatabaseSecret } from './database-secret'; 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 { PerformanceInsightRetention, RotationMultiUserOptions } from './props'; +import { Credentials, PerformanceInsightRetention, RotationMultiUserOptions, SnapshotCredentials } from './props'; import { DatabaseProxy, DatabaseProxyOptions, ProxyTarget } from './proxy'; import { CfnDBInstance, CfnDBInstanceProps } from './rds.generated'; import { ISubnetGroup, SubnetGroup } from './subnet-group'; @@ -750,20 +750,6 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { */ readonly allocatedStorage?: number; - /** - * The master user password. - * - * @default - a Secrets Manager generated password - */ - readonly masterUserPassword?: SecretValue; - - /** - * The KMS key used to encrypt the secret for the master user password. - * - * @default - default master key - */ - readonly masterUserPasswordEncryptionKey?: kms.IKey; - /** * The name of the database. * @@ -894,9 +880,11 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa */ export interface DatabaseInstanceProps extends DatabaseInstanceSourceProps { /** - * The master user name. + * Credentials for the administrative user + * + * @default - A username of 'admin' and SecretsManager-generated password */ - readonly masterUsername: string; + readonly credentials?: Credentials; /** * For supported engines, specifies the character set to associate with the @@ -936,22 +924,21 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas constructor(scope: Construct, id: string, props: DatabaseInstanceProps) { super(scope, id, props); - let secret: DatabaseSecret | undefined; - if (!props.masterUserPassword) { - secret = new DatabaseSecret(this, 'Secret', { - username: props.masterUsername, - encryptionKey: props.masterUserPasswordEncryptionKey, - }); + let credentials = props.credentials ?? Credentials.fromUsername('admin'); + if (!credentials.secret && !credentials.password) { + credentials = Credentials.fromSecret(new DatabaseSecret(this, 'Secret', { + username: credentials.username, + encryptionKey: credentials.encryptionKey, + })); } + const secret = credentials.secret; const instance = new CfnDBInstance(this, 'Resource', { ...this.sourceCfnProps, characterSetName: props.characterSetName, kmsKeyId: props.storageEncryptionKey && props.storageEncryptionKey.keyArn, - masterUsername: secret ? secret.secretValueFromJson('username').toString() : props.masterUsername, - masterUserPassword: secret - ? secret.secretValueFromJson('password').toString() - : props.masterUserPassword && props.masterUserPassword.toString(), + masterUsername: credentials.username, + masterUserPassword: credentials.password?.toString(), storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, }); @@ -985,26 +972,14 @@ export interface DatabaseInstanceFromSnapshotProps extends DatabaseInstanceSourc readonly snapshotIdentifier: string; /** - * The master user name. - * - * Specify this prop with the **current** master user name of the snapshot - * only when generating a new master user password with `generateMasterUserPassword`. - * The value will be set in the generated secret attached to the instance. + * Master user credentials. * - * It is not possible to change the master user name of a RDS instance. - * - * @default - inherited from the snapshot - */ - readonly masterUsername?: string; - - /** - * Whether to generate a new master user password and store it in - * Secrets Manager. `masterUsername` must be specified with the **current** - * master user name of the snapshot when this property is set to true. + * Note - It is not possible to change the master username for a snapshot; + * however, it is possible to provide (or generate) a new password. * - * @default false + * @default - The existing username and password from the snapshot will be used. */ - readonly generateMasterUserPassword?: boolean; + readonly credentials?: SnapshotCredentials; } /** @@ -1022,25 +997,17 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme constructor(scope: Construct, id: string, props: DatabaseInstanceFromSnapshotProps) { super(scope, id, props); - let secret: DatabaseSecret | undefined; - - if (props.generateMasterUserPassword) { - if (!props.masterUsername) { // We need the master username to include it in the generated secret - throw new Error('`masterUsername` must be specified when `generateMasterUserPassword` is set to true.'); - } - - if (props.masterUserPassword) { - throw new Error('Cannot specify `masterUserPassword` when `generateMasterUserPassword` is set to true.'); + let credentials = props.credentials; + let secret = credentials?.secret; + if (!secret && credentials?.generatePassword) { + if (!credentials.username) { + throw new Error('`credentials` `username` must be specified when `generatePassword` is set to true'); } secret = new DatabaseSecret(this, 'Secret', { - username: props.masterUsername, - encryptionKey: props.masterUserPasswordEncryptionKey, + username: credentials.username, + encryptionKey: credentials.encryptionKey, }); - } else { - if (props.masterUsername) { // It's not possible to change the master username of a RDS instance - throw new Error('Cannot specify `masterUsername` when `generateMasterUserPassword` is set to false.'); - } } const instance = new CfnDBInstance(this, 'Resource', { @@ -1048,7 +1015,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme dbSnapshotIdentifier: props.snapshotIdentifier, masterUserPassword: secret ? secret.secretValueFromJson('password').toString() - : props.masterUserPassword && props.masterUserPassword.toString(), + : credentials?.password?.toString(), }); this.instanceIdentifier = instance.ref; diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 8e037b77c7174..81a27a1e05d70 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -94,30 +94,166 @@ export interface BackupProps { readonly preferredWindow?: string; } +/** + * Options for creating a Login from a username. + */ +export interface CredentialsFromUsernameOptions { + /** + * Password + * + * Do not put passwords in your CDK code directly. + * + * @default - a Secrets Manager generated password + */ + readonly password?: SecretValue; + + /** + * KMS encryption key to encrypt the generated secret. + * + * @default - default master key + */ + readonly encryptionKey?: kms.IKey; +} + /** * Username and password combination */ -export interface Login { +export abstract class Credentials { + + /** + * Creates Credentials for the given username, and optional password and key. + * 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 }; + } + + /** + * Creates Credentials from an existing SecretsManager ``Secret`` (or ``DatabaseSecret``) + * + * The Secret must be a JSON string with a ``username`` and ``password`` field: + * ``` + * { + * ... + * "username": , + * "password": , + * } + * ``` + */ + public static fromSecret(secret: secretsmanager.Secret): Credentials { + return { + username: secret.secretValueFromJson('username').toString(), + password: secret.secretValueFromJson('password'), + encryptionKey: secret.encryptionKey, + secret, + }; + } + /** * Username */ - readonly username: string; + public abstract readonly username: string; /** * Password * * Do not put passwords in your CDK code directly. * - * @default a Secrets Manager generated password + * @default - a Secrets Manager generated password */ - readonly password?: SecretValue; + public abstract readonly password?: SecretValue; /** * KMS encryption key to encrypt the generated secret. * - * @default default master key + * @default - default master key */ - readonly encryptionKey?: kms.IKey; + public abstract readonly encryptionKey?: kms.IKey; + + /** + * Secret used to instantiate this Login. + * + * @default - none + */ + public abstract readonly secret?: secretsmanager.Secret; +} + +/** + * Credentials to update the password for a ``DatabaseInstanceFromSnapshot``. + */ +export abstract class SnapshotCredentials { + /** + * Generate a new password for the snapshot, using the existing username and an optional encryption key. + * + * 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 }; + } + + /** + * Update the snapshot login with an existing password. + */ + public static fromPassword(password: SecretValue): SnapshotCredentials { + return { generatePassword: false, password }; + } + + /** + * Update the snapshot login with an existing password from a Secret. + * + * The Secret must be a JSON string with a ``password`` field: + * ``` + * { + * ... + * "password": , + * } + * ``` + */ + public static fromSecret(secret: secretsmanager.Secret): SnapshotCredentials { + return { + generatePassword: false, + password: secret.secretValueFromJson('password'), + secret, + }; + } + + /** + * The master user name. + * + * Must be the **current** master user name of the snapshot. + * It is not possible to change the master user name of a RDS instance. + * + * @default - the existing username from the snapshot + */ + public abstract readonly username?: string; + + /** + * Whether a new password should be generated. + */ + public abstract readonly generatePassword: boolean; + + /** + * The master user password. + * + * Do not put passwords in your CDK code directly. + * + * @default - the existing password from the snapshot + */ + public abstract readonly password?: SecretValue; + + /** + * KMS encryption key to encrypt the generated secret. + * + * @default - default master key + */ + public abstract readonly encryptionKey?: kms.IKey; + + /** + * Secret used to instantiate this Login. + * + * @default - none + */ + public abstract readonly secret?: secretsmanager.Secret; } /** diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts index 900364b2aa7b8..0a429ae3d5742 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts @@ -10,9 +10,6 @@ const vpc = new ec2.Vpc(stack, 'VPC'); /// !show const cluster = new rds.DatabaseCluster(stack, 'Database', { engine: rds.DatabaseClusterEngine.AURORA, - masterUser: { - username: 'admin', - }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL), vpc, diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-s3.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster-s3.ts index 2153d8ea95410..5b4f2d3742f02 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-s3.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-s3.ts @@ -2,7 +2,7 @@ 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 cdk from '@aws-cdk/core'; -import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; +import { Credentials, DatabaseCluster, DatabaseClusterEngine } from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-s3-integ'); @@ -16,10 +16,7 @@ const exportBucket = new s3.Bucket(stack, 'ExportBucket'); const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { - username: 'admin', - password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), - }, + credentials: Credentials.fromUsername('admin', { password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6') }), instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL), vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index a5bdaba4883c9..245ab98ab8e78 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; -import { DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; +import { Credentials, DatabaseCluster, DatabaseClusterEngine, ParameterGroup } from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); @@ -20,10 +20,7 @@ const kmsKey = new kms.Key(stack, 'DbSecurity'); const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { - username: 'admin', - password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), - }, + credentials: Credentials.fromUsername('admin', { password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6') }), instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL), vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts index d3503d5889404..876ac2251e8fc 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts @@ -13,7 +13,6 @@ const exportBucket = new s3.Bucket(stack, 'ExportBucket', { removalPolicy: cdk.R new DatabaseInstance(stack, 'Database', { engine: DatabaseInstanceEngine.sqlServerSe({ version: SqlServerEngineVersion.VER_14_00_3192_2_V1 }), - masterUsername: 'admin', vpc, licenseModel: LicenseModel.LICENSE_INCLUDED, s3ImportBuckets: [importBucket], diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index 7f36806c35230..eafe7a2953735 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -49,7 +49,7 @@ class DatabaseInstanceStack extends cdk.Stack { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, - masterUsername: 'syscdk', + credentials: rds.Credentials.fromUsername('syscdk'), vpc, databaseName: 'ORCL', storageEncrypted: true, diff --git a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts index 8c2ef1879787b..e45da0950ed2a 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.proxy.ts @@ -10,7 +10,7 @@ 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), - masterUsername: 'master', + credentials: rds.Credentials.fromUsername('master'), vpc, }); diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index 8915973a5938d..82dce160e7128 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -20,7 +20,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -61,7 +61,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -95,7 +95,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -133,7 +133,7 @@ export = { }); new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -157,7 +157,7 @@ export = { const vpc = new ec2.Vpc(stack, 'Vpc'); new DatabaseCluster(stack, 'Cluster', { - masterUser: { username: 'admin' }, + credentials: { username: 'admin' }, engine: DatabaseClusterEngine.AURORA, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE), @@ -182,7 +182,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_MYSQL, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -239,7 +239,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_MYSQL, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -276,7 +276,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -305,7 +305,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -333,7 +333,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -358,7 +358,7 @@ export = { test.throws(() => { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -383,7 +383,7 @@ export = { engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_04_4, }), - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -411,7 +411,7 @@ export = { engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_10_7, }), - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -437,7 +437,7 @@ export = { // WHEN const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -538,7 +538,7 @@ export = { const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_5_7_12 }), - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -569,7 +569,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -635,7 +635,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -665,7 +665,7 @@ export = { // WHEN const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_MYSQL, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -687,7 +687,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_MYSQL, - masterUser: { username: 'admin' }, + credentials: { username: 'admin' }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, @@ -716,7 +716,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -764,7 +764,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -852,7 +852,7 @@ export = { version: AuroraPostgresEngineVersion.VER_10_12, }), instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -892,7 +892,7 @@ export = { version: AuroraPostgresEngineVersion.VER_10_4, }), instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -909,7 +909,7 @@ export = { version: AuroraPostgresEngineVersion.VER_10_4, }), instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -936,7 +936,7 @@ export = { version: AuroraPostgresEngineVersion.VER_10_12, }), instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -975,7 +975,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1023,7 +1023,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1113,7 +1113,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1184,7 +1184,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1251,7 +1251,7 @@ export = { version: AuroraPostgresEngineVersion.VER_11_6, }), instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1291,7 +1291,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_POSTGRESQL, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1339,7 +1339,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA_MYSQL, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1372,7 +1372,7 @@ export = { test.throws(() => new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1400,7 +1400,7 @@ export = { test.throws(() => new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1422,7 +1422,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -1449,7 +1449,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -1494,7 +1494,7 @@ export = { test.throws(() => { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -1517,7 +1517,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, - masterUser: { + credentials: { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, @@ -1545,7 +1545,7 @@ export = { new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.AURORA, instances: 1, - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1604,7 +1604,7 @@ export = { // WHEN new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.aurora({ version: AuroraEngineVersion.VER_1_22_2 }), - masterUser: { + credentials: { username: 'admin', }, instanceProps: { @@ -1630,7 +1630,7 @@ export = { // WHEN const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.aurora({ version: AuroraEngineVersion.VER_1_22_2 }), - masterUser: { + credentials: { username: 'admin', }, instanceProps: { diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 2cd024ead7aab..6dad43e8ec7f3 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -28,7 +28,7 @@ export = { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, - masterUsername: 'syscdk', + credentials: rds.Credentials.fromUsername('syscdk'), vpc, databaseName: 'ORCL', storageEncrypted: true, @@ -220,8 +220,6 @@ export = { new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, optionGroup, parameterGroup, @@ -244,7 +242,7 @@ export = { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19, }), - masterUsername: 'syscdk', + credentials: rds.Credentials.fromUsername('syscdk'), vpc, vpcPlacement: { subnetType: ec2.SubnetType.PRIVATE, @@ -272,67 +270,106 @@ export = { test.done(); }, - 'create an instance from snapshot'(test: Test) { - // WHEN - new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.POSTGRES, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc, - }); + 'DatabaseInstanceFromSnapshot': { + 'create an instance from snapshot'(test: Test) { + new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), + vpc, + }); - expect(stack).to(haveResource('AWS::RDS::DBInstance', { - DBSnapshotIdentifier: 'my-snapshot', - })); + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DBSnapshotIdentifier: 'my-snapshot', + })); - test.done(); - }, + test.done(); + }, - 'throws when trying to generate a new password from snapshot without username'(test: Test) { - // THEN - test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc, - generateMasterUserPassword: true, - }), '`masterUsername` must be specified when `generateMasterUserPassword` is set to true.'); + 'can generate a new snapshot password'(test: Test) { + new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), + vpc, + credentials: rds.SnapshotCredentials.fromGeneratedPassword('admin'), + }); - test.done(); - }, + expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { + MasterUsername: ABSENT, + MasterUserPassword: { + 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'InstanceSecret478E0A47' }, ':SecretString:password::}}']], + }, + })); + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + Description: { + 'Fn::Join': ['', ['Generated by the CDK for stack: ', { Ref: 'AWS::StackName' }]], + }, + GenerateSecretString: { + ExcludeCharacters: '\"@/\\', + GenerateStringKey: 'password', + PasswordLength: 30, + SecretStringTemplate: '{"username":"admin"}', + }, + })); - 'throws when specifying user name without asking to generate a new password'(test: Test) { - // THEN - test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc, - masterUsername: 'superadmin', - }), 'Cannot specify `masterUsername` when `generateMasterUserPassword` is set to false.'); + test.done(); + }, - test.done(); - }, + 'throws if generating a new password without a username'(test: Test) { + test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), + vpc, + credentials: { generatePassword: true }, + }), /`credentials` `username` must be specified when `generatePassword` is set to true/); - 'throws when password and generate password ar both specified'(test: Test) { - // THEN - test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { - snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), - vpc, - masterUserPassword: cdk.SecretValue.plainText('supersecret'), - generateMasterUserPassword: true, - }), 'Cannot specify `masterUserPassword` when `generateMasterUserPassword` is set to true.'); + test.done(); + }, - test.done(); + 'can set a new snapshot password from an existing SecretValue'(test: Test) { + new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), + vpc, + credentials: rds.SnapshotCredentials.fromPassword(cdk.SecretValue.plainText('mysecretpassword')), + }); + + // TODO - Expect this to be broken + expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { + MasterUsername: ABSENT, + MasterUserPassword: 'mysecretpassword', + })); + + test.done(); + }, + + 'can set a new snapshot password from an existing Secret'(test: Test) { + const secret = new rds.DatabaseSecret(stack, 'DBSecret', { + username: 'admin', + encryptionKey: new kms.Key(stack, 'PasswordKey'), + }); + new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { + snapshotIdentifier: 'my-snapshot', + engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), + vpc, + credentials: rds.SnapshotCredentials.fromSecret(secret), + }); + + expect(stack).to(haveResourceLike('AWS::RDS::DBInstance', { + MasterUsername: ABSENT, + MasterUserPassword: { + 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'DBSecretD58955BC' }, ':SecretString:password::}}']], + }, + })); + + test.done(); + }, }, 'create a read replica in the same region - with the subnet group name'(test: Test) { const sourceInstance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); @@ -368,8 +405,6 @@ export = { 'on event'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); const fn = new lambda.Function(stack, 'Function', { @@ -432,8 +467,6 @@ export = { 'on event without target'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); @@ -481,8 +514,6 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); @@ -502,8 +533,6 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); @@ -529,8 +558,6 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), }); @@ -575,8 +602,6 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, monitoringInterval: cdk.Duration.minutes(1), monitoringRole, @@ -601,8 +626,6 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, securityGroups: [securityGroup], }); @@ -635,9 +658,7 @@ export = { 'throws when trying to add rotation to an instance without secret'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', - masterUserPassword: cdk.SecretValue.plainText('tooshort'), + credentials: rds.Credentials.fromUsername('syscdk', { password: cdk.SecretValue.plainText('tooshort') }), vpc, }); @@ -651,7 +672,7 @@ export = { const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'syscdk', + credentials: rds.Credentials.fromUsername('syscdk'), vpc, }); @@ -674,8 +695,6 @@ export = { tzSupportedEngines.forEach((engine) => { test.ok(new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), - masterUsername: 'master', timezone: 'Europe/Zurich', vpc, })); @@ -684,8 +703,6 @@ export = { tzUnsupportedEngines.forEach((engine) => { test.throws(() => new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), - masterUsername: 'master', timezone: 'Europe/Zurich', vpc, }), /timezone property can not be configured for/); @@ -716,8 +733,6 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, backupRetention: cdk.Duration.seconds(0), maxAllocatedStorage: 250, @@ -735,7 +750,6 @@ export = { 'iam authentication - off by default'(test: Test) { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, }); @@ -749,7 +763,6 @@ export = { 'createGrant - creates IAM policy and enables IAM auth'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, }); const role = new Role(stack, 'DBRole', { @@ -779,7 +792,6 @@ export = { 'createGrant - throws if IAM auth disabled'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, iamAuthentication: false, }); @@ -799,7 +811,6 @@ export = { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.sqlServerWeb({ version: rds.SqlServerEngineVersion.VER_14_00_3192_2_V1 }), vpc, - masterUsername: 'admin', domain: domain, }); @@ -819,7 +830,6 @@ export = { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.sqlServerWeb({ version: rds.SqlServerEngineVersion.VER_14_00_3192_2_V1 }), vpc, - masterUsername: 'admin', domain: domain, domainRole: role, }); @@ -840,7 +850,6 @@ export = { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.sqlServerWeb({ version: rds.SqlServerEngineVersion.VER_14_00_3192_2_V1 }), vpc, - masterUsername: 'admin', domain: domain, }); @@ -892,8 +901,6 @@ export = { domainSupportedEngines.forEach((engine) => { test.ok(new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), - masterUsername: 'master', domain: 'd-90670a8d36', vpc, })); @@ -904,8 +911,6 @@ export = { test.throws(() => new rds.DatabaseInstance(stack, `${engine.engineType}-db`, { engine, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.SMALL), - masterUsername: 'master', domain: 'd-90670a8d36', vpc, }), expectedError); @@ -918,7 +923,6 @@ export = { 'instance with all performance insights properties'(test: Test) { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, enablePerformanceInsights: true, performanceInsightRetention: rds.PerformanceInsightRetention.LONG_TERM, @@ -937,7 +941,6 @@ export = { 'setting performance insights fields enables performance insights'(test: Test) { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, performanceInsightRetention: rds.PerformanceInsightRetention.LONG_TERM, }); @@ -954,7 +957,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, enablePerformanceInsights: false, performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT, @@ -968,7 +970,6 @@ export = { 'reuse an existing subnet group'(test: Test) { new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), - masterUsername: 'admin', vpc, subnetGroup: rds.SubnetGroup.fromSubnetGroupName(stack, 'SubnetGroup', 'my-subnet-group'), }); @@ -984,7 +985,6 @@ export = { 'defaultChild returns the DB Instance'(test: Test) { const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), - masterUsername: 'admin', vpc, }); @@ -997,7 +997,6 @@ export = { 'instance with s3 import and export buckets'(test: Test) { new rds.DatabaseInstance(stack, 'DB', { engine: rds.DatabaseInstanceEngine.sqlServerSe({ version: rds.SqlServerEngineVersion.VER_14_00_3192_2_V1 }), - masterUsername: 'admin', vpc, s3ImportBuckets: [new s3.Bucket(stack, 'S3Import')], s3ExportBuckets: [new s3.Bucket(stack, 'S3Export')], @@ -1058,7 +1057,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'DBWithImportBucket', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, s3ImportBuckets: [new s3.Bucket(stack, 'S3Import')], }); @@ -1066,7 +1064,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'DBWithImportRole', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, s3ImportRole, }); @@ -1083,7 +1080,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'DBWithExportBucket', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, s3ExportBuckets: [new s3.Bucket(stack, 'S3Export')], }); @@ -1091,7 +1087,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'DBWithExportRole', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), - masterUsername: 'admin', vpc, s3ExportRole: s3ExportRole, }); @@ -1111,7 +1106,6 @@ export = { test.throws(() => { new rds.DatabaseInstance(stack, 'DBWithExportBucket', { engine: rds.DatabaseInstanceEngine.sqlServerEe({ version: rds.SqlServerEngineVersion.VER_14_00_3192_2_V1 }), - masterUsername: 'admin', vpc, s3ImportRole, s3ExportRole, diff --git a/packages/@aws-cdk/aws-rds/test/test.proxy.ts b/packages/@aws-cdk/aws-rds/test/test.proxy.ts index 6e4de63c7aeff..7fa03684fa53b 100644 --- a/packages/@aws-cdk/aws-rds/test/test.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/test.proxy.ts @@ -12,7 +12,6 @@ export = { const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - masterUsername: 'admin', vpc, }); @@ -82,9 +81,6 @@ export = { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7, }), - masterUser: { - username: 'admin', - }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, @@ -157,9 +153,6 @@ export = { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7, }), - masterUser: { - username: 'admin', - }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, @@ -188,7 +181,6 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new rds.DatabaseCluster(stack, 'Database', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7 }), - masterUser: { username: 'admin' }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, From c5021147e5d6f2e2a91fe50daf3aacd149b0f895 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 23 Sep 2020 12:57:53 -0700 Subject: [PATCH 13/29] fix(rds): allow creating Proxies for imported resources (#10488) The current ProxyTarget relied on the underlying L1s to get the engine type for a given Cluster/Instance. Change IDatabaseCluster and IInstanceEngine to add an (optional) `engine` property that is used instead. Allow the user to specify the engine when importing a Cluster or Instance. Also move the logic of determining the engine family into `IEngine`. Fixes #9195 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-rds/lib/cluster-engine.ts | 2 + packages/@aws-cdk/aws-rds/lib/cluster-ref.ts | 14 + packages/@aws-cdk/aws-rds/lib/cluster.ts | 9 +- packages/@aws-cdk/aws-rds/lib/engine.ts | 11 + .../@aws-cdk/aws-rds/lib/instance-engine.ts | 5 + packages/@aws-cdk/aws-rds/lib/instance.ts | 17 ++ packages/@aws-cdk/aws-rds/lib/private/util.ts | 4 +- packages/@aws-cdk/aws-rds/lib/proxy.ts | 42 ++- packages/@aws-cdk/aws-rds/test/test.proxy.ts | 261 ++++++++++-------- 9 files changed, 221 insertions(+), 144 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index a25200ac22205..9fb5bf08ccf3a 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -154,6 +154,7 @@ interface MysqlClusterEngineBaseProps { } abstract class MySqlClusterEngineBase extends ClusterEngineBase { + public readonly engineFamily = 'MYSQL'; public readonly supportedLogTypes: string[] = ['error', 'general', 'slowquery', 'audit']; constructor(props: MysqlClusterEngineBaseProps) { @@ -493,6 +494,7 @@ class AuroraPostgresClusterEngine extends ClusterEngineBase { */ private static readonly S3_EXPORT_FEATURE_NAME = 's3Export'; + public readonly engineFamily = 'POSTGRESQL'; public readonly supportedLogTypes: string[] = ['postgresql']; constructor(version?: AuroraPostgresEngineVersion) { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts index a464e2a0fd5c0..de1f89bd3dafa 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts @@ -1,6 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { IResource } from '@aws-cdk/core'; +import { IClusterEngine } from './cluster-engine'; import { Endpoint } from './endpoint'; import { DatabaseProxy, DatabaseProxyOptions } from './proxy'; @@ -35,6 +36,12 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsma */ readonly instanceEndpoints: Endpoint[]; + /** + * The engine of this Cluster. + * May be not known for imported Clusters if it wasn't provided explicitly. + */ + readonly engine?: IClusterEngine; + /** * Add a new db proxy to this cluster. */ @@ -92,4 +99,11 @@ export interface DatabaseClusterAttributes { * @default - no instance endpoints */ readonly instanceEndpointAddresses?: string[]; + + /** + * The engine of the existing Cluster. + * + * @default - the imported Cluster's engine is unknown + */ + readonly engine?: IClusterEngine; } diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index a4d01868ba5fb..3ba13451e8731 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -281,7 +281,11 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC * Abstract base for ``DatabaseCluster`` and ``DatabaseClusterFromSnapshot`` */ abstract class DatabaseClusterNew extends DatabaseClusterBase { - + /** + * The engine for this Cluster. + * Never undefined. + */ + public readonly engine?: IClusterEngine; public readonly instanceIdentifiers: string[] = []; public readonly instanceEndpoints: Endpoint[] = []; @@ -331,6 +335,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { const clusterParameterGroup = props.parameterGroup ?? clusterEngineBindConfig.parameterGroup; const clusterParameterGroupConfig = clusterParameterGroup?.bindToCluster({}); + this.engine = props.engine; this.newCfnProps = { // Basic @@ -359,6 +364,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster { public readonly clusterIdentifier: string; public readonly connections: ec2.Connections; + public readonly engine?: IClusterEngine; private readonly _clusterEndpoint?: Endpoint; private readonly _clusterReadEndpoint?: Endpoint; @@ -375,6 +381,7 @@ class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCl securityGroups: attrs.securityGroups, defaultPort, }); + this.engine = attrs.engine; this._clusterEndpoint = (attrs.clusterEndpointAddress && attrs.port) ? new Endpoint(attrs.clusterEndpointAddress, attrs.port) : undefined; this._clusterReadEndpoint = (attrs.readerEndpointAddress && attrs.port) ? new Endpoint(attrs.readerEndpointAddress, attrs.port) : undefined; diff --git a/packages/@aws-cdk/aws-rds/lib/engine.ts b/packages/@aws-cdk/aws-rds/lib/engine.ts index b1ccef4084c87..2feced9927ca2 100644 --- a/packages/@aws-cdk/aws-rds/lib/engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/engine.ts @@ -28,4 +28,15 @@ export interface IEngine { * (which means the major version of the engine is also not known) */ readonly parameterGroupFamily?: string; + + /** + * The family this engine belongs to, + * like "MYSQL", or "POSTGRESQL". + * This property is used when creating a Database Proxy. + * Most engines don't belong to any family + * (and because of that, you can't create Database Proxies for their Clusters or Instances). + * + * @default - the engine doesn't belong to any family + */ + readonly engineFamily?: string; } diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index 3b2310212dbf8..25370706664c7 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -109,6 +109,7 @@ interface InstanceEngineBaseProps { readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; readonly version?: EngineVersion; readonly parameterGroupFamily?: string; + readonly engineFamily?: string; readonly features?: InstanceEngineFeatures; } @@ -118,6 +119,7 @@ abstract class InstanceEngineBase implements IInstanceEngine { public readonly parameterGroupFamily?: string; public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + public readonly engineFamily?: string; private readonly features?: InstanceEngineFeatures; @@ -129,6 +131,7 @@ abstract class InstanceEngineBase implements IInstanceEngine { this.engineVersion = props.version; this.parameterGroupFamily = props.parameterGroupFamily ?? (this.engineVersion ? `${this.engineType}${this.engineVersion.majorVersion}` : undefined); + this.engineFamily = props.engineFamily; } public bindToInstance(_scope: core.Construct, options: InstanceEngineBindOptions): InstanceEngineConfig { @@ -391,6 +394,7 @@ class MySqlInstanceEngine extends InstanceEngineBase { majorVersion: version.mysqlMajorVersion, } : undefined, + engineFamily: 'MYSQL', }); } } @@ -586,6 +590,7 @@ class PostgresInstanceEngine extends InstanceEngineBase { } : undefined, features: version ? version?._features : { s3Import: 's3Import' }, + engineFamily: 'POSTGRESQL', }); } } diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index fa08049df2f97..bfc5ac47f1b24 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -50,6 +50,13 @@ export interface IDatabaseInstance extends IResource, ec2.IConnectable, secretsm */ readonly instanceEndpoint: Endpoint; + /** + * The engine of this database Instance. + * May be not known for imported Instances if it wasn't provided explicitly, + * or for read replicas. + */ + readonly engine?: IInstanceEngine; + /** * Add a new db proxy to this instance. */ @@ -90,6 +97,13 @@ export interface DatabaseInstanceAttributes { * The security groups of the instance. */ readonly securityGroups: ec2.ISecurityGroup[]; + + /** + * The engine of the existing database Instance. + * + * @default - the imported Instance's engine is unknown + */ + readonly engine?: IInstanceEngine; } /** @@ -110,6 +124,7 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase public readonly dbInstanceEndpointAddress = attrs.instanceEndpointAddress; public readonly dbInstanceEndpointPort = attrs.port.toString(); public readonly instanceEndpoint = new Endpoint(attrs.instanceEndpointAddress, attrs.port); + public readonly engine = attrs.engine; protected enableIamAuthentication = true; } @@ -769,6 +784,7 @@ export interface DatabaseInstanceSourceProps extends DatabaseInstanceNewProps { * A new source database instance (not a read replica) */ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDatabaseInstance { + public readonly engine?: IInstanceEngine; /** * The AWS Secrets Manager secret attached to the instance. */ @@ -785,6 +801,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa this.singleUserRotationApplication = props.engine.singleUserRotationApplication; this.multiUserRotationApplication = props.engine.multiUserRotationApplication; + this.engine = props.engine; let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, true); const engineConfig = props.engine.bindToInstance(this, { diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index 0caf78551b88d..a8439b652abc9 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct, CfnDeletionPolicy, CfnResource, RemovalPolicy } from '@aws-cdk/core'; -import { IInstanceEngine } from '../instance-engine'; +import { IEngine } from '../engine'; /** Common base of `DatabaseInstanceProps` and `DatabaseClusterBaseProps` that has only the S3 props */ export interface DatabaseS3ImportExportProps { @@ -56,7 +56,7 @@ export function setupS3ImportExport( return { s3ImportRole, s3ExportRole }; } -export function engineDescription(engine: IInstanceEngine) { +export function engineDescription(engine: IEngine) { return engine.engineType + (engine.engineVersion?.fullVersion ? `-${engine.engineVersion.fullVersion}` : ''); } diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index 7109202019c2f..dec7df1443723 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -3,8 +3,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import { IDatabaseCluster } from './cluster-ref'; +import { IEngine } from './engine'; import { IDatabaseInstance } from './instance'; -import { CfnDBCluster, CfnDBInstance, CfnDBProxy, CfnDBProxyTargetGroup } from './rds.generated'; +import { engineDescription } from './private/util'; +import { CfnDBProxy, CfnDBProxyTargetGroup } from './rds.generated'; /** * SessionPinningFilter @@ -47,7 +49,7 @@ export class ProxyTarget { * @param instance RDS database instance */ public static fromInstance(instance: IDatabaseInstance): ProxyTarget { - return new ProxyTarget(instance); + return new ProxyTarget(instance, undefined); } /** @@ -59,34 +61,26 @@ export class ProxyTarget { return new ProxyTarget(undefined, cluster); } - private constructor(private readonly dbInstance?: IDatabaseInstance, private readonly dbCluster?: IDatabaseCluster) {} + private constructor( + private readonly dbInstance: IDatabaseInstance | undefined, + private readonly dbCluster: IDatabaseCluster | undefined) { + } /** * Bind this target to the specified database proxy. */ public bind(_: DatabaseProxy): ProxyTargetConfig { - let engine: string | undefined; - if (this.dbCluster && this.dbInstance) { - throw new Error('Proxy cannot target both database cluster and database instance.'); - } else if (this.dbCluster) { - engine = (this.dbCluster.node.defaultChild as CfnDBCluster).engine; - } else if (this.dbInstance) { - engine = (this.dbInstance.node.defaultChild as CfnDBInstance).engine; + const engine: IEngine | undefined = this.dbInstance?.engine ?? this.dbCluster?.engine; + + if (!engine) { + const errorResource = this.dbCluster ?? this.dbInstance; + throw new Error(`Could not determine engine for proxy target '${errorResource?.node.path}'. ` + + 'Please provide it explicitly when importing the resource'); } - let engineFamily; - switch (engine) { - case 'aurora': - case 'aurora-mysql': - case 'mysql': - engineFamily = 'MYSQL'; - break; - case 'aurora-postgresql': - case 'postgres': - engineFamily = 'POSTGRESQL'; - break; - default: - throw new Error(`Unsupported engine type - ${engine}`); + const engineFamily = engine.engineFamily; + if (!engineFamily) { + throw new Error(`Engine '${engineDescription(engine)}' does not support proxies`); } return { @@ -105,12 +99,14 @@ export interface ProxyTargetConfig { * The engine family of the database instance or cluster this proxy connects with. */ readonly engineFamily: string; + /** * The database instances to which this proxy connects. * Either this or `dbClusters` will be set and the other `undefined`. * @default - `undefined` if `dbClusters` is set. */ readonly dbInstances?: IDatabaseInstance[]; + /** * The database clusters to which this proxy connects. * Either this or `dbInstances` will be set and the other `undefined`. diff --git a/packages/@aws-cdk/aws-rds/test/test.proxy.ts b/packages/@aws-cdk/aws-rds/test/test.proxy.ts index 7fa03684fa53b..db57aaf4d8b93 100644 --- a/packages/@aws-cdk/aws-rds/test/test.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/test.proxy.ts @@ -1,17 +1,25 @@ -import { ABSENT, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResourceLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as rds from '../lib'; +let stack: cdk.Stack; +let vpc: ec2.IVpc; + export = { + 'setUp'(cb: () => void) { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'VPC'); + + cb(); + }, + 'create a DB proxy from an instance'(test: Test) { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.MYSQL, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, }); @@ -23,68 +31,59 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::RDS::DBProxy', { - Properties: { - Auth: [ - { - AuthScheme: 'SECRETS', - IAMAuth: 'DISABLED', - SecretArn: { - Ref: 'InstanceSecretAttachment83BEE581', - }, + expect(stack).to(haveResourceLike('AWS::RDS::DBProxy', { + Auth: [ + { + AuthScheme: 'SECRETS', + IAMAuth: 'DISABLED', + SecretArn: { + Ref: 'InstanceSecretAttachment83BEE581', }, - ], - DBProxyName: 'Proxy', - EngineFamily: 'MYSQL', - RequireTLS: true, - RoleArn: { - 'Fn::GetAtt': [ - 'ProxyIAMRole2FE8AB0F', - 'Arn', - ], }, - VpcSubnetIds: [ - { - Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', - }, - { - Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', - }, + ], + DBProxyName: 'Proxy', + EngineFamily: 'MYSQL', + RequireTLS: true, + RoleArn: { + 'Fn::GetAtt': [ + 'ProxyIAMRole2FE8AB0F', + 'Arn', ], }, - }, ResourcePart.CompleteDefinition)); + VpcSubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + })); // THEN - expect(stack).to(haveResource('AWS::RDS::DBProxyTargetGroup', { - Properties: { - DBProxyName: { - Ref: 'ProxyCB0DFB71', - }, - ConnectionPoolConfigurationInfo: {}, - DBInstanceIdentifiers: [ - { - Ref: 'InstanceC1063A87', - }, - ], - TargetGroupName: 'default', + expect(stack).to(haveResourceLike('AWS::RDS::DBProxyTargetGroup', { + DBProxyName: { + Ref: 'ProxyCB0DFB71', }, - }, ResourcePart.CompleteDefinition)); + ConnectionPoolConfigurationInfo: {}, + DBInstanceIdentifiers: [ + { + Ref: 'InstanceC1063A87', + }, + ], + TargetGroupName: 'default', + })); test.done(); }, 'create a DB proxy from a cluster'(test: Test) { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new rds.DatabaseCluster(stack, 'Database', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7, }), - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceProps: { vpc }, }); // WHEN @@ -95,106 +94,132 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::RDS::DBProxy', { - Properties: { - Auth: [ - { - AuthScheme: 'SECRETS', - IAMAuth: 'DISABLED', - SecretArn: { - Ref: 'DatabaseSecretAttachmentE5D1B020', - }, + expect(stack).to(haveResourceLike('AWS::RDS::DBProxy', { + Auth: [ + { + AuthScheme: 'SECRETS', + IAMAuth: 'DISABLED', + SecretArn: { + Ref: 'DatabaseSecretAttachmentE5D1B020', }, - ], - DBProxyName: 'Proxy', - EngineFamily: 'POSTGRESQL', - RequireTLS: true, - RoleArn: { - 'Fn::GetAtt': [ - 'ProxyIAMRole2FE8AB0F', - 'Arn', - ], }, - VpcSubnetIds: [ - { - Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', - }, - { - Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', - }, + ], + DBProxyName: 'Proxy', + EngineFamily: 'POSTGRESQL', + RequireTLS: true, + RoleArn: { + 'Fn::GetAtt': [ + 'ProxyIAMRole2FE8AB0F', + 'Arn', ], }, - }, ResourcePart.CompleteDefinition)); + VpcSubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + })); // THEN - expect(stack).to(haveResource('AWS::RDS::DBProxyTargetGroup', { - Properties: { - DBProxyName: { - Ref: 'ProxyCB0DFB71', - }, - ConnectionPoolConfigurationInfo: {}, - DBClusterIdentifiers: [ - { - Ref: 'DatabaseB269D8BB', - }, - ], - TargetGroupName: 'default', + expect(stack).to(haveResourceLike('AWS::RDS::DBProxyTargetGroup', { + DBProxyName: { + Ref: 'ProxyCB0DFB71', }, - }, ResourcePart.CompleteDefinition)); + ConnectionPoolConfigurationInfo: {}, + DBClusterIdentifiers: [ + { + Ref: 'DatabaseB269D8BB', + }, + ], + DBInstanceIdentifiers: ABSENT, + TargetGroupName: 'default', + })); test.done(); }, - 'Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers'(test: Test) { + 'One or more secrets are required.'(test: Test) { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new rds.DatabaseCluster(stack, 'Database', { - engine: rds.DatabaseClusterEngine.auroraPostgres({ - version: rds.AuroraPostgresEngineVersion.VER_10_7, - }), - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7 }), + instanceProps: { vpc }, }); // WHEN - test.doesNotThrow(() => { + test.throws(() => { new rds.DatabaseProxy(stack, 'Proxy', { proxyTarget: rds.ProxyTarget.fromCluster(cluster), - secrets: [cluster.secret!], + secrets: [], // No secret vpc, }); - }, /Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers/); - - expect(stack).to(haveResource('AWS::RDS::DBProxyTargetGroup', { - DBInstanceIdentifiers: ABSENT, - }, ResourcePart.Properties)); + }, 'One or more secrets are required.'); test.done(); }, - 'One or more secrets are required.'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new rds.DatabaseCluster(stack, 'Database', { - engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_10_7 }), - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + 'fails when trying to create a proxy for a target without an engine'(test: Test) { + const importedCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes(stack, 'Cluster', { + clusterIdentifier: 'my-cluster', + }); + + test.throws(() => { + new rds.DatabaseProxy(stack, 'Proxy', { + proxyTarget: rds.ProxyTarget.fromCluster(importedCluster), vpc, - }, + secrets: [new secretsmanager.Secret(stack, 'Secret')], + }); + }, /Could not determine engine for proxy target 'Default\/Cluster'\. Please provide it explicitly when importing the resource/); + + test.done(); + }, + + "fails when trying to create a proxy for a target with an engine that doesn't have engineFamily"(test: Test) { + const importedInstance = rds.DatabaseInstance.fromDatabaseInstanceAttributes(stack, 'Cluster', { + instanceIdentifier: 'my-instance', + instanceEndpointAddress: 'instance-address', + port: 5432, + securityGroups: [], + engine: rds.DatabaseInstanceEngine.mariaDb({ + version: rds.MariaDbEngineVersion.VER_10_0_24, + }), }); - // WHEN test.throws(() => { new rds.DatabaseProxy(stack, 'Proxy', { - proxyTarget: rds.ProxyTarget.fromCluster(cluster), - secrets: [], // No secret + proxyTarget: rds.ProxyTarget.fromInstance(importedInstance), vpc, + secrets: [new secretsmanager.Secret(stack, 'Secret')], }); - }, 'One or more secrets are required.'); + }, /Engine 'mariadb-10\.0\.24' does not support proxies/); + + test.done(); + }, + + 'correctly creates a proxy for an imported Cluster if its engine is known'(test: Test) { + const importedCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes(stack, 'Cluster', { + clusterIdentifier: 'my-cluster', + engine: rds.DatabaseClusterEngine.auroraPostgres({ + version: rds.AuroraPostgresEngineVersion.VER_9_6_11, + }), + }); + + new rds.DatabaseProxy(stack, 'Proxy', { + proxyTarget: rds.ProxyTarget.fromCluster(importedCluster), + vpc, + secrets: [new secretsmanager.Secret(stack, 'Secret')], + }); + + expect(stack).to(haveResourceLike('AWS::RDS::DBProxy', { + EngineFamily: 'POSTGRESQL', + })); + expect(stack).to(haveResourceLike('AWS::RDS::DBProxyTargetGroup', { + DBClusterIdentifiers: [ + 'my-cluster', + ], + })); test.done(); }, From 451200e3dba6b8d80bc8396dc0b7d4d84f29cb56 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 23 Sep 2020 18:24:32 -0700 Subject: [PATCH 14/29] chore(rds): add extra abstract properties to base classes to fix the build (#10502) Caused by JSII issue: https://github.com/aws/jsii/issues/2040 ---- *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/instance.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 3ba13451e8731..f3f9300788e1c 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -227,6 +227,9 @@ interface DatabaseClusterBaseProps { * A new or imported clustered database. */ export abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster { + // only required because of JSII bug: https://github.com/aws/jsii/issues/2040 + public abstract readonly engine?: IClusterEngine; + /** * Identifier of the cluster */ diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index bfc5ac47f1b24..1479fa2fafe40 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -135,6 +135,8 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase public abstract readonly dbInstanceEndpointAddress: string; public abstract readonly dbInstanceEndpointPort: string; public abstract readonly instanceEndpoint: Endpoint; + // only required because of JSII bug: https://github.com/aws/jsii/issues/2040 + public abstract readonly engine?: IInstanceEngine; protected abstract enableIamAuthentication?: boolean; /** @@ -1096,6 +1098,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements public readonly dbInstanceEndpointAddress: string; public readonly dbInstanceEndpointPort: string; public readonly instanceEndpoint: Endpoint; + public readonly engine?: IInstanceEngine = undefined; protected readonly instanceType: ec2.InstanceType; constructor(scope: Construct, id: string, props: DatabaseInstanceReadReplicaProps) { From c8e72e5567409c3340b243c2c240bb1ba1afa1dc Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 24 Sep 2020 13:13:02 +0200 Subject: [PATCH 15/29] 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 16/29] 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 17/29] 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 18/29] 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 19/29] 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 20/29] 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 21/29] 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 22/29] 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 23/29] 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 24/29] 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 25/29] 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 26/29] 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 27/29] 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 28/29] 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 29/29] 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.