From 8038dacba26b29a8839402420ac31ae7b1b724f2 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Sun, 7 Jun 2020 09:26:30 +0200 Subject: [PATCH] chore(bootstrap): split file/image publishing roles (#8403) For security purposes, we decided that it would be lower risk to assume a different role when we publish S3 assets and when we publish ECR assets. The reason is that ECR publishers execute `docker build` which can potentially execute 3rd party code (via a base docker image). This change modifies the conventional name for the publishing roles as well as adds a set of properties to the `DefaultStackSynthesizer` to allow customization as needed. This is a resubmission of #8319. That one was failing backwards regression tests... and for good reason! However in this case, the regression was intended (and deemed acceptable since we haven't officially "released" the feature we're breaking yet). Unfortunately the mechanism to skip integration tests during the regression tests has been broken recently, so had to be reintroduced here. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- allowed-breaking-changes.txt | 4 + .../stack-synthesizers/default-synthesizer.ts | 62 ++++++--- .../test.new-style-synthesis.ts | 74 +++++++++- packages/aws-cdk/.gitignore | 5 +- packages/aws-cdk/.npmignore | 3 + .../lib/api/bootstrap/bootstrap-template.yaml | 48 ++++++- .../integ/cli-regression-patches/README.md | 54 ++++++++ .../cli-regression-patches/v1.44.0/NOTES.md | 18 +++ .../v1.44.0/bootstrapping.integtest.js | 126 ++++++++++++++++++ .../v1.44.0/test.sh} | 15 ++- packages/aws-cdk/test/integ/cli.exclusions.js | 70 ---------- packages/aws-cdk/test/integ/cli/README.md | 7 +- packages/aws-cdk/test/integ/cli/app/app.js | 2 +- .../aws-cdk/test/integ/cli/aws-helpers.ts | 5 + .../test/integ/cli/bootstrapping.integtest.ts | 36 ++++- .../aws-cdk/test/integ/cli/cdk-helpers.ts | 8 +- .../aws-cdk/test/integ/cli/cli.integtest.ts | 57 ++++---- packages/aws-cdk/test/integ/cli/jest.setup.js | 8 -- .../aws-cdk/test/integ/cli/skip-tests.txt | 8 ++ .../aws-cdk/test/integ/cli/test-helpers.ts | 23 ++++ packages/aws-cdk/test/integ/cli/test.sh | 47 ++----- ...est-cli-regression-against-current-code.sh | 14 +- ...t-cli-regression-against-latest-release.sh | 2 +- packages/cdk-assets/lib/private/shell.ts | 6 +- 24 files changed, 504 insertions(+), 198 deletions(-) create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/README.md create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md create mode 100644 packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js rename packages/aws-cdk/test/integ/{cli/test-jest.sh => cli-regression-patches/v1.44.0/test.sh} (52%) delete mode 100644 packages/aws-cdk/test/integ/cli.exclusions.js delete mode 100644 packages/aws-cdk/test/integ/cli/jest.setup.js create mode 100644 packages/aws-cdk/test/integ/cli/skip-tests.txt create mode 100644 packages/aws-cdk/test/integ/cli/test-helpers.ts diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 8b137891791fe..e6bdc57ed11ae 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -1 +1,5 @@ +removed:@aws-cdk/core.BootstraplessSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN +removed:@aws-cdk/core.DefaultStackSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN +removed:@aws-cdk/core.DefaultStackSynthesizerProps.assetPublishingExternalId +removed:@aws-cdk/core.DefaultStackSynthesizerProps.assetPublishingRoleArn diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index ace086a9c4bd3..5cef2ac3daab4 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -13,6 +13,11 @@ import { IStackSynthesizer } from './types'; export const BOOTSTRAP_QUALIFIER_CONTEXT = '@aws-cdk/core:bootstrapQualifier'; +/** + * The minimum bootstrap stack version required by this app. + */ +const MIN_BOOTSTRAP_STACK_VERSION = 2; + /** * Configuration properties for DefaultStackSynthesizer */ @@ -44,7 +49,7 @@ export interface DefaultStackSynthesizerProps { readonly imageAssetsRepositoryName?: string; /** - * The role to use to publish assets to this environment + * The role to use to publish file assets to the S3 bucket in this environment * * You must supply this if you have given a non-standard name to the publishing role. * @@ -52,16 +57,36 @@ export interface DefaultStackSynthesizerProps { * be replaced with the values of qualifier and the stack's account and region, * respectively. * - * @default DefaultStackSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN + * @default DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN */ - readonly assetPublishingRoleArn?: string; + readonly fileAssetPublishingRoleArn?: string; /** - * External ID to use when assuming role for asset publishing + * External ID to use when assuming role for file asset publishing * * @default - No external ID */ - readonly assetPublishingExternalId?: string; + readonly fileAssetPublishingExternalId?: string; + + /** + * The role to use to publish image assets to the ECR repository in this environment + * + * You must supply this if you have given a non-standard name to the publishing role. + * + * The placeholders `${Qualifier}`, `${AWS::AccountId}` and `${AWS::Region}` will + * be replaced with the values of qualifier and the stack's account and region, + * respectively. + * + * @default DefaultStackSynthesizer.DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN + */ + readonly imageAssetPublishingRoleArn?: string; + + /** + * External ID to use when assuming role for image asset publishing + * + * @default - No external ID + */ + readonly imageAssetPublishingExternalId?: string; /** * The role to assume to initiate a deployment in this environment @@ -126,9 +151,14 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { public static readonly DEFAULT_DEPLOY_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region}'; /** - * Default asset publishing role ARN. + * Default asset publishing role ARN for file (S3) assets. + */ + public static readonly DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region}'; + + /** + * Default asset publishing role ARN for image (ECR) assets. */ - public static readonly DEFAULT_ASSET_PUBLISHING_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-publishing-role-${AWS::AccountId}-${AWS::Region}'; + public static readonly DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region}'; /** * Default image assets repository name @@ -145,7 +175,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { private repositoryName?: string; private _deployRoleArn?: string; private _cloudFormationExecutionRoleArn?: string; - private assetPublishingRoleArn?: string; + private fileAssetPublishingRoleArn?: string; + private imageAssetPublishingRoleArn?: string; private readonly files: NonNullable = {}; private readonly dockerImages: NonNullable = {}; @@ -178,7 +209,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { this.repositoryName = specialize(this.props.imageAssetsRepositoryName ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSETS_REPOSITORY_NAME); this._deployRoleArn = specialize(this.props.deployRoleArn ?? DefaultStackSynthesizer.DEFAULT_DEPLOY_ROLE_ARN); this._cloudFormationExecutionRoleArn = specialize(this.props.cloudFormationExecutionRole ?? DefaultStackSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN); - this.assetPublishingRoleArn = specialize(this.props.assetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN); + this.fileAssetPublishingRoleArn = specialize(this.props.fileAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN); + this.imageAssetPublishingRoleArn = specialize(this.props.imageAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN); // tslint:enable:max-line-length } @@ -199,8 +231,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { bucketName: this.bucketName, objectKey, region: resolvedOr(this.stack.region, undefined), - assumeRoleArn: this.assetPublishingRoleArn, - assumeRoleExternalId: this.props.assetPublishingExternalId, + assumeRoleArn: this.fileAssetPublishingRoleArn, + assumeRoleExternalId: this.props.fileAssetPublishingExternalId, }, }, }; @@ -237,8 +269,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { repositoryName: this.repositoryName, imageTag, region: resolvedOr(this.stack.region, undefined), - assumeRoleArn: this.assetPublishingRoleArn, - assumeRoleExternalId: this.props.assetPublishingExternalId, + assumeRoleArn: this.imageAssetPublishingRoleArn, + assumeRoleExternalId: this.props.imageAssetPublishingExternalId, }, }, }; @@ -262,7 +294,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { assumeRoleArn: this._deployRoleArn, cloudFormationExecutionRoleArn: this._cloudFormationExecutionRoleArn, stackTemplateAssetObjectUrl: templateManifestUrl, - requiresBootstrapStackVersion: 1, + requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, }, [artifactId]); } @@ -344,7 +376,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { type: cxschema.ArtifactType.ASSET_MANIFEST, properties: { file: manifestFile, - requiresBootstrapStackVersion: 1, + requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, }, }); diff --git a/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts b/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts index 723e7969c1d06..43591b9931148 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/test.new-style-synthesis.ts @@ -2,7 +2,7 @@ import * as asset_schema from '@aws-cdk/cdk-assets-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import { Test } from 'nodeunit'; -import { App, CfnResource, FileAssetPackaging, Stack } from '../../lib'; +import { App, CfnResource, DefaultStackSynthesizer, FileAssetPackaging, Stack } from '../../lib'; import { evaluateCFN } from '../evaluate-cfn'; const CFN_CONTEXT = { @@ -50,7 +50,7 @@ export = { 'current_account-current_region': { bucketName: 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', objectKey: '4bdae6e3b1b15f08c889d6c9133f24731ee14827a9a9ab9b6b6a9b42b6d34910', - assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-publishing-role-${AWS::AccountId}-${AWS::Region}', + assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}', }, }, }); @@ -106,22 +106,75 @@ export = { const asm = app.synth(); // THEN - we have an asset manifest with both assets and the stack template in there - const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; - test.ok(manifestArtifact); - const manifest: asset_schema.ManifestFile = JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); + const manifest = readAssetManifest(asm); test.equals(Object.keys(manifest.files || {}).length, 2); test.equals(Object.keys(manifest.dockerImages || {}).length, 1); // THEN - every artifact has an assumeRoleArn - for (const file of Object.values({...manifest.files, ...manifest.dockerImages})) { + for (const file of Object.values(manifest.files ?? {})) { + for (const destination of Object.values(file.destinations)) { + test.deepEqual(destination.assumeRoleArn, 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}'); + } + } + + for (const file of Object.values(manifest.dockerImages ?? {})) { for (const destination of Object.values(file.destinations)) { - test.ok(destination.assumeRoleArn); + test.deepEqual(destination.assumeRoleArn, 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}'); } } test.done(); }, + + 'customize publishing resources'(test: Test) { + // GIVEN + const myapp = new App(); + + // WHEN + const mystack = new Stack(myapp, 'mystack', { + synthesizer: new DefaultStackSynthesizer({ + fileAssetsBucketName: 'file-asset-bucket', + fileAssetPublishingRoleArn: 'file:role:arn', + fileAssetPublishingExternalId: 'file-external-id', + + imageAssetsRepositoryName: 'image-ecr-repository', + imageAssetPublishingRoleArn: 'image:role:arn', + imageAssetPublishingExternalId: 'image-external-id', + }), + }); + + mystack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'file-asset-hash', + }); + + mystack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'docker-asset-hash', + }); + + // THEN + const asm = myapp.synth(); + const manifest = readAssetManifest(asm); + + test.deepEqual(manifest.files?.['file-asset-hash']?.destinations?.['current_account-current_region'], { + bucketName: 'file-asset-bucket', + objectKey: 'file-asset-hash', + assumeRoleArn: 'file:role:arn', + assumeRoleExternalId: 'file-external-id', + }); + + test.deepEqual(manifest.dockerImages?.['docker-asset-hash']?.destinations?.['current_account-current_region'] , { + repositoryName: 'image-ecr-repository', + imageTag: 'docker-asset-hash', + assumeRoleArn: 'image:role:arn', + assumeRoleExternalId: 'image-external-id', + }); + + test.done(); + }, }; /** @@ -135,4 +188,11 @@ function evalCFN(value: any) { function isAssetManifest(x: cxapi.CloudArtifact): x is cxapi.AssetManifestArtifact { return x instanceof cxapi.AssetManifestArtifact; +} + +function readAssetManifest(asm: cxapi.CloudAssembly): asset_schema.ManifestFile { + const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; + if (!manifestArtifact) { throw new Error('no asset manifest in assembly'); } + + return JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); } \ No newline at end of file diff --git a/packages/aws-cdk/.gitignore b/packages/aws-cdk/.gitignore index 35d7fd343f085..59c135c41f21b 100644 --- a/packages/aws-cdk/.gitignore +++ b/packages/aws-cdk/.gitignore @@ -32,5 +32,6 @@ cdk.context.json # as the subdirs contain .js files that should be committed) test/integ/cli/*.js test/integ/cli/*.d.ts -!test/integ/cli/jest.setup.js -!test/integ/cli/jest.config.js +!test/integ/cli-regression-patches/**/* + +.DS_Store diff --git a/packages/aws-cdk/.npmignore b/packages/aws-cdk/.npmignore index 2a37a179d8d4a..49b9729723982 100644 --- a/packages/aws-cdk/.npmignore +++ b/packages/aws-cdk/.npmignore @@ -25,3 +25,6 @@ tsconfig.json jest.config.js !lib/init-templates/**/jest.config.js !test/integ/cli/jest.config.js +!test/integ/cli-regression-patches/**/* + +.DS_Store diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index 4da1a80bbeedc..5b61c2e99e7dd 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -106,7 +106,7 @@ Resources: Effect: Allow Principal: AWS: - Fn::Sub: "${PublishingRole.Arn}" + Fn::Sub: "${FilePublishingRole.Arn}" Resource: "*" Condition: CreateNewKey StagingBucket: @@ -158,7 +158,7 @@ Resources: - HasCustomContainerAssetsRepositoryName - Fn::Sub: "${ContainerAssetsRepositoryName}" - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} - PublishingRole: + FilePublishingRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: @@ -177,8 +177,28 @@ Resources: Ref: TrustedAccounts - Ref: AWS::NoValue RoleName: - Fn::Sub: cdk-${Qualifier}-publishing-role-${AWS::AccountId}-${AWS::Region} - PublishingRoleDefaultPolicy: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + ImagePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + FilePublishingRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: @@ -206,6 +226,16 @@ Resources: - CreateNewKey - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} + Version: '2012-10-17' + Roles: + - Ref: FilePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + ImagePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: - Action: - ecr:PutImage - ecr:InitiateLayerUpload @@ -223,9 +253,9 @@ Resources: Effect: Allow Version: '2012-10-17' Roles: - - Ref: PublishingRole + - Ref: ImagePublishingRole PolicyName: - Fn::Sub: cdk-${Qualifier}-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} DeploymentActionRole: Type: AWS::IAM::Role Properties: @@ -317,10 +347,14 @@ Outputs: Description: The domain name of the S3 bucket owned by the CDK toolkit stack Value: Fn::Sub: "${StagingBucket.RegionalDomainName}" + ImageRepositoryName: + Description: The name of the ECR repository which hosts docker image assets + Value: + Fn::Sub: "${ContainerAssetsRepository}" BootstrapVersion: Description: The version of the bootstrap resources that are currently mastered in this stack - Value: '1' + Value: '2' Export: Name: Fn::Sub: CdkBootstrap-${Qualifier}-Version \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/README.md b/packages/aws-cdk/test/integ/cli-regression-patches/README.md new file mode 100644 index 0000000000000..c930255e85809 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/README.md @@ -0,0 +1,54 @@ +Regression Test Patches +======================== + +The regression test suite will use the test suite of an OLD version +of the CLI when testing a NEW version of the CLI, to make sure the +old tests still pass. + +Sometimes though, the old tests won't pass. This can happen when we +introduce breaking changes to the framework or CLI (for something serious, +such as security reasons), or maybe because we had a bug in an old +version that happened to pass, but now the test needs to be updated +in order to pass a bugfix. + +## Mechanism + +The files in this directory will be copied over the test directory +so that you can exclude tests from running, or patch up test running +scripts. + +Files will be copied like so: + +``` +aws-cdk/test/integ/cli-regression-patches/vX.Y.Z/* + +# will be copied into + +aws-cdk/test/integ/cli +``` + +For example, to skip a certain integration test during regression +testing, create the following file: + +``` +cli-regression-patches/vX.Y.Z/skip-tests.txt +``` + +If you need to replace source files, it's probably best to stick +compiled `.js` files in here. `.ts` source files wouldn't compile +because they'd be missing `imports`. + +## Versioning + +The patch sets are versioned, so that they will only be applied for +a certain version of the tests and will automatically age out when +we proceed past that release. + +The version in the directory name needs to be named after the +version that contains the *tests* we're running, that need to be +patched. + +So for example, if we are running regression tests for release +candidate `1.45.0`, we would use the tests from released version +`1.44.0`, and so you would call the patch directory `v1.44.0`. + diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md new file mode 100644 index 0000000000000..961813bc3107b --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/NOTES.md @@ -0,0 +1,18 @@ +Patch notes: + +- Replace `test.sh` since we removed the old test exclusion + mechanism, and the `cli.exclusions.js` file that the old `test.sh` + depended upon. + +- We removed the old asset-publishing role from the new bootstrap + stack, and split it into separate file- and docker-publishing roles. + Since 1.44.0 would still expect the old asset-publishing role, + its test would fail, so we disable it: + +``` +test.skip('deploy new style synthesis to new style bootstrap', async () => { +``` + +There is a better mechanism for skipping certain tests by using `skip-tests.txt`, +but that one is only available AFTER this release, so for this version we just replace +source files. diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js new file mode 100644 index 0000000000000..d715afa923e1d --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/bootstrapping.integtest.js @@ -0,0 +1,126 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const aws_helpers_1 = require("./aws-helpers"); +const cdk_helpers_1 = require("./cdk-helpers"); +jest.setTimeout(600000); +const QUALIFIER = randomString(); +beforeAll(async () => { + await cdk_helpers_1.prepareAppFixture(); +}); +beforeEach(async () => { + await cdk_helpers_1.cleanup(); +}); +afterEach(async () => { + await cdk_helpers_1.cleanup(); +}); +test('can bootstrap without execution', async () => { + var _a; + const bootstrapStackName = cdk_helpers_1.fullStackName('bootstrap-stack'); + await cdk_helpers_1.cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--no-execute']); + const resp = await aws_helpers_1.cloudFormation('describeStacks', { + StackName: bootstrapStackName, + }); + expect((_a = resp.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); +}); +test('upgrade legacy bootstrap stack to new bootstrap stack while in use', async () => { + const bootstrapStackName = cdk_helpers_1.fullStackName('bootstrap-stack'); + const legacyBootstrapBucketName = `aws-cdk-bootstrap-integ-test-legacy-bckt-${randomString()}`; + const newBootstrapBucketName = `aws-cdk-bootstrap-integ-test-v2-bckt-${randomString()}`; + cdk_helpers_1.rememberToDeleteBucket(legacyBootstrapBucketName); // This one will leak + cdk_helpers_1.rememberToDeleteBucket(newBootstrapBucketName); // This one shouldn't leak if the test succeeds, but let's be safe in case it doesn't + // Legacy bootstrap + await cdk_helpers_1.cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--bootstrap-bucket-name', legacyBootstrapBucketName]); + // Deploy stack that uses file assets + await cdk_helpers_1.cdkDeploy('lambda', { + options: ['--toolkit-stack-name', bootstrapStackName], + }); + // Upgrade bootstrap stack to "new" style + await cdk_helpers_1.cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--bootstrap-bucket-name', newBootstrapBucketName, + '--qualifier', QUALIFIER], { + modEnv: { + CDK_NEW_BOOTSTRAP: '1', + }, + }); + // (Force) deploy stack again + // --force to bypass the check which says that the template hasn't changed. + await cdk_helpers_1.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--force', + ], + }); +}); +test.skip('deploy new style synthesis to new style bootstrap', async () => { + const bootstrapStackName = cdk_helpers_1.fullStackName('bootstrap-stack'); + await cdk_helpers_1.cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--qualifier', QUALIFIER, + '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess', + ], { + modEnv: { + CDK_NEW_BOOTSTRAP: '1', + }, + }); + // Deploy stack that uses file assets + await cdk_helpers_1.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${QUALIFIER}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +}); +test('deploy old style synthesis to new style bootstrap', async () => { + const bootstrapStackName = cdk_helpers_1.fullStackName('bootstrap-stack'); + await cdk_helpers_1.cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--qualifier', QUALIFIER, + '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess', + ], { + modEnv: { + CDK_NEW_BOOTSTRAP: '1', + }, + }); + // Deploy stack that uses file assets + await cdk_helpers_1.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + ], + }); +}); +test('deploying new style synthesis to old style bootstrap fails', async () => { + const bootstrapStackName = cdk_helpers_1.fullStackName('bootstrap-stack'); + await cdk_helpers_1.cdk(['bootstrap', '--toolkit-stack-name', bootstrapStackName]); + // Deploy stack that uses file assets, this fails because the bootstrap stack + // is version checked. + await expect(cdk_helpers_1.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + })).rejects.toThrow('exited with error'); +}); +test('can create multiple legacy bootstrap stacks', async () => { + var _a; + const bootstrapStackName1 = cdk_helpers_1.fullStackName('bootstrap-stack-1'); + const bootstrapStackName2 = cdk_helpers_1.fullStackName('bootstrap-stack-2'); + // deploy two toolkit stacks into the same environment (see #1416) + // one with tags + await cdk_helpers_1.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName1, '--tags', 'Foo=Bar']); + await cdk_helpers_1.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName2]); + const response = await aws_helpers_1.cloudFormation('describeStacks', { StackName: bootstrapStackName1 }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Tags).toEqual([ + { Key: 'Foo', Value: 'Bar' }, + ]); +}); +function randomString() { + // Crazy + return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9vdHN0cmFwcGluZy5pbnRlZ3Rlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJib290c3RyYXBwaW5nLmludGVndGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLCtDQUErQztBQUMvQywrQ0FBa0g7QUFFbEgsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFPLENBQUMsQ0FBQztBQUV6QixNQUFNLFNBQVMsR0FBRyxZQUFZLEVBQUUsQ0FBQztBQUVqQyxTQUFTLENBQUMsS0FBSyxJQUFJLEVBQUU7SUFDbkIsTUFBTSwrQkFBaUIsRUFBRSxDQUFDO0FBQzVCLENBQUMsQ0FBQyxDQUFDO0FBRUgsVUFBVSxDQUFDLEtBQUssSUFBSSxFQUFFO0lBQ3BCLE1BQU0scUJBQU8sRUFBRSxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxDQUFDO0FBRUgsU0FBUyxDQUFDLEtBQUssSUFBSSxFQUFFO0lBQ25CLE1BQU0scUJBQU8sRUFBRSxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxDQUFDO0FBRUgsSUFBSSxDQUFDLGlDQUFpQyxFQUFFLEtBQUssSUFBSSxFQUFFOztJQUNqRCxNQUFNLGtCQUFrQixHQUFHLDJCQUFhLENBQUMsaUJBQWlCLENBQUMsQ0FBQztJQUU1RCxNQUFNLGlCQUFHLENBQUMsQ0FBQyxXQUFXO1FBQ3BCLHNCQUFzQixFQUFFLGtCQUFrQjtRQUMxQyxjQUFjLENBQUMsQ0FBQyxDQUFDO0lBRW5CLE1BQU0sSUFBSSxHQUFHLE1BQU0sNEJBQWMsQ0FBQyxnQkFBZ0IsRUFBRTtRQUNsRCxTQUFTLEVBQUUsa0JBQWtCO0tBQzlCLENBQUMsQ0FBQztJQUVILE1BQU0sT0FBQyxJQUFJLENBQUMsTUFBTSwwQ0FBRyxDQUFDLEVBQUUsV0FBVyxDQUFDLENBQUMsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFDckUsQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLENBQUMsb0VBQW9FLEVBQUUsS0FBSyxJQUFJLEVBQUU7SUFDcEYsTUFBTSxrQkFB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RSxDQUFDLENBQUM7QUFDL0QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNsb3VkRm9ybWF0aW9uIH0gZnJvbSAnLi9hd3MtaGVscGVycyc7XG5pbXBvcnQgeyBjZGssIGNka0RlcGxveSwgY2xlYW51cCwgZnVsbFN0YWNrTmFtZSwgcHJlcGFyZUFwcEZpeHR1cmUsIHJlbWVtYmVyVG9EZWxldGVCdWNrZXQgfSBmcm9tICcuL2Nkay1oZWxwZXJzJztcblxuamVzdC5zZXRUaW1lb3V0KDYwMF8wMDApO1xuXG5jb25zdCBRVUFMSUZJRVIgPSByYW5kb21TdHJpbmcoKTtcblxuYmVmb3JlQWxsKGFzeW5jICgpID0+IHtcbiAgYXdhaXQgcHJlcGFyZUFwcEZpeHR1cmUoKTtcbn0pO1xuXG5iZWZvcmVFYWNoKGFzeW5jICgpID0+IHtcbiAgYXdhaXQgY2xlYW51cCgpO1xufSk7XG5cbmFmdGVyRWFjaChhc3luYyAoKSA9PiB7XG4gIGF3YWl0IGNsZWFudXAoKTtcbn0pO1xuXG50ZXN0KCdjYW4gYm9vdHN0cmFwIHdpdGhvdXQgZXhlY3V0aW9uJywgYXN5bmMgKCkgPT4ge1xuICBjb25zdCBib290c3RyYXBTdGFja05hbWUgPSBmdWxsU3RhY2tOYW1lKCdib290c3RyYXAtc3RhY2snKTtcblxuICBhd2FpdCBjZGsoWydib290c3RyYXAnLFxuICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICAnLS1uby1leGVjdXRlJ10pO1xuXG4gIGNvbnN0IHJlc3AgPSBhd2FpdCBjbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7XG4gICAgU3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUsXG4gIH0pO1xuXG4gIGV4cGVjdChyZXNwLlN0YWNrcz8uWzBdLlN0YWNrU3RhdHVzKS50b0VxdWFsKCdSRVZJRVdfSU5fUFJPR1JFU1MnKTtcbn0pO1xuXG50ZXN0KCd1cGdyYWRlIGxlZ2FjeSBib290c3RyYXAgc3RhY2sgdG8gbmV3IGJvb3RzdHJhcCBzdGFjayB3aGlsZSBpbiB1c2UnLCBhc3luYyAoKSA9PiB7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZSA9IGZ1bGxTdGFja05hbWUoJ2Jvb3RzdHJhcC1zdGFjaycpO1xuXG4gIGNvbnN0IGxlZ2FjeUJvb3RzdHJhcEJ1Y2tldE5hbWUgPSBgYXdzLWNkay1ib290c3RyYXAtaW50ZWctdGVzdC1sZWdhY3ktYmNrdC0ke3JhbmRvbVN0cmluZygpfWA7XG4gIGNvbnN0IG5ld0Jvb3RzdHJhcEJ1Y2tldE5hbWUgPSBgYXdzLWNkay1ib290c3RyYXAtaW50ZWctdGVzdC12Mi1iY2t0LSR7cmFuZG9tU3RyaW5nKCl9YDtcbiAgcmVtZW1iZXJUb0RlbGV0ZUJ1Y2tldChsZWdhY3lCb290c3RyYXBCdWNrZXROYW1lKTsgIC8vIFRoaXMgb25lIHdpbGwgbGVha1xuICByZW1lbWJlclRvRGVsZXRlQnVja2V0KG5ld0Jvb3RzdHJhcEJ1Y2tldE5hbWUpOyAgICAgLy8gVGhpcyBvbmUgc2hvdWxkbid0IGxlYWsgaWYgdGhlIHRlc3Qgc3VjY2VlZHMsIGJ1dCBsZXQncyBiZSBzYWZlIGluIGNhc2UgaXQgZG9lc24ndFxuXG4gIC8vIExlZ2FjeSBib290c3RyYXBcbiAgYXdhaXQgY2RrKFsnYm9vdHN0cmFwJyxcbiAgICAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUsXG4gICAgJy0tYm9vdHN0cmFwLWJ1Y2tldC1uYW1lJywgbGVnYWN5Qm9vdHN0cmFwQnVja2V0TmFtZV0pO1xuXG4gIC8vIERlcGxveSBzdGFjayB0aGF0IHVzZXMgZmlsZSBhc3NldHNcbiAgYXdhaXQgY2RrRGVwbG95KCdsYW1iZGEnLCB7XG4gICAgb3B0aW9uczogWyctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZV0sXG4gIH0pO1xuXG4gIC8vIFVwZ3JhZGUgYm9vdHN0cmFwIHN0YWNrIHRvIFwibmV3XCIgc3R5bGVcbiAgYXdhaXQgY2RrKFsnYm9vdHN0cmFwJyxcbiAgICAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUsXG4gICAgJy0tYm9vdHN0cmFwLWJ1Y2tldC1uYW1lJywgbmV3Qm9vdHN0cmFwQnVja2V0TmFtZSxcbiAgICAnLS1xdWFsaWZpZXInLCBRVUFMSUZJRVJdLCB7XG4gICAgbW9kRW52OiB7XG4gICAgICBDREtfTkVXX0JPT1RTVFJBUDogJzEnLFxuICAgIH0sXG4gIH0pO1xuXG4gIC8vIChGb3JjZSkgZGVwbG95IHN0YWNrIGFnYWluXG4gIC8vIC0tZm9yY2UgdG8gYnlwYXNzIHRoZSBjaGVjayB3aGljaCBzYXlzIHRoYXQgdGhlIHRlbXBsYXRlIGhhc24ndCBjaGFuZ2VkLlxuICBhd2FpdCBjZGtEZXBsb3koJ2xhbWJkYScsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUsXG4gICAgICAnLS1mb3JjZScsXG4gICAgXSxcbiAgfSk7XG59KTtcblxudGVzdCgnZGVwbG95IG5ldyBzdHlsZSBzeW50aGVzaXMgdG8gbmV3IHN0eWxlIGJvb3RzdHJhcCcsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZnVsbFN0YWNrTmFtZSgnYm9vdHN0cmFwLXN0YWNrJyk7XG5cbiAgYXdhaXQgY2RrKFsnYm9vdHN0cmFwJyxcbiAgICAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUsXG4gICAgJy0tcXVhbGlmaWVyJywgUVVBTElGSUVSLFxuICAgICctLWNsb3VkZm9ybWF0aW9uLWV4ZWN1dGlvbi1wb2xpY2llcycsICdhcm46YXdzOmlhbTo6YXdzOnBvbGljeS9BZG1pbmlzdHJhdG9yQWNjZXNzJyxcbiAgXSwge1xuICAgIG1vZEVudjoge1xuICAgICAgQ0RLX05FV19CT09UU1RSQVA6ICcxJyxcbiAgICB9LFxuICB9KTtcblxuICAvLyBEZXBsb3kgc3RhY2sgdGhhdCB1c2VzIGZpbGUgYXNzZXRzXG4gIGF3YWl0IGNka0RlcGxveSgnbGFtYmRhJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICAgICctLWNvbnRleHQnLCBgQGF3cy1jZGsvY29yZTpib290c3RyYXBRdWFsaWZpZXI9JHtRVUFMSUZJRVJ9YCxcbiAgICAgICctLWNvbnRleHQnLCAnQGF3cy1jZGsvY29yZTpuZXdTdHlsZVN0YWNrU3ludGhlc2lzPTEnLFxuICAgIF0sXG4gIH0pO1xufSk7XG5cbnRlc3QoJ2RlcGxveSBvbGQgc3R5bGUgc3ludGhlc2lzIHRvIG5ldyBzdHlsZSBib290c3RyYXAnLCBhc3luYyAoKSA9PiB7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZSA9IGZ1bGxTdGFja05hbWUoJ2Jvb3RzdHJhcC1zdGFjaycpO1xuXG4gIGF3YWl0IGNkayhbJ2Jvb3RzdHJhcCcsXG4gICAgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgICctLXF1YWxpZmllcicsIFFVQUxJRklFUixcbiAgICAnLS1jbG91ZGZvcm1hdGlvbi1leGVjdXRpb24tcG9saWNpZXMnLCAnYXJuOmF3czppYW06OmF3czpwb2xpY3kvQWRtaW5pc3RyYXRvckFjY2VzcycsXG4gIF0sIHtcbiAgICBtb2RFbnY6IHtcbiAgICAgIENES19ORVdfQk9PVFNUUkFQOiAnMScsXG4gICAgfSxcbiAgfSk7XG5cbiAgLy8gRGVwbG95IHN0YWNrIHRoYXQgdXNlcyBmaWxlIGFzc2V0c1xuICBhd2FpdCBjZGtEZXBsb3koJ2xhbWJkYScsIHtcbiAgICBvcHRpb25zOiBbXG4gICAgICAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUsXG4gICAgXSxcbiAgfSk7XG59KTtcblxudGVzdCgnZGVwbG95aW5nIG5ldyBzdHlsZSBzeW50aGVzaXMgdG8gb2xkIHN0eWxlIGJvb3RzdHJhcCBmYWlscycsIGFzeW5jICgpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZnVsbFN0YWNrTmFtZSgnYm9vdHN0cmFwLXN0YWNrJyk7XG5cbiAgYXdhaXQgY2RrKFsnYm9vdHN0cmFwJywgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lXSk7XG5cbiAgLy8gRGVwbG95IHN0YWNrIHRoYXQgdXNlcyBmaWxlIGFzc2V0cywgdGhpcyBmYWlscyBiZWNhdXNlIHRoZSBib290c3RyYXAgc3RhY2tcbiAgLy8gaXMgdmVyc2lvbiBjaGVja2VkLlxuICBhd2FpdCBleHBlY3QoY2RrRGVwbG95KCdsYW1iZGEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgICAgJy0tY29udGV4dCcsICdAYXdzLWNkay9jb3JlOm5ld1N0eWxlU3RhY2tTeW50aGVzaXM9MScsXG4gICAgXSxcbiAgfSkpLnJlamVjdHMudG9UaHJvdygnZXhpdGVkIHdpdGggZXJyb3InKTtcbn0pO1xuXG50ZXN0KCdjYW4gY3JlYXRlIG11bHRpcGxlIGxlZ2FjeSBib290c3RyYXAgc3RhY2tzJywgYXN5bmMgKCkgPT4ge1xuICBjb25zdCBib290c3RyYXBTdGFja05hbWUxID0gZnVsbFN0YWNrTmFtZSgnYm9vdHN0cmFwLXN0YWNrLTEnKTtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lMiA9IGZ1bGxTdGFja05hbWUoJ2Jvb3RzdHJhcC1zdGFjay0yJyk7XG5cbiAgLy8gZGVwbG95IHR3byB0b29sa2l0IHN0YWNrcyBpbnRvIHRoZSBzYW1lIGVudmlyb25tZW50IChzZWUgIzE0MTYpXG4gIC8vIG9uZSB3aXRoIHRhZ3NcbiAgYXdhaXQgY2RrKFsnYm9vdHN0cmFwJywgJy12JywgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lMSwgJy0tdGFncycsICdGb289QmFyJ10pO1xuICBhd2FpdCBjZGsoWydib290c3RyYXAnLCAnLXYnLCAnLS10b29sa2l0LXN0YWNrLW5hbWUnLCBib290c3RyYXBTdGFja05hbWUyXSk7XG5cbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7IFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lMSB9KTtcbiAgZXhwZWN0KHJlc3BvbnNlLlN0YWNrcz8uWzBdLlRhZ3MpLnRvRXF1YWwoW1xuICAgIHsgS2V5OiAnRm9vJywgVmFsdWU6ICdCYXInIH0sXG4gIF0pO1xufSk7XG5cbmZ1bmN0aW9uIHJhbmRvbVN0cmluZygpIHtcbiAgLy8gQ3JhenlcbiAgcmV0dXJuIE1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnJlcGxhY2UoL1teYS16MC05XSsvZywgJycpO1xufVxuIl19 diff --git a/packages/aws-cdk/test/integ/cli/test-jest.sh b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/test.sh similarity index 52% rename from packages/aws-cdk/test/integ/cli/test-jest.sh rename to packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/test.sh index 3367ac0129919..482956df450f4 100755 --- a/packages/aws-cdk/test/integ/cli/test-jest.sh +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.44.0/test.sh @@ -1,10 +1,17 @@ #!/bin/bash -# A number of tests have been written in TS/Jest, instead of bash. -# This script runs them. - set -euo pipefail scriptdir=$(cd $(dirname $0) && pwd) +echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' +echo 'CLI Integration Tests' +echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + +current_version=$(node -p "require('${scriptdir}/../../../package.json').version") + +# This allows injecting different versions, not just the current one. +# Useful when testing. +export VERSION_UNDER_TEST=${VERSION_UNDER_TEST:-${current_version}} + cd $scriptdir # Install these dependencies that the tests (written in Jest) need. @@ -16,4 +23,4 @@ if ! npx --no-install jest --version; then npm install --prefix . jest aws-sdk fi -npx jest --runInBand --verbose --setupFilesAfterEnv "$PWD/jest.setup.js" "$@" +npx jest --runInBand --verbose "$@" \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli.exclusions.js b/packages/aws-cdk/test/integ/cli.exclusions.js deleted file mode 100644 index 4204b62bd527c..0000000000000 --- a/packages/aws-cdk/test/integ/cli.exclusions.js +++ /dev/null @@ -1,70 +0,0 @@ -/** -List of exclusions when running backwards compatibility tests. -Add when you need to exclude a specific integration test from a specific version. - -This is an escape hatch for the rare cases where we need to introduce -a change that breaks existing integration tests. (e.g security) - -For example: - -{ - "test": "test-cdk-iam-diff.sh", - "version": "v1.30.0", - "justification": "iam policy generation has changed in version > 1.30.0 because..." -}, - -*/ -const exclusions = [ - { - "test": "test-cdk-deploy-nested-stack-with-parameters.sh", - "version": "v1.37.0", - "justification": "This test doesn't use a unique sns topic name for the topic in the nested stack and it collides with our regular integ suite" - }, - { - "test": "test-cdk-deploy-wildcard-with-outputs.sh", - "version": "v1.37.0", - "justification": "This test doesn't use a unique sns topic name and it collides with our regular integ suite" - }, - { - "test": "test-cdk-deploy-with-outputs.sh", - "version": "v1.37.0", - "justification": "This test doesn't use a unique sns topic name and it collides with our regular integ suite" - } -] - -function getExclusion(test, version) { - - const filtered = exclusions.filter(e => { - return e.test === test && e.version === version; - }); - - if (filtered.length === 0) { - return undefined; - } - - if (filtered.length === 1) { - return filtered[0]; - } - - throw new Error(`Multiple exclusions found for (${test, version}): ${filtered.length}`); - -} - -module.exports.shouldSkip = function (test, version) { - - const exclusion = getExclusion(test, version); - - return exclusion != undefined - -} - -module.exports.getJustification = function (test, version) { - - const exclusion = getExclusion(test, version); - - if (!exclusion) { - throw new Error(`Exclusion not found for (${test}, ${version})`); - } - - return exclusion.justification; -} diff --git a/packages/aws-cdk/test/integ/cli/README.md b/packages/aws-cdk/test/integ/cli/README.md index 44d531623e112..9e0e8d9b5e5f1 100644 --- a/packages/aws-cdk/test/integ/cli/README.md +++ b/packages/aws-cdk/test/integ/cli/README.md @@ -20,9 +20,6 @@ Running against a failing dist build: ## Adding tests -Older tests were written in bash; new tests should be written in -TypeScript/Jest, that is much more comfortable to write in. - Even though tests are now written in TypeScript, this does not conceptually change their SUT! They are still testing the CLI via running it as a subprocess, they are NOT reaching directly into the CLI @@ -34,8 +31,8 @@ Compilation of the tests is done as part of the normal package build, at which point it is using the dependencies brought in by the containing `aws-cdk` package's `package.json`. -When run in a non-develompent repo (as done during integ tests or canary runs), -the required dependencies are brought in just-in-time via `test-jest.sh`. Any +When run in a non-development repo (as done during integ tests or canary runs), +the required dependencies are brought in just-in-time via `test.sh`. Any new dependencies added for the tests should be added there as well. But, better yet, don't add any dependencies at all. You shouldn't need to, these tests are simple. diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index efd6acde64145..a61bc4b798b32 100644 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -255,7 +255,7 @@ new MultiParameterStack(app, `${stackPrefix}-param-test-3`); new OutputsStack(app, `${stackPrefix}-outputs-test-1`); new AnotherOutputsStack(app, `${stackPrefix}-outputs-test-2`); // Not included in wildcard -new IamStack(app, `${stackPrefix}-iam-test`); +new IamStack(app, `${stackPrefix}-iam-test`, { env: defaultEnv }); const providing = new ProvidingStack(app, `${stackPrefix}-order-providing`); new ConsumingStack(app, `${stackPrefix}-order-consuming`, { providingStack: providing }); diff --git a/packages/aws-cdk/test/integ/cli/aws-helpers.ts b/packages/aws-cdk/test/integ/cli/aws-helpers.ts index 92cb7a77131a3..fb54db4f60bcd 100644 --- a/packages/aws-cdk/test/integ/cli/aws-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/aws-helpers.ts @@ -20,6 +20,7 @@ export let testEnv = async (): Promise => { export const cloudFormation = makeAwsCaller(AWS.CloudFormation); export const s3 = makeAwsCaller(AWS.S3); +export const ecr = makeAwsCaller(AWS.ECR); export const sns = makeAwsCaller(AWS.SNS); export const iam = makeAwsCaller(AWS.IAM); export const lambda = makeAwsCaller(AWS.Lambda); @@ -188,6 +189,10 @@ export async function emptyBucket(bucketName: string) { }); } +export async function deleteImageRepository(repositoryName: string) { + await ecr('deleteRepository', { repositoryName, force: true }); +} + export async function deleteBucket(bucketName: string) { try { await emptyBucket(bucketName); diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index 3c674470abe4c..93f9a0974aa2a 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -1,5 +1,6 @@ import { cloudFormation } from './aws-helpers'; import { cdk, cdkDeploy, cleanup, fullStackName, prepareAppFixture, rememberToDeleteBucket } from './cdk-helpers'; +import { integTest } from './test-helpers'; jest.setTimeout(600_000); @@ -17,7 +18,7 @@ afterEach(async () => { await cleanup(); }); -test('can bootstrap without execution', async () => { +integTest('can bootstrap without execution', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); await cdk(['bootstrap', @@ -31,7 +32,7 @@ test('can bootstrap without execution', async () => { expect(resp.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); }); -test('upgrade legacy bootstrap stack to new bootstrap stack while in use', async () => { +integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); const legacyBootstrapBucketName = `aws-cdk-bootstrap-integ-test-legacy-bckt-${randomString()}`; @@ -69,7 +70,7 @@ test('upgrade legacy bootstrap stack to new bootstrap stack while in use', async }); }); -test('deploy new style synthesis to new style bootstrap', async () => { +integTest('deploy new style synthesis to new style bootstrap', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); await cdk(['bootstrap', @@ -92,7 +93,30 @@ test('deploy new style synthesis to new style bootstrap', async () => { }); }); -test('deploy old style synthesis to new style bootstrap', async () => { +integTest('deploy new style synthesis to new style bootstrap (with docker image)', async () => { + const bootstrapStackName = fullStackName('bootstrap-stack'); + + await cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--qualifier', QUALIFIER, + '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess', + ], { + modEnv: { + CDK_NEW_BOOTSTRAP: '1', + }, + }); + + // Deploy stack that uses file assets + await cdkDeploy('docker', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${QUALIFIER}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +}); + +integTest('deploy old style synthesis to new style bootstrap', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); await cdk(['bootstrap', @@ -113,7 +137,7 @@ test('deploy old style synthesis to new style bootstrap', async () => { }); }); -test('deploying new style synthesis to old style bootstrap fails', async () => { +integTest('deploying new style synthesis to old style bootstrap fails', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); await cdk(['bootstrap', '--toolkit-stack-name', bootstrapStackName]); @@ -128,7 +152,7 @@ test('deploying new style synthesis to old style bootstrap fails', async () => { })).rejects.toThrow('exited with error'); }); -test('can create multiple legacy bootstrap stacks', async () => { +integTest('can create multiple legacy bootstrap stacks', async () => { const bootstrapStackName1 = fullStackName('bootstrap-stack-1'); const bootstrapStackName2 = fullStackName('bootstrap-stack-2'); diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index d43e7bfe23000..410b8d71d9e71 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -1,7 +1,7 @@ import * as child_process from 'child_process'; import * as os from 'os'; import * as path from 'path'; -import { cloudFormation, deleteBucket, deleteStacks, emptyBucket, outputFromStack, testEnv } from './aws-helpers'; +import { cloudFormation, deleteBucket, deleteImageRepository, deleteStacks, emptyBucket, outputFromStack, testEnv } from './aws-helpers'; export const INTEG_TEST_DIR = path.join(os.tmpdir(), 'cdk-integ-test2'); @@ -155,6 +155,10 @@ export async function cleanup(): Promise { const bucketNames = stacksToDelete.map(stack => outputFromStack('BucketName', stack)).filter(defined); await Promise.all(bucketNames.map(emptyBucket)); + // Bootstrap stacks have ECR repositories with images which should be deleted + const imageRepositoryNames = stacksToDelete.map(stack => outputFromStack('ImageRepositoryName', stack)).filter(defined); + await Promise.all(imageRepositoryNames.map(deleteImageRepository)); + await deleteStacks(...stacksToDelete.map(s => s.StackName)); // We might have leaked some buckets by upgrading the bootstrap stack. Be @@ -209,7 +213,7 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom 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}`)); + reject(new Error(`'${command.join(' ')}' exited with error code ${code}: ${Buffer.concat(stderr).toString('utf-8').trim()}`)); } }); }); diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index 47a7f9fe46ce3..4c9896236155a 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import { cloudFormation, iam, lambda, retry, sleep, sns, sts, testEnv } from './aws-helpers'; import { cdk, cdkDeploy, cdkDestroy, cleanup, cloneDirectory, fullStackName, INTEG_TEST_DIR, log, prepareAppFixture, shell, STACK_NAME_PREFIX } from './cdk-helpers'; +import { integTest } from './test-helpers'; jest.setTimeout(600 * 1000); @@ -19,7 +20,7 @@ afterEach(async () => { await cleanup(); }); -test('VPC Lookup', async () => { +integTest('VPC Lookup', async () => { log('Making sure we are clean before starting.'); await cdkDestroy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' }}); @@ -31,14 +32,14 @@ test('VPC Lookup', async () => { await cdkDeploy('import-vpc', { modEnv: { ENABLE_VPC_TESTING: 'IMPORT' }}); }); -test('Two ways of shoing the version', async () => { +integTest('Two ways of shoing the version', async () => { const version1 = await cdk(['version']); const version2 = await cdk(['--version']); expect(version1).toEqual(version2); }); -test('Termination protection', async () => { +integTest('Termination protection', async () => { const stackName = 'termination-protection'; await cdkDeploy(stackName); @@ -50,7 +51,7 @@ test('Termination protection', async () => { await cdkDestroy(stackName); }); -test('cdk synth', async () => { +integTest('cdk synth', async () => { await expect(cdk(['synth', fullStackName('test-1')])).resolves.toEqual( `Resources: topic69831491: @@ -70,7 +71,7 @@ test('cdk synth', async () => { aws:cdk:path: ${STACK_NAME_PREFIX}-test-2/topic2/Resource`); }); -test('ssm parameter provider error', async () => { +integTest('ssm parameter provider error', async () => { await expect(cdk(['synth', fullStackName('missing-ssm-parameter'), '-c', 'test:ssm-parameter-name=/does/not/exist', @@ -79,7 +80,7 @@ test('ssm parameter provider error', async () => { })).resolves.toContain('SSM parameter not available in account'); }); -test('automatic ordering', async () => { +integTest('automatic ordering', async () => { // Deploy the consuming stack which will include the producing stack await cdkDeploy('order-consuming'); @@ -87,7 +88,7 @@ test('automatic ordering', async () => { await cdkDestroy('order-providing'); }); -test('context setting', async () => { +integTest('context setting', async () => { await fs.writeFile(path.join(INTEG_TEST_DIR, 'cdk.context.json'), JSON.stringify({ contextkey: 'this is the context value', })); @@ -106,7 +107,7 @@ test('context setting', async () => { } }); -test('deploy', async () => { +integTest('deploy', async () => { const stackArn = await cdkDeploy('test-2', { captureStderr: false }); // verify the number of resources in the stack @@ -116,14 +117,14 @@ test('deploy', async () => { expect(response.StackResources?.length).toEqual(2); }); -test('deploy all', async () => { +integTest('deploy all', async () => { const arns = await 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('nested stack with parameters', async () => { +integTest('nested stack with parameters', async () => { // 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 cdkDeploy('with-nested-stack-using-parameters', { @@ -141,7 +142,7 @@ test('nested stack with parameters', async () => { expect(response.StackResources?.length).toEqual(1); }); -test('deploy without execute', async () => { +integTest('deploy without execute', async () => { const stackArn = await cdkDeploy('test-2', { options: ['--no-execute'], captureStderr: false, @@ -156,7 +157,7 @@ test('deploy without execute', async () => { expect(response.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); }); -test('security related changes without a CLI are expected to fail', async () => { +integTest('security related changes without a CLI are expected to fail', async () => { // 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 @@ -172,7 +173,7 @@ test('security related changes without a CLI are expected to fail', async () => })).rejects.toThrow('does not exist'); }); -test('deploy wildcard with outputs', async () => { +integTest('deploy wildcard with outputs', async () => { const outputsFile = path.join(INTEG_TEST_DIR, 'outputs', 'outputs.json'); await fs.mkdir(path.dirname(outputsFile), { recursive: true }); @@ -191,7 +192,7 @@ test('deploy wildcard with outputs', async () => { }); }); -test('deploy with parameters', async () => { +integTest('deploy with parameters', async () => { const stackArn = await cdkDeploy('param-test-1', { options: [ '--parameters', `TopicNameParam=${STACK_NAME_PREFIX}bazinga`, @@ -211,7 +212,7 @@ test('deploy with parameters', async () => { ]); }); -test('deploy with wildcard and parameters', async () => { +integTest('deploy with wildcard and parameters', async () => { await cdkDeploy('param-test-*', { options: [ '--parameters', `${STACK_NAME_PREFIX}-param-test-1:TopicNameParam=${STACK_NAME_PREFIX}bazinga`, @@ -222,7 +223,7 @@ test('deploy with wildcard and parameters', async () => { }); }); -test('deploy with parameters multi', async () => { +integTest('deploy with parameters multi', async () => { const paramVal1 = `${STACK_NAME_PREFIX}bazinga`; const paramVal2 = `${STACK_NAME_PREFIX}=jagshemash`; @@ -250,7 +251,7 @@ test('deploy with parameters multi', async () => { ]); }); -test('deploy with notification ARN', async () => { +integTest('deploy with notification ARN', async () => { const topicName = `${STACK_NAME_PREFIX}-test-topic`; const response = await sns('createTopic', { Name: topicName }); @@ -272,7 +273,7 @@ test('deploy with notification ARN', async () => { } }); -test('deploy with role', async () => { +integTest('deploy with role', async () => { const roleName = `${STACK_NAME_PREFIX}-test-role`; await deleteRole(); @@ -350,7 +351,7 @@ test('deploy with role', async () => { } }); -test('cdk diff', async () => { +integTest('cdk diff', async () => { const diff1 = await cdk(['diff', fullStackName('test-1')]); expect(diff1).toContain('AWS::SNS::Topic'); @@ -362,11 +363,11 @@ test('cdk diff', async () => { .rejects.toThrow('exited with error'); }); -test('deploy stack with docker asset', async () => { +integTest('deploy stack with docker asset', async () => { await cdkDeploy('docker'); }); -test('deploy and test stack with lambda asset', async () => { +integTest('deploy and test stack with lambda asset', async () => { const stackArn = await cdkDeploy('lambda', { captureStderr: false }); const response = await cloudFormation('describeStacks', { @@ -384,7 +385,7 @@ test('deploy and test stack with lambda asset', async () => { expect(JSON.stringify(output.Payload)).toContain('dear asset'); }); -test('cdk ls', async () => { +integTest('cdk ls', async () => { const listing = await cdk(['ls'], { captureStderr: false }); const expectedStacks = [ @@ -414,7 +415,7 @@ test('cdk ls', async () => { } }); -test('deploy stack without resource', async () => { +integTest('deploy stack without resource', async () => { // Deploy the stack without resources await cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' }}); @@ -432,7 +433,7 @@ test('deploy stack without resource', async () => { .rejects.toThrow('conditional-resource does not exist'); }); -test('IAM diff', async () => { +integTest('IAM diff', async () => { const output = await cdk(['diff', fullStackName('iam-test')]); // Roughly check for a table like this: @@ -448,7 +449,7 @@ test('IAM diff', async () => { expect(output).toContain('ec2.${AWS::URLSuffix}'); }); -test('fast deploy', async () => { +integTest('fast deploy', async () => { // 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. @@ -479,12 +480,12 @@ test('fast deploy', async () => { } }); -test('failed deploy does not hang', async () => { +integTest('failed deploy does not hang', async () => { // this will hang if we introduce https://github.com/aws/aws-cdk/issues/6403 again. await expect(cdkDeploy('failed')).rejects.toThrow('exited with error'); }); -test('can still load old assemblies', async () => { +integTest('can still load old assemblies', async () => { const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); const testAssembliesDirectory = path.join(__dirname, 'cloud-assemblies'); @@ -519,7 +520,7 @@ test('can still load old assemblies', async () => { } }); -test('generating and loading assembly', async () => { +integTest('generating and loading assembly', async () => { const asmOutputDir = path.join(os.tmpdir(), 'cdk-integ-asm'); await shell(['rm', '-rf', asmOutputDir]); diff --git a/packages/aws-cdk/test/integ/cli/jest.setup.js b/packages/aws-cdk/test/integ/cli/jest.setup.js deleted file mode 100644 index 752a75d4f73d4..0000000000000 --- a/packages/aws-cdk/test/integ/cli/jest.setup.js +++ /dev/null @@ -1,8 +0,0 @@ -// Print a big banner before every test, much more readable output -jasmine.getEnv().addReporter({ - specStarted: currentTest => { - process.stdout.write('================================================================\n'); - process.stdout.write(`${currentTest.fullName}\n`); - process.stdout.write('================================================================\n'); - } -}); \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/skip-tests.txt b/packages/aws-cdk/test/integ/cli/skip-tests.txt new file mode 100644 index 0000000000000..bb43b8f55b68f --- /dev/null +++ b/packages/aws-cdk/test/integ/cli/skip-tests.txt @@ -0,0 +1,8 @@ +# This file is empty on purpose. Leave it here as documentation +# and an example. +# +# Copy this file to cli-regression-patches/vX.Y.Z/skip-tests.txt +# and edit it there if you want to exclude certain tests from running +# when performing a certain version's regression tests. +# +# Put a test name on a line by itself to skip it. \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/test-helpers.ts b/packages/aws-cdk/test/integ/cli/test-helpers.ts new file mode 100644 index 0000000000000..1aef74a6efd28 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli/test-helpers.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +const SKIP_TESTS = fs.readFileSync(path.join(__dirname, 'skip-tests.txt'), { encoding: 'utf-8' }).split('\n'); + +/** + * A wrapper for jest's 'test' which takes regression-disabled tests into account and prints a banner + */ +export function integTest(name: string, callback: () => A | Promise) { + const runner = shouldSkip(name) ? test.skip : test; + + runner(name, () => { + process.stdout.write('================================================================\n'); + process.stdout.write(`${name}\n`); + process.stdout.write('================================================================\n'); + + return callback(); + }); +} + +function shouldSkip(testName: string) { + return SKIP_TESTS.includes(testName); +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/test.sh b/packages/aws-cdk/test/integ/cli/test.sh index 75f98aefb9380..482956df450f4 100755 --- a/packages/aws-cdk/test/integ/cli/test.sh +++ b/packages/aws-cdk/test/integ/cli/test.sh @@ -10,42 +10,17 @@ current_version=$(node -p "require('${scriptdir}/../../../package.json').version # This allows injecting different versions, not just the current one. # Useful when testing. -VERSION_UNDER_TEST=${VERSION_UNDER_TEST:-${current_version}} +export VERSION_UNDER_TEST=${VERSION_UNDER_TEST:-${current_version}} -# check if a specific test should be skiped -# from execution in the current version. -function should_skip { - test=$1 - echo $(node -p "require('${scriptdir}/../cli.exclusions.js').shouldSkip('${test}', '${VERSION_UNDER_TEST}')") -} +cd $scriptdir -# get the justification for why a test is skipped. -# this will fail if there is no justification! -function get_skip_jusitification { - test=$1 - echo $(node -p "require('${scriptdir}/../cli.exclusions.js').getJustification('${test}', '${VERSION_UNDER_TEST}')") -} - -for test in $(cd ${scriptdir} && ls test-*.sh); do - echo "============================================================================================" - - # first check this if this test should be skipped. - # this can happen when running in regression mode - # when we introduce an intentional breaking change. - skip=$(should_skip ${test}) - - if [ ${skip} == "true" ]; then - - # make sure we have a justification, this will fail if not. - jusitification="$(get_skip_jusitification ${test})" - - # skip this specific test. - echo "${test} - skipped (${jusitification})" - continue - fi - - echo "${test}" - echo "============================================================================================" - /bin/bash ${scriptdir}/${test} -done +# Install these dependencies that the tests (written in Jest) need. +# Only if we're not running from the repo, because if we are the +# dependencies have already been installed by the containing 'aws-cdk' package's +# package.json. +if ! npx --no-install jest --version; then + echo 'Looks like we need to install jest first. Hold on.' >& 2 + npm install --prefix . jest aws-sdk +fi +npx jest --runInBand --verbose "$@" \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh index a9a68d19e6001..02219e64c73f4 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh @@ -70,15 +70,19 @@ download_repo ${VERSION_UNDER_TEST} # bad behvaior when using it as directory names. sanitized_version=$(sed 's/\//-/g' <<< "${VERSION_UNDER_TEST}") +# Test must be created in the same directory here because the script files liberally +# include files from '..' and they have to exist. integ_under_test=${integdir}/cli-backwards-tests-${sanitized_version} rm -rf ${integ_under_test} echo "Copying integration tests of version ${VERSION_UNDER_TEST} to ${integ_under_test} (dont worry, its gitignored)" cp -r ${temp_dir}/package/test/integ/cli ${integ_under_test} -echo "Hotpatching the test runner (can be removed after release 1.40.0)" >&2 -cp -r ${integdir}/cli/test-jest.sh ${integ_under_test} -cp -r ${integdir}/cli/jest.config.js ${integ_under_test} -cp -r ${integdir}/cli/jest.setup.js ${integ_under_test} +patch_dir="${integdir}/cli-regression-patches/${VERSION_UNDER_TEST}" +if [[ -d "$patch_dir" ]]; then + echo "Hotpatching the tests with files from $patch_dir" >&2 + cp -r "$patch_dir"/* ${integ_under_test} +fi echo "Running integration tests of version ${VERSION_UNDER_TEST} from ${integ_under_test}" -VERSION_UNDER_TEST=${VERSION_UNDER_TEST} ${integ_under_test}/test.sh +set -x +VERSION_UNDER_TEST=${VERSION_UNDER_TEST} ${integ_under_test}/test.sh "$@" diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh index fc3e4e3b9a859..6d0133ca06108 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh @@ -5,4 +5,4 @@ integdir=$(cd $(dirname $0) && pwd) # run the regular regression test but pass the env variable that will # eventually instruct our runners and wrappers to install the framework # from npmjs.org rather then using the local code. -USE_PUBLISHED_FRAMEWORK_VERSION=True ${integdir}/test-cli-regression-against-current-code.sh +USE_PUBLISHED_FRAMEWORK_VERSION=True ${integdir}/test-cli-regression-against-current-code.sh "$@" diff --git a/packages/cdk-assets/lib/private/shell.ts b/packages/cdk-assets/lib/private/shell.ts index fd145cf517704..1ae57dba1b062 100644 --- a/packages/cdk-assets/lib/private/shell.ts +++ b/packages/cdk-assets/lib/private/shell.ts @@ -30,6 +30,7 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom } const stdout = new Array(); + const stderr = new Array(); // Both write to stdout and collect child.stdout.on('data', chunk => { @@ -43,6 +44,8 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom if (!options.quiet) { process.stderr.write(chunk); } + + stderr.push(chunk); }); child.once('error', reject); @@ -51,7 +54,8 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom if (code === 0) { resolve(Buffer.concat(stdout).toString('utf-8')); } else { - reject(new ProcessFailed(code, `${renderCommandLine(command)} exited with error code ${code}`)); + const out = Buffer.concat(stderr).toString('utf-8').trim(); + reject(new ProcessFailed(code, `${renderCommandLine(command)} exited with error code ${code}: ${out}`)); } }); });