From e0da41f722bb0369733bfba764ebcf6c9496517f 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 ephemeral Stacks. An ephemeral Stack is a Stack that is meant to be deployed outside the context of `cdk deploy` - for example, in a CodePipeline. Ephemeral 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 | 11 ++++- packages/@aws-cdk/cdk/lib/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 | 60 ++++++++++++++++++++++- 6 files changed, 78 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 25452e6e1d991..fae7728df7f73 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -22,6 +22,11 @@ export interface StackProps { * Optional. If not supplied, the HashedNamingScheme will be used. */ namingScheme?: IAddressingScheme; + + /** + * @default false + */ + ephemeral?: boolean; } /** @@ -79,6 +84,8 @@ export class Stack extends Construct { */ public readonly name: string; + public readonly ephemeral: boolean; + /* * Used to determine if this construct is a stack. */ @@ -113,6 +120,7 @@ export class Stack extends Construct { this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme()); this.name = this.node.id; + this.ephemeral = props && props.ephemeral === true ? true : false; } /** @@ -453,7 +461,8 @@ export class Stack extends Construct { environment: this.environment, properties: { templateFile: template, - } + }, + ephemeral: this.ephemeral, }; 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..271b81f2481aa 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 || {}, + ephemeral: artifact.ephemeral, }; 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/cx-api/lib/artifacts.ts b/packages/@aws-cdk/cx-api/lib/artifacts.ts index 133027dfe04b1..db80719575454 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 }; + ephemeral?: 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..f7f0fe5095016 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; + ephemeral?: 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..6575346603e25 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); + // filter out ephemeral Stacks + const nonEphemeralStacks = stacks.filter(s => s.ephemeral !== true); + debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(nonEphemeralStacks)); + return this.applyRenames(nonEphemeralStacks); } 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..d17eedbfb57d1 100644 --- a/packages/aws-cdk/test/api/test.stacks.ts +++ b/packages/aws-cdk/test/api/test.stacks.ts @@ -86,4 +86,62 @@ export = { test.done(); }, -}; \ No newline at end of file + + async 'does not return ephemeral Stacks when called without any selectors'(test: Test) { + // GIVEN + const result: cxapi.SynthesizeResponse = { + version: '1', + stacks: [ + { + name: 'EphemeralStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + ephemeral: true, + }, + ], + }; + const stacks = new AppStacks({ + configuration: new Configuration(), + aws: new SDK(), + synthesizer: async () => result, + }); + + // WHEN + const synthed = await stacks.selectStacks([], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 0); + + test.done(); + }, + + async 'does return ephemeral Stacks when called with selectors matching it'(test: Test) { + // GIVEN + const result: cxapi.SynthesizeResponse = { + version: '1', + stacks: [ + { + name: 'EphemeralStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + ephemeral: true, + }, + ], + }; + const stacks = new AppStacks({ + configuration: new Configuration(), + aws: new SDK(), + synthesizer: async () => result, + }); + + // WHEN + const synthed = await stacks.selectStacks(['EphemeralStack'], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 1); + + test.done(); + }, +};