diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js index 8735e727c..8e4ef8fd6 100755 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js @@ -993,6 +993,10 @@ switch (stackSet) { case 'stage-with-no-stacks': break; + case 'stage-only': + new SomeStage(app, `${stackPrefix}-stage`); + break; + default: throw new Error(`Unrecognized INTEG_STACK_SET: '${stackSet}'`); } diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only-with-all-option.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only-with-all-option.integtest.ts new file mode 100644 index 000000000..47568533a --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/destroy/cdk-destroy-stage-only-with-all-option.integtest.ts @@ -0,0 +1,27 @@ +import { DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; +import { integTest, withDefaultFixture } from '../../../lib'; + +integTest('cdk destroy can destroy stacks in stage-only configuration with --all option', withDefaultFixture(async (fixture) => { + const integStackSet = 'stage-only'; + + await fixture.cdkDeploy([], { + options: ['--all'], + modEnv: { + INTEG_STACK_SET: integStackSet, + }, + }); + + const stackName = `${fixture.fullStackName('stage')}-StackInStage`; + const stack = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName })); + expect(stack.Stacks?.length ?? 0).toEqual(1); + + await fixture.cdkDestroy([], { + options: ['--all'], + modEnv: { + INTEG_STACK_SET: integStackSet, + }, + }); + + await expect(fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: stackName }))) + .rejects.toThrow(/does not exist/); +})); diff --git a/packages/aws-cdk/lib/cxapp/cloud-assembly.ts b/packages/aws-cdk/lib/cxapp/cloud-assembly.ts index 824cd6aaf..2a3e0d983 100644 --- a/packages/aws-cdk/lib/cxapp/cloud-assembly.ts +++ b/packages/aws-cdk/lib/cxapp/cloud-assembly.ts @@ -103,7 +103,10 @@ export class CloudAssembly extends BaseStackAssembly { throw new ToolkitError('This app contains no stacks'); } - if (allTopLevel) { + const someCondition = false // TODO: replace + if (allTopLevel && someCondition) { + return new StackCollection(this, stacks); + } else if (allTopLevel) { return this.selectTopLevelStacks(stacks, topLevelStacks, options.extend); } else if (patterns.length > 0) { return this.selectMatchingStacks(stacks, patterns, options.extend); diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index 45c611918..459e4ca63 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -150,6 +150,26 @@ function defaultToolkitSetup() { }); } +// only stacks within stages (no top-level stacks) +async function stageOnlyToolkitSetup() { + const stageOnlyExecutable = await MockCloudExecutable.create({ + stacks: [], + nestedAssemblies: [{ + stacks: [MockStack.MOCK_STACK_C], + }], + }); + + return new CdkToolkit({ + ioHost, + cloudExecutable: stageOnlyExecutable, + configuration: stageOnlyExecutable.configuration, + sdkProvider: stageOnlyExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-C': { Baz: 'Zinga!' }, + }), + }); +} + const mockSdk = new MockSdk(); describe('bootstrap', () => { @@ -284,6 +304,17 @@ describe('deploy', () => { }); }); + test('deploy all stacks in stage-only configuration with --all option', async () => { + // GIVEN + const toolkit = await stageOnlyToolkitSetup(); + + // WHEN & THEN + await expect(toolkit.deploy({ + selector: { patterns: [], allTopLevel: true }, + deploymentMethod: { method: 'change-set' }, + })).resolves.not.toThrow(); + }); + test('uses display names to reference assets', async () => { // GIVEN cloudExecutable = await MockCloudExecutable.create({ @@ -1080,14 +1111,23 @@ describe('destroy', () => { test('destroy correct stack', async () => { const toolkit = defaultToolkitSetup(); - expect(() => { - return toolkit.destroy({ - selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, - exclusively: true, - force: true, - fromDeploy: true, - }); - }).resolves; + await expect(toolkit.destroy({ + selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); + }); + + test('destroy all stacks in stage-only configuration with --all option', async () => { + const toolkit = await stageOnlyToolkitSetup(); + + await expect(toolkit.destroy({ + selector: { patterns: [], allTopLevel: true }, + exclusively: true, + force: true, + fromDeploy: true, + })).resolves.not.toThrow(); }); }); diff --git a/packages/aws-cdk/test/cxapp/cloud-assembly.test.ts b/packages/aws-cdk/test/cxapp/cloud-assembly.test.ts index 60cb8da5f..5c3537dd1 100644 --- a/packages/aws-cdk/test/cxapp/cloud-assembly.test.ts +++ b/packages/aws-cdk/test/cxapp/cloud-assembly.test.ts @@ -17,6 +17,19 @@ test('select all top level stacks in the presence of nested assemblies', async ( expect(x.stackIds).toContain('withouterrors'); }); +test('select all stacks when only nested assemblies exist (Stage-only apps)', async () => { + // GIVEN + const cxasm = await testStageOnlyCloudAssembly(); + + // WHEN + const x = await cxasm.selectStacks({ allTopLevel: true, patterns: [] }, { defaultBehavior: DefaultSelection.AllStacks }); + + // THEN + expect(x.stackCount).toBe(2); + expect(x.stackIds).toContain('stack1'); + expect(x.stackIds).toContain('stack2'); +}); + test('select stacks by glob pattern', async () => { // GIVEN const cxasm = await testCloudAssembly(); @@ -337,3 +350,28 @@ async function testNestedCloudAssembly({ env }: { env?: string; versionReporting const asm = await cloudExec.synthesize(); return cliAssemblyWithForcedVersion(asm, '30.0.0'); } + +async function testStageOnlyCloudAssembly({ env }: { env?: string; versionReporting?: boolean } = {}) { + const cloudExec = await MockCloudExecutable.create({ + stacks: [], // No top-level stacks + nestedAssemblies: [{ + stacks: [{ + stackName: 'stack1', + displayName: 'stage1/stack1', + env, + template: { resource: 'resource1' }, + }], + }, + { + stacks: [{ + stackName: 'stack2', + displayName: 'stage2/stack2', + env, + template: { resource: 'resource2' }, + }], + }], + }); + + const asm = await cloudExec.synthesize(); + return cliAssemblyWithForcedVersion(asm, '30.0.0'); +}