From 095da1495e7408e3ed7d4eba9eec176565f39eae Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 27 Dec 2018 15:54:40 +0200 Subject: [PATCH] fix(toolkit): support multiple toolkit stacks in the same environment (#1427) 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 --- docs/src/tools.rst | 23 ++++++++++++++++++ packages/aws-cdk/bin/cdk.ts | 11 +++++---- .../test-cdk-multiple-toolkit-stacks.sh | 24 +++++++++++++++++++ .../aws-cdk/lib/api/bootstrap-environment.ts | 22 ++++++++--------- 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100755 packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh diff --git a/docs/src/tools.rst b/docs/src/tools.rst index a1f1d436a329c..238453ee3b31e 100644 --- a/docs/src/tools.rst +++ b/docs/src/tools.rst @@ -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 diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 0682e7db32b32..56e6fed5c3b86 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -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 @@ -144,6 +143,10 @@ async function initCommandLine() { async function main(command: string, args: any): Promise { 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 || []; diff --git a/packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh b/packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh new file mode 100755 index 0000000000000..082013b3164b2 --- /dev/null +++ b/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-1-${RANDOM}" +toolkit_stack_name_2="toolkit-stack-2-${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" diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts index 9755769caa4a0..6503c5870aa02 100644 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap-environment.ts @@ -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 { 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"] } } } },