Skip to content

Commit 74303d3

Browse files
committed
feat(toolkit): add a return type for toolkit.destroy()
`toolkit.destroy()` now returns information about the stacks it deployed.
1 parent aecffa6 commit 74303d3

File tree

5 files changed

+77
-9
lines changed

5 files changed

+77
-9
lines changed

packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as uuid from 'uuid';
77
import { NonInteractiveIoHost } from './non-interactive-io-host';
88
import type { ToolkitServices } from './private';
99
import { assemblyFromSource } from './private';
10-
import type { DeployResult } from './types';
10+
import type { DeployResult, DestroyResult } from './types';
1111
import type { BootstrapEnvironments, BootstrapOptions, BootstrapResult, EnvironmentBootstrapResult } from '../actions/bootstrap';
1212
import { BootstrapSource } from '../actions/bootstrap';
1313
import { AssetBuildTime, type DeployOptions } from '../actions/deploy';
@@ -923,7 +923,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
923923
*
924924
* Destroys the selected Stacks.
925925
*/
926-
public async destroy(cx: ICloudAssemblySource, options: DestroyOptions): Promise<void> {
926+
public async destroy(cx: ICloudAssemblySource, options: DestroyOptions): Promise<DestroyResult> {
927927
const ioHelper = asIoHelper(this.ioHost, 'destroy');
928928
const assembly = await assemblyFromSource(ioHelper, cx);
929929
return this._destroy(assembly, 'destroy', options);
@@ -932,18 +932,23 @@ export class Toolkit extends CloudAssemblySourceBuilder {
932932
/**
933933
* Helper to allow destroy being called as part of the deploy action.
934934
*/
935-
private async _destroy(assembly: StackAssembly, action: 'deploy' | 'destroy', options: DestroyOptions): Promise<void> {
935+
private async _destroy(assembly: StackAssembly, action: 'deploy' | 'destroy', options: DestroyOptions): Promise<DestroyResult> {
936936
const ioHelper = asIoHelper(this.ioHost, action);
937937
const synthSpan = await ioHelper.span(SPAN.SYNTH_ASSEMBLY).begin({ stacks: options.stacks });
938938
// The stacks will have been ordered for deployment, so reverse them for deletion.
939939
const stacks = (await assembly.selectStacksV2(options.stacks)).reversed();
940940
await synthSpan.end();
941941

942+
const ret: DestroyResult = {
943+
stacks: [],
944+
};
945+
942946
const motivation = 'Destroying stacks is an irreversible action';
943947
const question = `Are you sure you want to delete: ${chalk.red(stacks.hierarchicalIds.join(', '))}`;
944948
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I7010.req(question, { motivation }));
945949
if (!confirmed) {
946-
return ioHelper.notify(IO.CDK_TOOLKIT_E7010.msg('Aborted by user'));
950+
await ioHelper.notify(IO.CDK_TOOLKIT_E7010.msg('Aborted by user'));
951+
return ret;
947952
}
948953

949954
const destroySpan = await ioHelper.span(SPAN.DESTROY_ACTION).begin({
@@ -959,18 +964,30 @@ export class Toolkit extends CloudAssemblySourceBuilder {
959964
stack,
960965
});
961966
const deployments = await this.deploymentsForAction(action);
962-
await deployments.destroyStack({
967+
const result = await deployments.destroyStack({
963968
stack,
964969
deployName: stack.stackName,
965970
roleArn: options.roleArn,
966971
});
972+
973+
ret.stacks.push({
974+
environment: {
975+
account: stack.environment.account,
976+
region: stack.environment.region,
977+
},
978+
stackName: stack.stackName,
979+
stackArn: result.stackArn,
980+
});
981+
967982
await ioHelper.notify(IO.CDK_TOOLKIT_I7900.msg(chalk.green(`\n ✅ ${chalk.blue(stack.displayName)}: ${action}ed`), stack));
968983
await singleDestroySpan.end();
969984
} catch (e: any) {
970985
await ioHelper.notify(IO.CDK_TOOLKIT_E7900.msg(`\n ❌ ${chalk.blue(stack.displayName)}: ${action} failed ${e}`, { error: e }));
971986
throw e;
972987
}
973988
}
989+
990+
return ret;
974991
} finally {
975992
await destroySpan.end();
976993
}

packages/@aws-cdk/toolkit-lib/lib/toolkit/types.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,42 @@ export interface Environment {
6363
*/
6464
readonly region: string;
6565
}
66+
67+
/**
68+
* Result interface for toolkit.destroy operation
69+
*/
70+
export interface DestroyResult {
71+
/**
72+
* List of stacks destroyed by this operation
73+
*/
74+
readonly stacks: DestroyedStack[];
75+
}
76+
77+
/**
78+
* A stack targeted by a destroy operation
79+
*/
80+
export interface DestroyedStack {
81+
/**
82+
* The name of the stack
83+
*
84+
* A stack name is unique inside its environment, but not unique globally.
85+
*/
86+
readonly stackName: string;
87+
88+
/**
89+
* The environment of the stack
90+
*
91+
* This environment is always concrete, because even though the CDK app's
92+
* stack may be region-agnostic, in order to be deployed it will have to have
93+
* been specialized.
94+
*/
95+
readonly environment: Environment;
96+
97+
/**
98+
* The ARN of the stack that was destroyed, if any.
99+
*
100+
* If the stack didn't exist to begin with, the operation will succeed but
101+
* this value will be undefined.
102+
*/
103+
readonly stackArn?: string;
104+
}

packages/aws-cdk/lib/api/cloudformation/stack-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class CloudFormationStack {
8585
}
8686

8787
/**
88-
* The stack's ID
88+
* The stack's ID (which is the same as its ARN)
8989
*
9090
* Throws if the stack doesn't exist.
9191
*/

packages/aws-cdk/lib/api/deployments/deploy-stack.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,13 +663,23 @@ export interface DestroyStackOptions {
663663
deployName?: string;
664664
}
665665

666-
export async function destroyStack(options: DestroyStackOptions, ioHelper: IoHelper) {
666+
export interface DestroyStackResult {
667+
/**
668+
* The ARN of the stack that was destroyed, if any.
669+
*
670+
* If the stack didn't exist to begin with, the operation will succeed
671+
* but this value will be undefined.
672+
*/
673+
readonly stackArn?: string;
674+
}
675+
676+
export async function destroyStack(options: DestroyStackOptions, ioHelper: IoHelper): Promise<DestroyStackResult> {
667677
const deployName = options.deployName || options.stack.stackName;
668678
const cfn = options.sdk.cloudFormation();
669679

670680
const currentStack = await CloudFormationStack.lookup(cfn, deployName);
671681
if (!currentStack.exists) {
672-
return;
682+
return {};
673683
}
674684
const monitor = new StackActivityMonitor({
675685
cfn,
@@ -685,6 +695,8 @@ export async function destroyStack(options: DestroyStackOptions, ioHelper: IoHel
685695
if (destroyedStack && destroyedStack.stackStatus.name !== 'DELETE_COMPLETE') {
686696
throw new ToolkitError(`Failed to destroy ${deployName}: ${destroyedStack.stackStatus}`);
687697
}
698+
699+
return { stackArn: currentStack.stackId };
688700
} catch (e: any) {
689701
throw new ToolkitError(suffixWithErrors(formatErrorMessage(e), monitor.errors));
690702
} finally {

packages/aws-cdk/lib/api/deployments/deployments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ export class Deployments {
570570
);
571571
}
572572

573-
public async destroyStack(options: DestroyStackOptions): Promise<void> {
573+
public async destroyStack(options: DestroyStackOptions) {
574574
const env = await this.envs.accessStackForMutableStackOperations(options.stack);
575575
const executionRoleArn = await env.replacePlaceholders(options.roleArn ?? options.stack.cloudFormationExecutionRoleArn);
576576

0 commit comments

Comments
 (0)