Skip to content

Commit

Permalink
feat(toolkit): introduce the concept of auto-deployed Stacks. (#2046)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
skinny85 authored Mar 20, 2019
1 parent bf79c82 commit abacc66
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 7 deletions.
26 changes: 25 additions & 1 deletion packages/@aws-cdk/cdk/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/cdk/lib/synthesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -369,4 +370,4 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store:
}

return stacks;
}
}
3 changes: 2 additions & 1 deletion packages/@aws-cdk/cdk/test/test.synthesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
},
});
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/cx-api/lib/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/cx-api/lib/cxapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface SynthesizedStack {
missing?: { [key: string]: MissingContext };
metadata: StackMetadata;
template: any;
autoDeploy?: boolean;

/**
* Other stacks this stack depends on
Expand Down
6 changes: 4 additions & 2 deletions packages/aws-cdk/lib/api/cxapp/stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, cxapi.SynthesizedStack>();
Expand Down
84 changes: 83 additions & 1 deletion packages/aws-cdk/test/api/test.stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,86 @@ export = {

test.done();
},
};

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

0 comments on commit abacc66

Please sign in to comment.