Skip to content

Commit

Permalink
explicitly detect for a DELETE_FAILED stack state during deletion and…
Browse files Browse the repository at this point in the history
… report this and the reason to user immediately; this fixes architect/architect#1156
  • Loading branch information
filmaj committed Jun 9, 2021
1 parent 87fe6bf commit 3543715
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 15 deletions.
11 changes: 10 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# Architect Sandbox changelog
# Architect Destroy changelog

---

## [1.2.4] 2021-06-09

### Changed

- If the CloudFormation DeleteStack operation detects that the Stack has a status of `DELETE_FAILED`, it will now report this, along with the status reason, and exit with a non-zero code rather than wait for the `destroy` command timeout to run out; this fixes [#1156](https://github.com/architect/architect/issues/1156)
- `destroy` will now exit with a non-zero code if any errors are raised during its execution

---

Expand Down
30 changes: 17 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module.exports = function destroy (params, callback) {
throw ReferenceError('Missing params.appname')
}

// StackName → AWS, stackname → user-specified
// StackName → AWS, stackname → user-specified suffix (via --name param)
let StackName = toLogicalID(`${appname}-${env}`)
if (stackname) {
StackName += toLogicalID(stackname)
Expand Down Expand Up @@ -183,22 +183,26 @@ module.exports = function destroy (params, callback) {
cloudformation.describeStacks({
StackName
},
function done (err) {
function done (err, result) {
if (stackNotFound(StackName, err)) {
update.done(`Successfully destroyed ${StackName}`)
callback()
return callback()
}
else {
setTimeout(function delay () {
if (tries === max) {
callback(Error('Destroy failed; hit max retries'))
}
else {
tries += 1
checkit()
}
}, 10000 * tries)
if (!err && result.Stacks) {
let stack = result.Stacks.find(s => s.StackName === StackName)
if (stack && stack.StackStatus === 'DELETE_FAILED') {
return callback(Error(`CloudFormation Stack "${StackName}" destroy failed: ${stack.StackStatusReason}`))
}
}
setTimeout(function delay () {
if (tries === max) {
callback(Error('Destroy failed; hit max retries'))
}
else {
tries += 1
checkit()
}
}, 10000 * tries)
})
}
checkit()
Expand Down
24 changes: 23 additions & 1 deletion test/unit/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ test('destroy should error if DynamoDB tables exist and force is not provided',
})
})

test('destroy should invoke deleteStack', t => {
test('destroy should invoke deleteStack and return once describeStacks return a not found message', t => {
t.plan(1)
mocks.staticBucket(false) // no static bucket
mocks.deployBucket(false) // no deploy bucket
Expand All @@ -172,3 +172,25 @@ test('destroy should invoke deleteStack', t => {
aws.restore()
})
})

test('destroy should invoke deleteStack and error if describeStacks returns a status of DELETE_FAILED', t => {
t.plan(2)
aws.mock('CloudFormation', 'describeStacks', (ps, cb) => {
cb(null, { Stacks: [ {
StackName: 'PentagonSecurityStaging',
StackStatus: 'DELETE_FAILED',
StackStatusReason: 'task failed successfully',
Outputs: []
} ] })
})
mocks.deployBucket(false) // no deploy bucket
mocks.dbTables([]) // one table
mocks.ssmParams([]) // no params
mocks.cloudwatchLogs([]) // no logs
mocks.deleteStack()
destroy(base, (err) => {
t.ok(err, 'Error returned')
t.match(err.message, /task failed successfully/, 'Delete failed reason provided')
aws.restore()
})
})

0 comments on commit 3543715

Please sign in to comment.