feat(cli): warn of non-existent stacks in cdk destroy#984
Conversation
Head branch was pushed to by a user without write access
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #984 +/- ##
=======================================
Coverage 89.59% 89.59%
=======================================
Files 77 77
Lines 11758 11758
Branches 1651 1651
=======================================
Hits 10534 10534
Misses 1195 1195
Partials 29 29
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The `refactor` command only operates on the stacks that are relevant for the target CDK application. However, it gets all the templates of the deployed stacks before filtering them, which is wasteful. Invert the order, so that the filtering happens before getting the templates. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
|
@go-to-k I am looking to get this in soon (hence the rebase), but first want to change |
Align cdk destroy with the confirmation-prompt contract from aws#1667: declining now throws AbortError('DestroyAborted', 'Deletion cancelled') and exits non-zero instead of emitting CDK_TOOLKIT_E7010 and exiting 0.
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…stroy # Conflicts: # packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts # packages/aws-cdk/lib/cli/cdk-toolkit.ts # packages/aws-cdk/test/_helpers/io-recorder.ts # packages/aws-cdk/test/commands/__io_snapshots__/destroy/destroy_failure_emits_a_failure_message_and_rethrows_when_destroyStack_fails.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_false_confirmation_prompt_aborts_with_an_AbortError_and_destroys_nothing_when_the_user_declines.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_false_confirmation_prompt_asks_for_confirmation_and_proceeds_when_the_user_confirms.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_false_confirmation_prompt_rethrows_an_unexpected_error_from_the_confirmation_prompt.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_true_no_confirmation_prompt_destroys_a_single_nested_stack_fromDeploy_makes_it_say_deployed.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_true_no_confirmation_prompt_destroys_all_top-level_stacks_with_concurrency.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_true_no_confirmation_prompt_forwards_the_roleArn_to_destroyStack.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/destroy/force_true_no_confirmation_prompt_respects_dependency_order_with_concurrency.ndjson # packages/aws-cdk/test/commands/__io_snapshots__/list/cdk_list_prints_machine-readable_JSON_without_the_synthesis-time_line.ndjson # packages/aws-cdk/test/commands/destroy.test.ts
… match Re-applies the destroy warning feature on top of main's toolkit-lib destroy migration (aws#1686): - W7011: emit a warning and exit without prompting when no stacks match - W7010: warn about each non-existent name (suggesting a close match when one exists, including stacks nested in a stage) while still destroying matches - adds suggestStacks() to Toolkit and the supporting message codes/registry - restores toolkit-lib unit tests, stage fixtures, and the destroy integ tests The toolkit-lib migration plumbing from this branch is dropped in favor of main's implementation; only the warning behavior is preserved.
The destroy confirmation prompt colored the stack names red (inherited from main's _destroy). Use blue to match the color the CLI used before destroy was routed through toolkit-lib, and assert it so the merge does not silently re-introduce red. Ref: aws#984 (comment)
|
@mrgrain Could you please take a look at my changes? |
| private async suggestStacks( | ||
| ioHelper: IoHelper, | ||
| assembly: StackAssembly, | ||
| selector: StackSelector, | ||
| selected: StackCollection, | ||
| ): Promise<void> { | ||
| const patterns = selector.patterns ?? []; | ||
| if (patterns.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| const allStacks = await assembly.selectStacksV2(ALL_STACKS); | ||
|
|
||
| for (const pattern of patterns) { | ||
| const matched = selected.stackArtifacts.some((stack) => picomatch.isMatch(stack.hierarchicalId, pattern)); | ||
| if (matched) { | ||
| continue; | ||
| } | ||
|
|
||
| const closeMatches = allStacks.stackArtifacts | ||
| .filter((stack) => picomatch.isMatch(stack.hierarchicalId.toLowerCase(), pattern.toLowerCase())) | ||
| .map((stack) => stack.hierarchicalId); |
There was a problem hiding this comment.
This function to match candidates (basically to this line) should live on StackAssembly class. It should not log any output but return a string[].
| const matched = selected.stackArtifacts.some((stack) => picomatch.isMatch(stack.hierarchicalId, pattern)); | ||
| if (matched) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Can you explain what purpose this is serving? I just don't understand it. We are excluding any of the stacks that already match the pattern themselves? But why?
There was a problem hiding this comment.
This handles the case where several names are given and only some exist. E.g. cdk destroy StackA Typo: StackA is destroyed, Typo matches nothing. The matched check skips StackA and warns only about Typo ("Did you mean …?").
With "only warn when 0 stacks matched", a typo mixed in with valid names would slip through unnoticed, that's what I wanted to catch. That said, if you'd prefer a single message (only when nothing matched at all), I'm happy to fold it into one payload-carrying message. Which do you prefer?
There was a problem hiding this comment.
Okay, thank you. I understand now what this is trying to achieve. Let me think about this for a bit. Some of my comments are now obviously contingent on this decision here.
There was a problem hiding this comment.
Holding off until the direction above is settled, then I'll adjust accordingly.
| const ioHelper = asIoHelper(this.ioHost, action); | ||
| const stacks = await assembly.selectStacksV2(selectStacks); | ||
|
|
||
| await this.suggestStacks(ioHelper, assembly, selectStacks, stacks); |
There was a problem hiding this comment.
I think we should only call this if the original selector returned 0 results.
| }; | ||
|
|
||
| if (stacks.stackCount === 0) { | ||
| await ioHelper.notify(IO.CDK_TOOLKIT_W7011.msg( |
There was a problem hiding this comment.
I think both messages can be folded into a single message that informs the user that no matching stacks were found. And if we have identified possible close matches ("candidates"), then we suggest them ("Did you mean...").
The Toolkit message should include a payload of the unmatched pattern and the identified matches as string[] | undefined.
| CDK_TOOLKIT_W7010: make.warn({ | ||
| code: 'CDK_TOOLKIT_W7010', | ||
| description: 'A provided stack name does not match any stack', | ||
| }), | ||
| CDK_TOOLKIT_W7011: make.warn({ | ||
| code: 'CDK_TOOLKIT_W7011', | ||
| description: 'No stacks match the provided names, nothing to destroy', | ||
| }), |
There was a problem hiding this comment.
Like said elsewhere, I think a single message will be enough but it should carry a payload of the unmatched selector and idenfified matches.
mrgrain
left a comment
There was a problem hiding this comment.
Thanks for your continued work! FYI I want to let the changes to destroy bake a little bit longer to see if there's any issue from that.
But this PR is getting close to be ready now. Made some final comments on code organisation and messaging. We will also expect new tests in packages/aws-cdk/test/commands/destroy.test.ts and associated snapshots to record the exact output.



Fixes aws/aws-cdk#32836, aws/aws-cdk#32545, aws/aws-cdk#27179, aws/aws-cdk#22240
Reason for this change
This PR implements the feature that warns users when non-existent stacks are specified in
cdk destroy.Are you sure you want to delete:if there is no matching stack.cdk destroywill not fail, it will just print a warning.For examples (that have
Stacka,StackA,StackX):Difference from previous PR
The previous PR was reverted in aws/aws-cdk#32839 due to a regression with only nested stage stacks. So this version addresses that regression with comprehensive tests.
Description of changes
The original implementation added warnings for non-existent stacks in
cdk destroy, but it failed when applications had only nested stage stacks (no top-level stacks). This happened because the code usedallTopLevel: true, which only searched for stacks directly under the App, ignoring stacks within nested Stages.Fixed the regression by changing
suggestStacksmethod to useDefaultSelection.AllStacksinstead ofallTopLevel: true, ensuring the warning feature works for all stack configurations (top-level only, nested only, or both). Added regression tests to verify the fix for the nested stage scenario.private async suggestStacks(props: { selector: StackSelector; stacks: StackCollection; exclusively?: boolean; }) { const assembly = await this.assembly(); const selectorWithoutPatterns: StackSelector = { - ...props.selector, - allTopLevel: true, patterns: [], }; const stacksWithoutPatterns = await assembly.selectStacks(selectorWithoutPatterns, { extend: props.exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Downstream, - defaultBehavior: DefaultSelection.OnlySingle, + defaultBehavior: DefaultSelection.AllStacks, });Additional Information
When running
cdk destroy --allorcdk deploy --allagainst a configuration with no top-level stacks (nested stages only), the following error occurs.However, this behavior existed before this PR and is unrelated to the changes made here. Since it is outside the scope of this PR, no fix has been implemented for this behavior.
FYI: I have submitted an issue and a PR about this behavior.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license