Skip to content

Commit 83a118e

Browse files
authored
fix(cloudformation): refresh stacks after changeset is deleted (aws#8351)
## Problem Stacks remained in panel as REVIEW_IN_PROGRESS status after ChangeSet was deleted due to the lack of refresh. ## Solution Refresh stacks after change set deletion. On create change sets, this would clear out the stack from panel. Removing some dead code as well. Since view/delete change set is disabled from command palette the `params` would never be undefined. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 6bd7267 commit 83a118e

File tree

4 files changed

+105
-37
lines changed

4 files changed

+105
-37
lines changed

packages/core/src/awsService/cloudformation/commands/cfnCommands.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
shouldImportResources,
3232
getResourcesToImport,
3333
getEnvironmentName,
34-
getChangeSetName,
3534
chooseOptionalFlagSuggestion as chooseOptionalFlagMode,
3635
getTags,
3736
getOnStackFailure,
@@ -111,14 +110,8 @@ export function executeChangeSetCommand(client: LanguageClient, coordinator: Sta
111110
}
112111

113112
export function deleteChangeSetCommand(client: LanguageClient) {
114-
return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params?: ChangeSetReference) => {
113+
return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params: ChangeSetReference) => {
115114
try {
116-
params = params ?? (await promptForChangeSetReference())
117-
118-
if (!params) {
119-
return
120-
}
121-
122115
const changeSetDeletion = new ChangeSetDeletion(params.stackName, params.changeSetName, client)
123116

124117
await changeSetDeletion.delete()
@@ -129,14 +122,8 @@ export function deleteChangeSetCommand(client: LanguageClient) {
129122
}
130123

131124
export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffWebviewProvider) {
132-
return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params?: ChangeSetReference) => {
125+
return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params: ChangeSetReference) => {
133126
try {
134-
params = params ?? (await promptForChangeSetReference())
135-
136-
if (!params) {
137-
return
138-
}
139-
140127
const describeChangeSetResult = await describeChangeSet(client, {
141128
changeSetName: params.changeSetName,
142129
stackName: params.stackName,
@@ -157,16 +144,6 @@ export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffW
157144
})
158145
}
159146

160-
async function promptForChangeSetReference(): Promise<ChangeSetReference | undefined> {
161-
const stackName = await getStackName()
162-
const changeSetName = await getChangeSetName()
163-
if (!stackName || !changeSetName) {
164-
return undefined
165-
}
166-
167-
return { stackName: stackName, changeSetName: changeSetName }
168-
}
169-
170147
export function deployTemplateCommand(
171148
client: LanguageClient,
172149
diffProvider: DiffWebviewProvider,

packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ import {
1515
import { deleteChangeSet, describeChangeSetDeletionStatus, getChangeSetDeletionStatus } from './stackActionApi'
1616
import { createChangeSetDeletionParams } from './stackActionUtil'
1717
import { getLogger } from '../../../../shared/logger/logger'
18-
import { extractErrorMessage } from '../../utils'
18+
import { commandKey, extractErrorMessage } from '../../utils'
19+
import { commands } from 'vscode'
20+
import globals from '../../../../shared/extensionGlobals'
1921

2022
export class ChangeSetDeletion {
2123
private readonly id: string
22-
private readonly stackName: string
23-
private readonly changeSetName: string
24-
private readonly client: LanguageClient
2524
private status: StackActionPhase | undefined
2625

27-
constructor(stackName: string, changeSetName: string, client: LanguageClient) {
26+
constructor(
27+
private readonly stackName: string,
28+
private readonly changeSetName: string,
29+
private readonly client: LanguageClient
30+
) {
2831
this.id = uuidv4()
29-
this.stackName = stackName
30-
this.changeSetName = changeSetName
31-
this.client = client
3232
}
3333

3434
async delete() {
@@ -38,7 +38,7 @@ export class ChangeSetDeletion {
3838
}
3939

4040
private pollForProgress() {
41-
const interval = setInterval(() => {
41+
const interval = globals.clock.setInterval(() => {
4242
getChangeSetDeletionStatus(this.client, { id: this.id })
4343
.then(async (deletionResult) => {
4444
if (deletionResult.phase === this.status) {
@@ -66,7 +66,8 @@ export class ChangeSetDeletion {
6666
describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided'
6767
)
6868
}
69-
clearInterval(interval)
69+
void commands.executeCommand(commandKey('stacks.refresh'))
70+
globals.clock.clearInterval(interval)
7071
break
7172
case StackActionPhase.DELETION_FAILED: {
7273
const describeDeplomentStatusResult = await describeChangeSetDeletionStatus(this.client, {
@@ -77,15 +78,17 @@ export class ChangeSetDeletion {
7778
this.stackName,
7879
describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided'
7980
)
80-
clearInterval(interval)
81+
void commands.executeCommand(commandKey('stacks.refresh'))
82+
globals.clock.clearInterval(interval)
8183
break
8284
}
8385
}
8486
})
8587
.catch(async (error) => {
8688
getLogger().error(`Error polling for deletion status: ${error}`)
8789
showErrorMessage(`Error polling for deletion status: ${extractErrorMessage(error)}`)
88-
clearInterval(interval)
90+
void commands.executeCommand(commandKey('stacks.refresh'))
91+
globals.clock.clearInterval(interval)
8992
})
9093
}, 1000)
9194
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import { SinonSandbox, SinonStub, createSandbox } from 'sinon'
8+
import { commands } from 'vscode'
9+
import { ChangeSetDeletion } from '../../../../../awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow'
10+
import {
11+
StackActionPhase,
12+
StackActionState,
13+
} from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType'
14+
import { commandKey } from '../../../../../awsService/cloudformation/utils'
15+
import { globals } from '../../../../../shared'
16+
17+
describe('ChangeSetDeletion', function () {
18+
let sandbox: SinonSandbox
19+
20+
beforeEach(function () {
21+
sandbox = createSandbox()
22+
})
23+
24+
afterEach(function () {
25+
sandbox.restore()
26+
})
27+
28+
describe('delete', function () {
29+
let mockClient: any
30+
let executeCommandStub: SinonStub
31+
let getChangeSetDeletionStatusStub: SinonStub
32+
let describeChangeSetDeletionStatusStub: SinonStub
33+
34+
beforeEach(function () {
35+
mockClient = { sendRequest: sandbox.stub().resolves({}) }
36+
executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves()
37+
38+
const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi')
39+
getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus')
40+
describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus')
41+
sandbox.stub(stackActionApi, 'deleteChangeSet').resolves()
42+
43+
sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => {
44+
setImmediate(() => callback())
45+
return 1 as any
46+
})
47+
sandbox.stub(globals.clock, 'clearInterval')
48+
})
49+
50+
it('should call refresh command after successful deletion', async function () {
51+
getChangeSetDeletionStatusStub.resolves({
52+
phase: StackActionPhase.DELETION_COMPLETE,
53+
state: StackActionState.SUCCESSFUL,
54+
})
55+
56+
const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient)
57+
await deletion.delete()
58+
await new Promise((resolve) => setImmediate(resolve))
59+
60+
assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh')))
61+
})
62+
63+
it('should call refresh command after failed deletion', async function () {
64+
getChangeSetDeletionStatusStub.resolves({ phase: StackActionPhase.DELETION_FAILED })
65+
describeChangeSetDeletionStatusStub.resolves({ FailureReason: 'Test failure' })
66+
67+
const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient)
68+
await deletion.delete()
69+
await new Promise((resolve) => setImmediate(resolve))
70+
71+
assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh')))
72+
})
73+
74+
it('should not call refresh command when polling encounters error', async function () {
75+
getChangeSetDeletionStatusStub.rejects(new Error('Polling error'))
76+
77+
const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient)
78+
await deletion.delete()
79+
await new Promise((resolve) => setImmediate(resolve))
80+
81+
assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh')))
82+
})
83+
})
84+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "CloudFormation: refresh stacks after change set deletion"
4+
}

0 commit comments

Comments
 (0)