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, + }); +}