diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts index ccdf07b166f33..683aea62966ab 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts @@ -1268,6 +1268,13 @@ integTest('hotswap deployment supports Fn::ImportValue intrinsic', withDefaultFi } })); +integTest('cdk destroy fails when the stacks do not exist', withDefaultFixture(async (fixture) => { + const nonExistingStackName1 = 'non-existing-stack-1'; + const nonExistingStackName2 = 'non-existing-stack-2'; + + await expect(fixture.cdkDestroy([nonExistingStackName1, nonExistingStackName2])).rejects.toThrow('exited with error'); +})); + async function listChildren(parent: string, pred: (x: string) => Promise) { const ret = new Array(); for (const child of await fs.readdir(parent, { encoding: 'utf-8' })) { diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 0ab13f0261412..8e37c6e974022 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -4,7 +4,9 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as chalk from 'chalk'; import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; +import { minimatch } from 'minimatch'; import * as promptly from 'promptly'; +import * as semver from 'semver'; import { DeploymentMethod } from './api'; import { SdkProvider } from './api/aws-auth'; import { Bootstrapper, BootstrapEnvironmentOptions } from './api/bootstrap'; @@ -26,6 +28,7 @@ import { validateSnsTopicArn } from './util/validate-notification-arn'; import { Concurrency, WorkGraph } from './util/work-graph'; import { WorkGraphBuilder } from './util/work-graph-builder'; import { AssetBuildNode, AssetPublishNode, StackNode } from './util/work-graph-types'; +import { versionNumber } from './version'; import { environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../lib/api/cxapp/environments'; export interface CdkToolkitProps { @@ -768,7 +771,12 @@ export class CdkToolkit { defaultBehavior: DefaultSelection.OnlySingle, }); - // No validation + const notExistPatterns = selector.patterns.filter(pattern => !stacks.stackArtifacts.find(stack => + minimatch(stack.hierarchicalId, pattern) || (stack.id === pattern && semver.major(versionNumber()) < 2), + )); + if (notExistPatterns.length > 0) { + throw new Error(`Cannot run cdk destroy on stack(s) ${selector.patterns.join(', ')}. ${notExistPatterns.join(', ')} not exist.`); + } return stacks; } diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index bb84e0fd6b4a7..d19ad84ab7693 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -602,6 +602,19 @@ describe('destroy', () => { }); }).resolves; }); + + test('fail on non-existent stack', async () => { + const toolkit = defaultToolkitSetup(); + + await expect(() => { + return toolkit.destroy({ + selector: { patterns: ['Test-Stack-A/Test-Stack-C', 'Test-Stack-X', 'Test-Stack-Y'] }, + exclusively: true, + force: true, + fromDeploy: true, + }); + }).rejects.toThrowError('Cannot run cdk destroy on stack(s) Test-Stack-A/Test-Stack-C, Test-Stack-X, Test-Stack-Y. Test-Stack-X, Test-Stack-Y not exist.'); + }); }); describe('watch', () => {