Skip to content

Commit

Permalink
fix(toolkit): support multiple toolkit stacks in the same environment
Browse files Browse the repository at this point in the history
The --toolkit-stack-name option can be used to specify the name for
the toolkit stack. However, since the the toolkit stack outputs
had "Export"s, which must be unique within an environment, it was 
impossible to deploy multiple toolkit stacks.

This change removes the "Export"s as they are actually not used or needed
and also adds an integration test to verify that multiple toolkit stacks
can be deployed into the same environment.

`toolkitStackName` can also be specified in `cdk.json` or `~/.cdk.json`.
Updated the toolkit documentation topic to describe this.

Fixes #1416
  • Loading branch information
Elad Ben-Israel committed Dec 24, 2018
1 parent 068fa46 commit af8f23d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 17 deletions.
23 changes: 23 additions & 0 deletions docs/src/tools.rst
Expand Up @@ -96,6 +96,29 @@ Here are the actions you can take on your CDK app
If one of cdk.json or ~/.cdk.json exists, options specified there will be used
as defaults. Settings in cdk.json take precedence.
.. _config-files:
Configuration
=============
The CDK toolkit resolves its configuration by reading files in the following order:
1. ``~/.cdk.json``: user-level configuration file
2. ``cdk.json``: project configuration file
3. Command line arguments
The following options are supported in **cdk.json**:
* ``app`` (string): the command-line to use in order to invoke your CDK app.
* ``browser`` (string): the command to use to open the browser, using %u as a placeholder for the path of the file to open
* ``context`` (hash): key-value pairs of context values which can later be read by ``Construct.getContext(key)``
* ``language`` (string): programming langauge to use for **cdk-init**
* ``pathMetadata`` (boolean): Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)
* ``plugin`` (array): Name or path of a node package that extend the CDK features
* ``requireApproval`` (string): what security-sensitive changes need manual approval (choices: "never", "any-change", "broadening")
* ``toolkitStackName`` (string): the name of the CDK toolkit stack
* ``versionReporting`` (boolean): Include the "AWS::CDK::Metadata" resource in synthesized templates
.. _security-changes:
Security-related changes
Expand Down
13 changes: 8 additions & 5 deletions packages/aws-cdk/bin/cdk.ts
Expand Up @@ -46,16 +46,15 @@ async function parseCommandLineArguments() {
.option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined })
.option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true })
.option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined })
.option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack' })
.command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs
.option('long', { type: 'boolean', default: false, alias: 'l', desc: 'display environment information for each stack' }))
.command([ 'synthesize [STACKS..]', 'synth [STACKS..]' ], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs
.option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' })
.option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory' }))
.command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', yargs => yargs
.option('toolkit-stack-name', { type: 'string', desc: 'the name of the CDK toolkit stack' }))
.command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment')
.command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs
.option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'what security-sensitive changes need manual approval' })
.option('toolkit-stack-name', { type: 'string', desc: 'the name of the CDK toolkit stack' }))
.option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'what security-sensitive changes need manual approval' }))
.command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs
.option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' }))
.command('diff [STACK]', 'Compares the specified stack with the deployed stack or a local template file', yargs => yargs
Expand Down Expand Up @@ -144,6 +143,10 @@ async function initCommandLine() {
async function main(command: string, args: any): Promise<number | string | {} | void> {
const toolkitStackName: string = configuration.combined.get(['toolkitStackName']) || DEFAULT_TOOLKIT_STACK_NAME;

if (toolkitStackName !== DEFAULT_TOOLKIT_STACK_NAME) {
print(`Toolkit stack: ${colors.bold(toolkitStackName)}`);
}

args.STACKS = args.STACKS || [];
args.ENVIRONMENTS = args.ENVIRONMENTS || [];

Expand Down Expand Up @@ -320,7 +323,7 @@ async function initCommandLine() {
}

if (deployName !== stack.name) {
print('%s: deploying... (was %s)', colors.bold(deployName), colors.bold(stack.name));
print('%s: deploying... (was %s) using %s', colors.bold(deployName), colors.bold(stack.name));
} else {
print('%s: deploying...', colors.bold(stack.name));
}
Expand Down
24 changes: 24 additions & 0 deletions packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
scriptdir=$(cd $(dirname $0) && pwd)
source ${scriptdir}/common.bash
# ----------------------------------------------------------

setup

toolkit_stack_name_1="toolkit-stack-${RANDOM}"
toolkit_stack_name_2="toolkit-stack-${RANDOM}"

# deploy two toolkit stacks into the same environment (see #1416)
cdk bootstrap --toolkit-stack-name ${toolkit_stack_name_1}
cdk bootstrap --toolkit-stack-name ${toolkit_stack_name_2}

# just check that the new stack exists
aws cloudformation describe-stack-resources --stack-name ${toolkit_stack_name_1}
aws cloudformation describe-stack-resources --stack-name ${toolkit_stack_name_2}

# clean up
aws cloudformation delete-stack --stack-name ${toolkit_stack_name_1}
aws cloudformation delete-stack --stack-name ${toolkit_stack_name_2}

echo "✅ success"
22 changes: 10 additions & 12 deletions packages/aws-cdk/lib/api/bootstrap-environment.ts
Expand Up @@ -10,28 +10,26 @@ export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName';
export async function bootstrapEnvironment(environment: Environment, aws: SDK, toolkitStackName: string, roleArn: string | undefined): Promise<DeployStackResult> {
const synthesizedStack: SynthesizedStack = {
environment,
metadata: { },
metadata: {},
template: {
Description: "The CDK Toolkit Stack. It cas created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.",
Resources: {
StagingBucket: {
Type: "AWS::S3::Bucket",
Properties: {
AccessControl: "Private",
BucketEncryption: { ServerSideEncryptionConfiguration: [ { ServerSideEncryptionByDefault: { SSEAlgorithm: "aws:kms" } } ] }
}
Type: "AWS::S3::Bucket",
Properties: {
AccessControl: "Private",
BucketEncryption: { ServerSideEncryptionConfiguration: [{ ServerSideEncryptionByDefault: { SSEAlgorithm: "aws:kms" } }] }
}
}
},
Outputs: {
[BUCKET_NAME_OUTPUT]: {
Description: "The name of the S3 bucket owned by the CDK toolkit stack",
Value: { Ref: "StagingBucket" },
Export: { Name: "CDKToolkit:BucketName" }
Description: "The name of the S3 bucket owned by the CDK toolkit stack",
Value: { Ref: "StagingBucket" }
},
[BUCKET_DOMAIN_NAME_OUTPUT]: {
Description: "The domain name of the S3 bucket owned by the CDK toolkit stack",
Value: { "Fn::GetAtt": [ "StagingBucket", "DomainName" ] },
Export: { Name: "CDKToolkit:BucketDomainName" }
Description: "The domain name of the S3 bucket owned by the CDK toolkit stack",
Value: { "Fn::GetAtt": ["StagingBucket", "DomainName"] }
}
}
},
Expand Down

0 comments on commit af8f23d

Please sign in to comment.