Skip to content

Commit d7a7cb6

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

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
@@ -6,7 +6,7 @@ import * as fs from 'fs-extra';
66
import { NonInteractiveIoHost } from './non-interactive-io-host';
77
import type { ToolkitServices } from './private';
88
import { assemblyFromSource } from './private';
9-
import type { DeployResult } from './types';
9+
import type { DeployResult, DestroyResult } from './types';
1010
import type { BootstrapEnvironments, BootstrapOptions, BootstrapResult, EnvironmentBootstrapResult } from '../actions/bootstrap';
1111
import { BootstrapSource } from '../actions/bootstrap';
1212
import { AssetBuildTime, type DeployOptions } from '../actions/deploy';
@@ -854,7 +854,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
854854
*
855855
* Destroys the selected Stacks.
856856
*/
857-
public async destroy(cx: ICloudAssemblySource, options: DestroyOptions): Promise<void> {
857+
public async destroy(cx: ICloudAssemblySource, options: DestroyOptions): Promise<DestroyResult> {
858858
const ioHelper = asIoHelper(this.ioHost, 'destroy');
859859
const assembly = await assemblyFromSource(ioHelper, cx);
860860
return this._destroy(assembly, 'destroy', options);
@@ -863,18 +863,23 @@ export class Toolkit extends CloudAssemblySourceBuilder {
863863
/**
864864
* Helper to allow destroy being called as part of the deploy action.
865865
*/
866-
private async _destroy(assembly: StackAssembly, action: 'deploy' | 'destroy', options: DestroyOptions): Promise<void> {
866+
private async _destroy(assembly: StackAssembly, action: 'deploy' | 'destroy', options: DestroyOptions): Promise<DestroyResult> {
867867
const ioHelper = asIoHelper(this.ioHost, action);
868868
const synthSpan = await ioHelper.span(SPAN.SYNTH_ASSEMBLY).begin({ stacks: options.stacks });
869869
// The stacks will have been ordered for deployment, so reverse them for deletion.
870870
const stacks = (await assembly.selectStacksV2(options.stacks)).reversed();
871871
await synthSpan.end();
872872

873+
const ret: DestroyResult = {
874+
stacks: [],
875+
};
876+
873877
const motivation = 'Destroying stacks is an irreversible action';
874878
const question = `Are you sure you want to delete: ${chalk.red(stacks.hierarchicalIds.join(', '))}`;
875879
const confirmed = await ioHelper.requestResponse(IO.CDK_TOOLKIT_I7010.req(question, { motivation }));
876880
if (!confirmed) {
877-
return ioHelper.notify(IO.CDK_TOOLKIT_E7010.msg('Aborted by user'));
881+
await ioHelper.notify(IO.CDK_TOOLKIT_E7010.msg('Aborted by user'));
882+
return ret;
878883
}
879884

880885
const destroySpan = await ioHelper.span(SPAN.DESTROY_ACTION).begin({
@@ -890,18 +895,30 @@ export class Toolkit extends CloudAssemblySourceBuilder {
890895
stack,
891896
});
892897
const deployments = await this.deploymentsForAction(action);
893-
await deployments.destroyStack({
898+
const result = await deployments.destroyStack({
894899
stack,
895900
deployName: stack.stackName,
896901
roleArn: options.roleArn,
897902
});
903+
904+
ret.stacks.push({
905+
environment: {
906+
account: stack.environment.account,
907+
region: stack.environment.region,
908+
},
909+
stackName: stack.stackName,
910+
stackArn: result.stackArn,
911+
});
912+
898913
await ioHelper.notify(IO.CDK_TOOLKIT_I7900.msg(chalk.green(`\n ✅ ${chalk.blue(stack.displayName)}: ${action}ed`), stack));
899914
await singleDestroySpan.end();
900915
} catch (e: any) {
901916
await ioHelper.notify(IO.CDK_TOOLKIT_E7900.msg(`\n ❌ ${chalk.blue(stack.displayName)}: ${action} failed ${e}`, { error: e }));
902917
throw e;
903918
}
904919
}
920+
921+
return ret;
905922
} finally {
906923
await destroySpan.end();
907924
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,42 @@ export interface Environment {
128128
*/
129129
readonly region: string;
130130
}
131+
132+
/**
133+
* Result interface for toolkit.destroy operation
134+
*/
135+
export interface DestroyResult {
136+
/**
137+
* List of stacks destroyed by this operation
138+
*/
139+
readonly stacks: DestroyedStack[];
140+
}
141+
142+
/**
143+
* A stack targeted by a destroy operation
144+
*/
145+
export interface DestroyedStack {
146+
/**
147+
* The name of the stack
148+
*
149+
* A stack name is unique inside its environment, but not unique globally.
150+
*/
151+
readonly stackName: string;
152+
153+
/**
154+
* The environment of the stack
155+
*
156+
* This environment is always concrete, because even though the CDK app's
157+
* stack may be region-agnostic, in order to be deployed it will have to have
158+
* been specialized.
159+
*/
160+
readonly environment: Environment;
161+
162+
/**
163+
* The ARN of the stack that was destroyed, if any.
164+
*
165+
* If the stack didn't exist to begin with, the operation will succeed but
166+
* this value will be undefined.
167+
*/
168+
readonly stackArn?: string;
169+
}

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)