diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index b56d13d4bf642..7f8e3e9a898c6 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -1184,12 +1184,11 @@ export class Stack extends Construct implements ITaggable { * Indicates whether the stack requires bundling or not */ public get bundlingRequired() { - const bundlingStacks: string[] = this.node.tryGetContext(cxapi.BUNDLING_STACKS) ?? ['*']; + const bundlingStacks: string[] = this.node.tryGetContext(cxapi.BUNDLING_STACKS) ?? ['**']; - // bundlingStacks is of the form `Stage/Stack`, convert it to `Stage-Stack` before comparing to stack name return bundlingStacks.some(pattern => minimatch( - this.stackName, - pattern.replace('/', '-'), + this.node.path, // use the same value for pattern matching as the aws-cdk CLI (displayName / hierarchicalId) + pattern, )); } } diff --git a/packages/@aws-cdk/core/test/staging.test.ts b/packages/@aws-cdk/core/test/staging.test.ts index 46f20f4ad03f9..16e14f492adaf 100644 --- a/packages/@aws-cdk/core/test/staging.test.ts +++ b/packages/@aws-cdk/core/test/staging.test.ts @@ -930,6 +930,155 @@ describe('staging', () => { expect(dockerStubInput).not.toMatch(DockerStubCommand.MULTIPLE_FILES); }); + test('correctly skips bundling with stack under stage and custom stack name', () => { + // GIVEN + const app = new App(); + + const stage = new Stage(app, 'Stage'); + stage.node.setContext(cxapi.BUNDLING_STACKS, ['Stage/Stack1']); + + const stack1 = new Stack(stage, 'Stack1', { stackName: 'unrelated-stack1-name' }); + const stack2 = new Stack(stage, 'Stack2', { stackName: 'unrelated-stack2-name' }); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + new AssetStaging(stack1, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + + new AssetStaging(stack2, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.MULTIPLE_FILES], + }, + }); + + // THEN + const dockerStubInput = readDockerStubInputConcat(); + // Docker ran for the asset in Stack1 + expect(dockerStubInput).toMatch(DockerStubCommand.SUCCESS); + // Docker did not run for the asset in Stack2 + expect(dockerStubInput).not.toMatch(DockerStubCommand.MULTIPLE_FILES); + }); + + test('correctly bundles with stack under stage and the default stack pattern', () => { + // GIVEN + const app = new App(); + + const stage = new Stage(app, 'Stage'); + + const stack1 = new Stack(stage, 'Stack1'); + const stack2 = new Stack(stage, 'Stack2'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + new AssetStaging(stack1, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + + new AssetStaging(stack2, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.MULTIPLE_FILES], + }, + }); + + // THEN + const dockerStubInput = readDockerStubInputConcat(); + // Docker ran for the asset in Stack1 + expect(dockerStubInput).toMatch(DockerStubCommand.SUCCESS); + // Docker ran for the asset in Stack2 + expect(dockerStubInput).toMatch(DockerStubCommand.MULTIPLE_FILES); + }); + + test('correctly bundles with stack under stage and partial globstar wildcard', () => { + // GIVEN + const app = new App(); + + const stage = new Stage(app, 'Stage'); + stage.node.setContext(cxapi.BUNDLING_STACKS, ['**/Stack1']); // a single wildcard prefix ('*Stack1') won't match + + const stack1 = new Stack(stage, 'Stack1'); + const stack2 = new Stack(stage, 'Stack2'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + new AssetStaging(stack1, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + + new AssetStaging(stack2, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.MULTIPLE_FILES], + }, + }); + + // THEN + const dockerStubInput = readDockerStubInputConcat(); + // Docker ran for the asset in Stack1 + expect(dockerStubInput).toMatch(DockerStubCommand.SUCCESS); + // Docker did not run for the asset in Stack2 + expect(dockerStubInput).not.toMatch(DockerStubCommand.MULTIPLE_FILES); + }); + + test('correctly bundles selected stacks nested in Stack/Stage/Stack', () => { + // GIVEN + const app = new App(); + + const topStack = new Stack(app, 'TopStack'); + topStack.node.setContext(cxapi.BUNDLING_STACKS, ['TopStack/MiddleStage/BottomStack']); + + const middleStage = new Stage(topStack, 'MiddleStage'); + const bottomStack = new Stack(middleStage, 'BottomStack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + new AssetStaging(bottomStack, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + new AssetStaging(topStack, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.OUTPUT, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.MULTIPLE_FILES], + }, + }); + + const dockerStubInput = readDockerStubInputConcat(); + // Docker ran for the asset in BottomStack + expect(dockerStubInput).toMatch(DockerStubCommand.SUCCESS); + // Docker did not run for the asset in TopStack + expect(dockerStubInput).not.toMatch(DockerStubCommand.MULTIPLE_FILES); + }); + test('bundling still occurs with partial wildcard', () => { // GIVEN const app = new App(); @@ -954,7 +1103,7 @@ describe('staging', () => { expect(asset.assetHash).toEqual('33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); // hash of MyStack/Asset }); - test('bundling still occurs with full wildcard', () => { + test('bundling still occurs with a single wildcard', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'MyStack'); diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 57356153cf7b2..8ee36557dd51c 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -288,7 +288,7 @@ are written to the same output file where each stack artifact ID is a key in the ```console -$ cdk deploy '*' --outputs-file "/Users/code/myproject/outputs.json" +$ cdk deploy '**' --outputs-file "/Users/code/myproject/outputs.json" ``` Example `outputs.json` after deployment of multiple stacks diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 01259f7a11763..d4bb217135d59 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -44,7 +44,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom context[cxapi.DISABLE_ASSET_STAGING_CONTEXT] = true; } - const bundlingStacks = config.settings.get(['bundlingStacks']) ?? ['*']; + const bundlingStacks = config.settings.get(['bundlingStacks']) ?? ['**']; context[cxapi.BUNDLING_STACKS] = bundlingStacks; debug('context:', context); diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index adb467e49b798..b94f563eb8365 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -256,8 +256,8 @@ export class Settings { // If we deploy, diff, synth or watch a list of stacks exclusively we skip // bundling for all other stacks. bundlingStacks = argv.exclusively - ? argv.STACKS ?? ['*'] - : ['*']; + ? argv.STACKS ?? ['**'] + : ['**']; } else { // Skip bundling for all stacks bundlingStacks = []; } diff --git a/packages/aws-cdk/test/settings.test.ts b/packages/aws-cdk/test/settings.test.ts index 8c2c894ae4634..3e0c6e49586ac 100644 --- a/packages/aws-cdk/test/settings.test.ts +++ b/packages/aws-cdk/test/settings.test.ts @@ -90,24 +90,24 @@ test('bundling stacks defaults to an empty list', () => { expect(settings.get(['bundlingStacks'])).toEqual([]); }); -test('bundling stacks defaults to * for deploy', () => { +test('bundling stacks defaults to ** for deploy', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ _: [Command.DEPLOY], }); // THEN - expect(settings.get(['bundlingStacks'])).toEqual(['*']); + expect(settings.get(['bundlingStacks'])).toEqual(['**']); }); -test('bundling stacks defaults to * for watch', () => { +test('bundling stacks defaults to ** for watch', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ _: [Command.WATCH], }); // THEN - expect(settings.get(['bundlingStacks'])).toEqual(['*']); + expect(settings.get(['bundlingStacks'])).toEqual(['**']); }); test('bundling stacks with deploy exclusively', () => { @@ -154,4 +154,4 @@ test('providing a build arg', () => { // THEN expect(settings.get(['build'])).toEqual('mvn package'); -}); \ No newline at end of file +});