diff --git a/packages/aws-cdk/lib/deploy.ts b/packages/aws-cdk/lib/deploy.ts index 95f45e8d93299..75cc91b025c94 100644 --- a/packages/aws-cdk/lib/deploy.ts +++ b/packages/aws-cdk/lib/deploy.ts @@ -16,7 +16,7 @@ export const deployStacks = async (stacks: cxapi.CloudFormationStackArtifact[], stack.dependencies .map(({ id }) => id) .filter((id) => !id.endsWith('.assets')) - .every((id) => deploymentStates[id] === 'completed'); + .every((id) => !deploymentStates[id] || deploymentStates[id] === 'completed'); // Dependency not selected or already finished const hasAnyStackFailed = (states: Record) => Object.values(states).includes('failed'); @@ -57,4 +57,10 @@ export const deployStacks = async (stacks: cxapi.CloudFormationStackArtifact[], if (deploymentErrors.length) { throw Error(`Stack Deployments Failed: ${deploymentErrors}`); } + + // We shouldn't be able to get here, but check it anyway + const neverUnblocked = Object.entries(deploymentStates).filter(([_, s]) => s === 'pending').map(([n, _]) => n); + if (neverUnblocked.length > 0) { + throw new Error(`The following stacks never became unblocked: ${neverUnblocked.join(', ')}. Please report this at https://github.com/aws/aws-cdk/issues`); + } }; \ No newline at end of file diff --git a/packages/aws-cdk/test/deploy.test.ts b/packages/aws-cdk/test/deploy.test.ts index 600f47531dd95..9a8a83b0d1c13 100644 --- a/packages/aws-cdk/test/deploy.test.ts +++ b/packages/aws-cdk/test/deploy.test.ts @@ -110,6 +110,14 @@ describe('DeployStacks', () => { ], expected: ['C', 'D', 'A', 'B'], }, + { + scenario: 'A -> B, A not selected', + concurrency: 1, + toDeploy: [ + { id: 'B', dependencies: [{ id: 'A' }] }, + ], + expected: ['B'], + }, ])('Success - Concurrency: $concurrency - $scenario', async ({ concurrency, expected, toDeploy }) => { await expect(deployStacks(toDeploy as unknown as Stack[], { concurrency, deployStack })).resolves.toBeUndefined(); diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index 8c6a722bdb674..131e759e10495 100755 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -279,6 +279,9 @@ class DockerStackWithCustomFile extends cdk.Stack { } } +/** + * A stack that will never succeed deploying (done in a way that CDK cannot detect but CFN will complain about) + */ class FailedStack extends cdk.Stack { constructor(parent, id, props) { @@ -400,7 +403,11 @@ switch (stackSet) { new LambdaHotswapStack(app, `${stackPrefix}-lambda-hotswap`); new DockerStack(app, `${stackPrefix}-docker`); new DockerStackWithCustomFile(app, `${stackPrefix}-docker-with-custom-file`); - new FailedStack(app, `${stackPrefix}-failed`) + const failed = new FailedStack(app, `${stackPrefix}-failed`) + + // A stack that depends on the failed stack -- used to test that '-e' does not deploy the failing stack + const dependsOnFailed = new OutputsStack(app, `${stackPrefix}-depends-on-failed`); + dependsOnFailed.addDependency(failed); if (process.env.ENABLE_VPC_TESTING) { // Gating so we don't do context fetching unless that's what we are here for const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }; diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index 77832d9df2a45..89477861966d1 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -143,6 +143,29 @@ integTest('automatic ordering with concurrency', withDefaultFixture(async (fixtu await fixture.cdkDestroy('order-providing'); })); +integTest('--exclusively selects only selected stack', withDefaultFixture(async (fixture) => { + // Deploy the "depends-on-failed" stack, with --exclusively. It will NOT fail (because + // of --exclusively) and it WILL create an output we can check for to confirm that it did + // get deployed. + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + + await fixture.cdkDeploy('depends-on-failed', { + options: [ + '--exclusively', + '--outputs-file', outputsFile, + ], + }); + + // Verify the output to see that the stack deployed + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + expect(outputs).toEqual({ + [`${fixture.stackNamePrefix}-depends-on-failed`]: { + TopicName: `${fixture.stackNamePrefix}-depends-on-failedMyTopic`, + }, + }); +})); + integTest('context setting', withDefaultFixture(async (fixture) => { await fs.writeFile(path.join(fixture.integTestDir, 'cdk.context.json'), JSON.stringify({ contextkey: 'this is the context value',