Skip to content

Commit

Permalink
chore(aws-cdk): correct concurrent deploy error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
relm923 committed Aug 9, 2022
1 parent a3a0eef commit fa2369b
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 28 deletions.
50 changes: 23 additions & 27 deletions packages/aws-cdk/lib/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type DeploymentState = 'pending' | 'queued' | 'deploying' | 'completed' | 'faile
export const deployStacks = async (stacks: cxapi.CloudFormationStackArtifact[], { concurrency, deployStack }: Options): Promise<void> => {
const queue = new PQueue({ concurrency });
const deploymentStates = stacks.reduce((acc, stack) => ({ ...acc, [stack.id]: 'pending' as const }), {} as Record<string, DeploymentState>);
const deployPromises: Promise<void>[] = [];

const isStackUnblocked = (stack: cxapi.CloudFormationStackArtifact) =>
stack.dependencies
Expand All @@ -21,31 +20,32 @@ export const deployStacks = async (stacks: cxapi.CloudFormationStackArtifact[],

const hasAnyStackFailed = (states: Record<string, DeploymentState>) => Object.values(states).includes('failed');

const deploymentErrors: Error[] = [];

const enqueueStackDeploys = () => {
stacks.forEach(async (stack) => {
if (deploymentStates[stack.id] === 'pending' && isStackUnblocked(stack)) {
deploymentStates[stack.id] = 'queued';

deployPromises.push(
queue.add(async () => {
// Do not start new deployments if any has already failed
if (hasAnyStackFailed(deploymentStates)) {
deploymentStates[stack.id] = 'skipped';
return;
}

deploymentStates[stack.id] = 'deploying';
try {
await deployStack(stack);
} catch (e) {
deploymentStates[stack.id] = 'failed';
throw e;
}

deploymentStates[stack.id] = 'completed';
await enqueueStackDeploys();
}),
);
await queue.add(async () => {
// Do not start new deployments if any has already failed
if (hasAnyStackFailed(deploymentStates)) {
deploymentStates[stack.id] = 'skipped';
return;
}

deploymentStates[stack.id] = 'deploying';

await deployStack(stack).catch((err) => {
deploymentStates[stack.id] = 'failed';
throw err;
});

deploymentStates[stack.id] = 'completed';
enqueueStackDeploys();
}).catch((err) => {
deploymentErrors.push(err);
});
}
});
};
Expand All @@ -54,11 +54,7 @@ export const deployStacks = async (stacks: cxapi.CloudFormationStackArtifact[],

await queue.onIdle();

const results = await Promise.allSettled(deployPromises);
const isRejectedResult = (result: PromiseSettledResult<void>): result is PromiseRejectedResult => result.status === 'rejected';
const errors = results.filter(isRejectedResult).map(({ reason }) => reason);

if (errors.length) {
throw Error(`Stack Deployments Failed: ${errors}`);
if (deploymentErrors.length) {
throw Error(`Stack Deployments Failed: ${deploymentErrors}`);
}
};
2 changes: 1 addition & 1 deletion packages/aws-cdk/test/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { deployStacks } from '../lib/deploy';

type Stack = cxapi.CloudFormationStackArtifact;

const sleep = async (duration: number) => new Promise((resolve) => setTimeout(() => resolve(), duration));
const sleep = async (duration: number) => new Promise<void>((resolve) => setTimeout(() => resolve(), duration));

/**
* Repurposing unused stack attributes to create specific test scenarios
Expand Down

0 comments on commit fa2369b

Please sign in to comment.