From 2af83094b19c547f75e8757c2365d25388f78fc3 Mon Sep 17 00:00:00 2001 From: Romain Marcadier-Muller Date: Fri, 12 Oct 2018 15:26:18 +0200 Subject: [PATCH] fix(aws-cdk): Auto-delete stacks that failed creating before new attempt (#917) Restore the previous behavior around stacks that failed creation, detecting the condition and automatically deleting the stack before making a new attempt. The stack is not deleted after the failure in order to allow for forensics to be performed. Also, deleting the stack on the next creation attempt is more robust. --- packages/aws-cdk/lib/api/deploy-stack.ts | 11 ++++++++++- packages/aws-cdk/lib/api/util/cloudformation.ts | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 6d3a5c71e79e9..b7b9fe7ce849b 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -7,7 +7,7 @@ import { prepareAssets } from '../assets'; import { debug, error } from '../logging'; import { Mode } from './aws-auth/credentials'; import { ToolkitInfo } from './toolkit-info'; -import { describeStack, stackExists, waitForChangeSet, waitForStack } from './util/cloudformation'; +import { describeStack, stackExists, stackFailedCreating, waitForChangeSet, waitForStack } from './util/cloudformation'; import { StackActivityMonitor } from './util/cloudformation/stack-activity-monitor'; import { StackStatus } from './util/cloudformation/stack-status'; import { SDK } from './util/sdk'; @@ -43,6 +43,15 @@ export async function deployStack(stack: cxapi.SynthesizedStack, const cfn = await sdk.cloudFormation(stack.environment, Mode.ForWriting); const bodyParameter = await makeBodyParameter(stack, toolkitInfo); + if (await stackFailedCreating(cfn, deployName)) { + debug(`Found existing stack ${deployName} that had previously failed creation. Deleting it before attempting to re-create it.`); + await cfn.deleteStack({ StackName: deployName }).promise(); + const deletedStack = await waitForStack(cfn, deployName, false); + if (deletedStack && deletedStack.StackStatus !== 'DELETE_COMPLETE') { + throw new Error(`Failed deleting stack ${deployName} that had previously failed creation (current state: ${deletedStack.StackStatus})`); + } + } + const update = await stackExists(cfn, deployName); const changeSetName = `CDK-${executionId}`; diff --git a/packages/aws-cdk/lib/api/util/cloudformation.ts b/packages/aws-cdk/lib/api/util/cloudformation.ts index 52f1bc076bfd1..3551bca8e7ae3 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation.ts @@ -49,6 +49,20 @@ export async function stackExists(cfn: CloudFormation, stackName: string): Promi return description !== undefined; } +/** + * Checks whether a stack has failed creation in CloudFormation. This is identified by the current stack Status being + * ``ROLLBACK_COMPLETE``. + * + * @param cfn a CloudFormation client + * @param stackName the name of the stack to be checked for + * + * @returns +true+ if the stack exists and is in failed-creation state. + */ +export async function stackFailedCreating(cfn: CloudFormation, stackName: string): Promise { + const description = await describeStack(cfn, stackName); + return description != null && description.StackStatus === 'ROLLBACK_COMPLETE'; +} + /** * Waits for a function to return non-+undefined+ before returning. *