From a5ceee24b1e9a6986951ab91644d735c9196f138 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Mon, 24 Dec 2018 17:41:20 +0200 Subject: [PATCH 1/2] fix(toolkit): support multiple toolkit stacks in the same environment 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 7b47d30f831c9..4ea63202380f2 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 32de0c5f228c1..3f45103f2cde1 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..cee149a83c095 --- /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-${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" \ No newline at end of file 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"] } } } }, From 66343e7051417e36dcb1d87ce155e5561cc2ac51 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 27 Dec 2018 11:47:36 +0200 Subject: [PATCH 2/2] Extended toolkit stack string literal portion to avoid duplicates --- .../aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index cee149a83c095..082013b3164b2 100755 --- a/packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh +++ b/packages/aws-cdk/integ-tests/test-cdk-multiple-toolkit-stacks.sh @@ -6,8 +6,8 @@ source ${scriptdir}/common.bash setup -toolkit_stack_name_1="toolkit-stack-${RANDOM}" -toolkit_stack_name_2="toolkit-stack-${RANDOM}" +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} @@ -21,4 +21,4 @@ aws cloudformation describe-stack-resources --stack-name ${toolkit_stack_name_2} aws cloudformation delete-stack --stack-name ${toolkit_stack_name_1} aws cloudformation delete-stack --stack-name ${toolkit_stack_name_2} -echo "✅ success" \ No newline at end of file +echo "✅ success"