Skip to content

Commit abacc66

Browse files
authored
feat(toolkit): introduce the concept of auto-deployed Stacks. (#2046)
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.
1 parent bf79c82 commit abacc66

File tree

7 files changed

+119
-7
lines changed

7 files changed

+119
-7
lines changed

packages/@aws-cdk/cdk/lib/stack.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ export interface StackProps {
3030
* Optional. If not supplied, the HashedNamingScheme will be used.
3131
*/
3232
namingScheme?: IAddressingScheme;
33+
34+
/**
35+
* Should the Stack be deployed when running `cdk deploy` without arguments
36+
* (and listed when running `cdk synth` without arguments).
37+
* Setting this to `false` is useful when you have a Stack in your CDK app
38+
* that you don't want to deploy using the CDK toolkit -
39+
* for example, because you're planning on deploying it through CodePipeline.
40+
*
41+
* @default true
42+
*/
43+
autoDeploy?: boolean;
3344
}
3445

3546
/**
@@ -90,6 +101,17 @@ export class Stack extends Construct {
90101
*/
91102
public readonly name: string;
92103

104+
/**
105+
* Should the Stack be deployed when running `cdk deploy` without arguments
106+
* (and listed when running `cdk synth` without arguments).
107+
* Setting this to `false` is useful when you have a Stack in your CDK app
108+
* that you don't want to deploy using the CDK toolkit -
109+
* for example, because you're planning on deploying it through CodePipeline.
110+
*
111+
* By default, this is `true`.
112+
*/
113+
public readonly autoDeploy: boolean;
114+
93115
/*
94116
* Used to determine if this construct is a stack.
95117
*/
@@ -132,6 +154,7 @@ export class Stack extends Construct {
132154

133155
this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme());
134156
this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName();
157+
this.autoDeploy = props && props.autoDeploy === false ? false : true;
135158
}
136159

137160
/**
@@ -474,7 +497,8 @@ export class Stack extends Construct {
474497
environment: this.environment,
475498
properties: {
476499
templateFile: template,
477-
}
500+
},
501+
autoDeploy: this.autoDeploy ? undefined : false,
478502
};
479503

480504
if (Object.keys(this.parameterValues).length > 0) {

packages/@aws-cdk/cdk/lib/synthesis.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store:
354354
environment: { name: artifact.environment.substr('aws://'.length), account: match[1], region: match[2] },
355355
template,
356356
metadata: artifact.metadata || {},
357+
autoDeploy: artifact.autoDeploy,
357358
};
358359

359360
if (artifact.dependencies && artifact.dependencies.length > 0) {
@@ -369,4 +370,4 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store:
369370
}
370371

371372
return stacks;
372-
}
373+
}

packages/@aws-cdk/cdk/test/test.synthesis.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export = {
8989
'one-stack': {
9090
type: 'aws:cloudformation:stack',
9191
environment: 'aws://unknown-account/unknown-region',
92-
properties: { templateFile: 'one-stack.template.json' }
92+
properties: { templateFile: 'one-stack.template.json' },
93+
autoDeploy: undefined,
9394
}
9495
},
9596
});

packages/@aws-cdk/cx-api/lib/artifacts.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ export interface Artifact {
1313
dependencies?: string[];
1414
missing?: { [key: string]: any };
1515
properties?: { [name: string]: any };
16+
autoDeploy?: boolean;
1617
}
1718

1819
export function validateArtifact(artifcat: Artifact) {
1920
if (!AWS_ENV_REGEX.test(artifcat.environment)) {
2021
throw new Error(`Artifact "environment" must conform to ${AWS_ENV_REGEX}: ${artifcat.environment}`);
2122
}
22-
}
23+
}

packages/@aws-cdk/cx-api/lib/cxapi.ts

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface SynthesizedStack {
8181
missing?: { [key: string]: MissingContext };
8282
metadata: StackMetadata;
8383
template: any;
84+
autoDeploy?: boolean;
8485

8586
/**
8687
* Other stacks this stack depends on

packages/aws-cdk/lib/api/cxapp/stacks.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ export class AppStacks {
8787
}
8888

8989
if (selectors.length === 0) {
90-
debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(stacks));
91-
return this.applyRenames(stacks);
90+
// remove non-auto deployed Stacks
91+
const autoDeployedStacks = stacks.filter(s => s.autoDeploy !== false);
92+
debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(autoDeployedStacks));
93+
return this.applyRenames(autoDeployedStacks);
9294
}
9395

9496
const allStacks = new Map<string, cxapi.SynthesizedStack>();

packages/aws-cdk/test/api/test.stacks.ts

+83-1
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,86 @@ export = {
8686

8787
test.done();
8888
},
89-
};
89+
90+
async 'does not return non-autoDeployed Stacks when called without any selectors'(test: Test) {
91+
// GIVEN
92+
const stacks = appStacksWith([
93+
{
94+
name: 'NotAutoDeployedStack',
95+
template: { resource: 'Resource' },
96+
environment: { name: 'dev', account: '12345', region: 'here' },
97+
metadata: {},
98+
autoDeploy: false,
99+
},
100+
]);
101+
102+
// WHEN
103+
const synthed = await stacks.selectStacks([], ExtendedStackSelection.None);
104+
105+
// THEN
106+
test.equal(synthed.length, 0);
107+
108+
test.done();
109+
},
110+
111+
async 'does return non-autoDeployed Stacks when called with selectors matching it'(test: Test) {
112+
// GIVEN
113+
const stacks = appStacksWith([
114+
{
115+
name: 'NotAutoDeployedStack',
116+
template: { resource: 'Resource' },
117+
environment: { name: 'dev', account: '12345', region: 'here' },
118+
metadata: {},
119+
autoDeploy: false,
120+
},
121+
]);
122+
123+
// WHEN
124+
const synthed = await stacks.selectStacks(['NotAutoDeployedStack'], ExtendedStackSelection.None);
125+
126+
// THEN
127+
test.equal(synthed.length, 1);
128+
129+
test.done();
130+
},
131+
132+
async "does return an non-autoDeployed Stack when it's a dependency of a selected Stack"(test: Test) {
133+
// GIVEN
134+
const stacks = appStacksWith([
135+
{
136+
name: 'NotAutoDeployedStack',
137+
template: { resource: 'Resource' },
138+
environment: { name: 'dev', account: '12345', region: 'here' },
139+
metadata: {},
140+
autoDeploy: false,
141+
},
142+
{
143+
name: 'AutoDeployedStack',
144+
template: { resource: 'Resource' },
145+
environment: { name: 'dev', account: '12345', region: 'here' },
146+
metadata: {},
147+
dependsOn: ['NotAutoDeployedStack'],
148+
},
149+
]);
150+
151+
// WHEN
152+
const synthed = await stacks.selectStacks(['AutoDeployedStack'], ExtendedStackSelection.Upstream);
153+
154+
// THEN
155+
test.equal(synthed.length, 2);
156+
157+
test.done();
158+
},
159+
};
160+
161+
function appStacksWith(stacks: cxapi.SynthesizedStack[]): AppStacks {
162+
const response: cxapi.SynthesizeResponse = {
163+
version: '1',
164+
stacks,
165+
};
166+
return new AppStacks({
167+
configuration: new Configuration(),
168+
aws: new SDK(),
169+
synthesizer: async () => response,
170+
});
171+
}

0 commit comments

Comments
 (0)