From 8b5e0e20679f4d8a2ebe9b6b98aee6d58249dad5 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 15 Mar 2019 16:58:25 -0700 Subject: [PATCH] feat(toolkit): introduce the concept of auto-deployed Stacks. A Stack that is not auto-deployed is meant to be deployed outside the context of the `cdk deploy` command - for example, in a CodePipeline. These Stacks do not appear when running `cdk synth` or `cdk deploy`, unless you explicitly filter for them. This is useful when modeling things like Lambda in CodePipeline, where the main deployment needs to happen in the Pipeline, but you might want to test things locally before pushing it to the Pipeline. --- packages/@aws-cdk/cdk/lib/stack.ts | 26 +++++- packages/@aws-cdk/cdk/lib/synthesis.ts | 3 +- packages/@aws-cdk/cdk/test/test.synthesis.ts | 3 +- packages/@aws-cdk/cx-api/lib/artifacts.ts | 3 +- packages/@aws-cdk/cx-api/lib/cxapi.ts | 1 + packages/aws-cdk/lib/api/cxapp/stacks.ts | 6 +- packages/aws-cdk/test/api/test.stacks.ts | 84 +++++++++++++++++++- 7 files changed, 119 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 38ad44c04ba88..6147adaec4f3b 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -30,6 +30,17 @@ export interface StackProps { * Optional. If not supplied, the HashedNamingScheme will be used. */ namingScheme?: IAddressingScheme; + + /** + * Should the Stack be deployed when running `cdk deploy` without arguments + * (and listed when running `cdk synth` without arguments). + * Setting this to `false` is useful when you have a Stack in your CDK app + * that you don't want to deploy using the CDK toolkit - + * for example, because you're planning on deploying it through CodePipeline. + * + * @default true + */ + autoDeploy?: boolean; } /** @@ -90,6 +101,17 @@ export class Stack extends Construct { */ public readonly name: string; + /** + * Should the Stack be deployed when running `cdk deploy` without arguments + * (and listed when running `cdk synth` without arguments). + * Setting this to `false` is useful when you have a Stack in your CDK app + * that you don't want to deploy using the CDK toolkit - + * for example, because you're planning on deploying it through CodePipeline. + * + * By default, this is `true`. + */ + public readonly autoDeploy: boolean; + /* * Used to determine if this construct is a stack. */ @@ -132,6 +154,7 @@ export class Stack extends Construct { this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme()); this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName(); + this.autoDeploy = props && props.autoDeploy === false ? false : true; } /** @@ -474,7 +497,8 @@ export class Stack extends Construct { environment: this.environment, properties: { templateFile: template, - } + }, + autoDeploy: this.autoDeploy ? undefined : false, }; if (Object.keys(this.parameterValues).length > 0) { diff --git a/packages/@aws-cdk/cdk/lib/synthesis.ts b/packages/@aws-cdk/cdk/lib/synthesis.ts index 9b5d95d3ae1ae..b1c169047525a 100644 --- a/packages/@aws-cdk/cdk/lib/synthesis.ts +++ b/packages/@aws-cdk/cdk/lib/synthesis.ts @@ -354,6 +354,7 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store: environment: { name: artifact.environment.substr('aws://'.length), account: match[1], region: match[2] }, template, metadata: artifact.metadata || {}, + autoDeploy: artifact.autoDeploy, }; if (artifact.dependencies && artifact.dependencies.length > 0) { @@ -369,4 +370,4 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store: } return stacks; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/test/test.synthesis.ts b/packages/@aws-cdk/cdk/test/test.synthesis.ts index 3398759363eb6..88d0582d8e755 100644 --- a/packages/@aws-cdk/cdk/test/test.synthesis.ts +++ b/packages/@aws-cdk/cdk/test/test.synthesis.ts @@ -89,7 +89,8 @@ export = { 'one-stack': { type: 'aws:cloudformation:stack', environment: 'aws://unknown-account/unknown-region', - properties: { templateFile: 'one-stack.template.json' } + properties: { templateFile: 'one-stack.template.json' }, + autoDeploy: undefined, } }, }); diff --git a/packages/@aws-cdk/cx-api/lib/artifacts.ts b/packages/@aws-cdk/cx-api/lib/artifacts.ts index 133027dfe04b1..3d2e7727fcda4 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts.ts @@ -13,10 +13,11 @@ export interface Artifact { dependencies?: string[]; missing?: { [key: string]: any }; properties?: { [name: string]: any }; + autoDeploy?: boolean; } export function validateArtifact(artifcat: Artifact) { if (!AWS_ENV_REGEX.test(artifcat.environment)) { throw new Error(`Artifact "environment" must conform to ${AWS_ENV_REGEX}: ${artifcat.environment}`); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 8bbbdcf9340bb..f2ee63733958f 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -81,6 +81,7 @@ export interface SynthesizedStack { missing?: { [key: string]: MissingContext }; metadata: StackMetadata; template: any; + autoDeploy?: boolean; /** * Other stacks this stack depends on diff --git a/packages/aws-cdk/lib/api/cxapp/stacks.ts b/packages/aws-cdk/lib/api/cxapp/stacks.ts index 7578717093553..85cea47b6a9ab 100644 --- a/packages/aws-cdk/lib/api/cxapp/stacks.ts +++ b/packages/aws-cdk/lib/api/cxapp/stacks.ts @@ -87,8 +87,10 @@ export class AppStacks { } if (selectors.length === 0) { - debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(stacks)); - return this.applyRenames(stacks); + // remove non-auto deployed Stacks + const autoDeployedStacks = stacks.filter(s => s.autoDeploy !== false); + debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(autoDeployedStacks)); + return this.applyRenames(autoDeployedStacks); } const allStacks = new Map(); diff --git a/packages/aws-cdk/test/api/test.stacks.ts b/packages/aws-cdk/test/api/test.stacks.ts index 55aefd18d7a4d..2322668d3ad67 100644 --- a/packages/aws-cdk/test/api/test.stacks.ts +++ b/packages/aws-cdk/test/api/test.stacks.ts @@ -86,4 +86,86 @@ export = { test.done(); }, -}; \ No newline at end of file + + async 'does not return non-autoDeployed Stacks when called without any selectors'(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks([], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 0); + + test.done(); + }, + + async 'does return non-autoDeployed Stacks when called with selectors matching it'(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks(['NotAutoDeployedStack'], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 1); + + test.done(); + }, + + async "does return an non-autoDeployed Stack when it's a dependency of a selected Stack"(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + { + name: 'AutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + dependsOn: ['NotAutoDeployedStack'], + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks(['AutoDeployedStack'], ExtendedStackSelection.Upstream); + + // THEN + test.equal(synthed.length, 2); + + test.done(); + }, +}; + +function appStacksWith(stacks: cxapi.SynthesizedStack[]): AppStacks { + const response: cxapi.SynthesizeResponse = { + version: '1', + stacks, + }; + return new AppStacks({ + configuration: new Configuration(), + aws: new SDK(), + synthesizer: async () => response, + }); +}