From 90de4b52e166591b1b30b6fac9a3b0876639bb03 Mon Sep 17 00:00:00 2001 From: Thorsten Hoeger Date: Fri, 14 Feb 2020 23:21:35 +0100 Subject: [PATCH 01/18] feat(appsync): mapping template for lambdy proxy --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 62ae52d322147..73b69d491b969 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -706,6 +706,13 @@ export abstract class MappingTemplate { return this.fromString(`{"version": "2017-02-28", "operation": "Invoke", "payload": ${payload}}`); } + /** + * Mapping template to invoke a Lambda function with all available context fields + */ + public static lambdaFullRequest(): MappingTemplate { + return this.fromString('{"version": "2017-02-28", "operation": "Invoke", "payload": $util.toJson($ctx)}'); + } + /** * Mapping template to return the Lambda result to the caller */ From a97b48ae4a7dafe050997e00bb57bf64117b551b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Sun, 16 Feb 2020 12:41:35 +0100 Subject: [PATCH 02/18] fix(lambda-nodejs): not meaningful parcel error message when build fails (#6277) * fix(lambda-nodejs): no parcel error message when build fails Parcel outputs its error message to `stdout` not `stderr`. Also refactored tests to isolate tests on the `build` function. Fixes #6274 * remove nodeVersion check Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../@aws-cdk/aws-lambda-nodejs/lib/build.ts | 2 +- .../aws-lambda-nodejs/test/build.test.ts | 54 +++++++++++++++++++ .../aws-lambda-nodejs/test/function.test.ts | 51 +++++++----------- 3 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda-nodejs/test/build.test.ts diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/build.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/build.ts index 2b9169b948f6c..e32bd1ec081f4 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/build.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/build.ts @@ -85,7 +85,7 @@ export function build(options: BuildOptions): void { } if (parcel.status !== 0) { - throw new Error(parcel.stderr.toString().trim()); + throw new Error(parcel.stdout.toString().trim()); } } catch (err) { throw new Error(`Failed to build file at ${options.entry}: ${err}`); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/build.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/build.test.ts new file mode 100644 index 0000000000000..0dea4c73e70e7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/build.test.ts @@ -0,0 +1,54 @@ +import { spawnSync } from 'child_process'; +import { build } from '../lib/build'; + +jest.mock('child_process', () => ({ + spawnSync: jest.fn((_cmd: string, args: string[]) => { + if (args[1] === 'error') { + return { error: 'parcel-error' }; + } + + if (args[1] === 'status') { + return { status: 1, stdout: Buffer.from('status-error') }; + } + + return { error: null, status: 0 }; + }) +})); + +test('calls parcel with the correct args', () => { + build({ + entry: 'entry', + global: 'handler', + outDir: 'out-dir', + cacheDir: 'cache-dir', + }); + + expect(spawnSync).toHaveBeenCalledWith(expect.stringContaining('parcel-bundler'), expect.arrayContaining([ + 'build', 'entry', + '--out-dir', 'out-dir', + '--out-file', 'index.js', + '--global', 'handler', + '--target', 'node', + '--bundle-node-modules', + '--log-level', '2', + '--no-minify', + '--no-source-maps', + '--cache-dir', 'cache-dir' + ])); +}); + +test('throws in case of error', () => { + expect(() => build({ + entry: 'error', + global: 'handler', + outDir: 'out-dir' + })).toThrow('parcel-error'); +}); + +test('throws if status is not 0', () => { + expect(() => build({ + entry: 'status', + global: 'handler', + outDir: 'out-dir' + })).toThrow('status-error'); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts index 4cbd5c203658e..ca6f19dd21760 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts @@ -4,11 +4,11 @@ import { Stack } from '@aws-cdk/core'; import * as fs from 'fs-extra'; import * as path from 'path'; import { NodejsFunction } from '../lib'; +import { build, BuildOptions } from '../lib/build'; -jest.mock('child_process', () => ({ - spawnSync: jest.fn((_cmd: string, args: string[]) => { - require('fs-extra').ensureDirSync(args[3]); // eslint-disable-line @typescript-eslint/no-require-imports - return { error: null, status: 0 }; +jest.mock('../lib/build', () => ({ + build: jest.fn((options: BuildOptions) => { + require('fs-extra').ensureDirSync(options.outDir); // eslint-disable-line @typescript-eslint/no-require-imports }) })); @@ -23,42 +23,31 @@ afterEach(() => { fs.removeSync(buildDir); }); -test('NodejsFunction', () => { +test('NodejsFunction with .ts handler', () => { // WHEN new NodejsFunction(stack, 'handler1'); - new NodejsFunction(stack, 'handler2'); - - // THEN - const { spawnSync } = require('child_process'); // eslint-disable-line @typescript-eslint/no-require-imports - expect(spawnSync).toHaveBeenCalledWith(expect.stringContaining('parcel-bundler'), expect.arrayContaining([ - 'build', - expect.stringContaining('function.test.handler1.ts'), // Automatically finds .ts handler file - '--out-dir', - expect.stringContaining(buildDir), - '--out-file', - 'index.js', - '--global', - 'handler', - '--target', - 'node', - '--bundle-node-modules', - '--log-level', - '2', - '--no-minify', - '--no-source-maps' - ])); - - // Automatically finds .js handler file - expect(spawnSync).toHaveBeenCalledWith(expect.stringContaining('parcel-bundler'), expect.arrayContaining([ - expect.stringContaining('function.test.handler2.js'), - ])); + expect(build).toHaveBeenCalledWith(expect.objectContaining({ + entry: expect.stringContaining('function.test.handler1.ts'), // Automatically finds .ts handler file + global: 'handler', + outDir: expect.stringContaining(buildDir) + })); expect(stack).toHaveResource('AWS::Lambda::Function', { Handler: 'index.handler', }); }); +test('NodejsFunction with .js handler', () => { + // WHEN + new NodejsFunction(stack, 'handler2'); + + // THEN + expect(build).toHaveBeenCalledWith(expect.objectContaining({ + entry: expect.stringContaining('function.test.handler2.js'), // Automatically finds .ts handler file + })); +}); + test('throws when entry is not js/ts', () => { expect(() => new NodejsFunction(stack, 'Fn', { entry: 'handler.py' From fe2d9868d6123379abfb351b213dfdba50fc205f Mon Sep 17 00:00:00 2001 From: Thorsten Hoeger Date: Sun, 16 Feb 2020 23:24:53 +0100 Subject: [PATCH 03/18] chore: PR review --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 73b69d491b969..09c28278cf69b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -312,7 +312,7 @@ export abstract class BaseDataSource extends Construct implements IGrantable { */ public readonly name: string; /** - * the underlying CFNN data source resource + * the underlying CFN data source resource */ public readonly ds: CfnDataSource; @@ -700,17 +700,12 @@ export abstract class MappingTemplate { /** * Mapping template to invoke a Lambda function - * @param payload the VTL template snippet of the payload to send to the lambda - */ - public static lambdaRequest(payload: string): MappingTemplate { - return this.fromString(`{"version": "2017-02-28", "operation": "Invoke", "payload": ${payload}}`); - } - - /** - * Mapping template to invoke a Lambda function with all available context fields + * + * @param payload the VTL template snippet of the payload to send to the lambda. + * If no payload is provided all available context fields are sent to the Lambda function */ - public static lambdaFullRequest(): MappingTemplate { - return this.fromString('{"version": "2017-02-28", "operation": "Invoke", "payload": $util.toJson($ctx)}'); + public static lambdaRequest(payload?: string): MappingTemplate { + return this.fromString(`{"version": "2017-02-28", "operation": "Invoke", "payload": ${payload ?? '$util.toJson($ctx)'}}`); } /** From ad5994203675a907944a2d30972e03f39c02b276 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 17 Feb 2020 17:03:23 +0100 Subject: [PATCH 04/18] docs: add example code for CloudWatch actions (#6307) Fixes #2089. --- packages/@aws-cdk/aws-cloudwatch/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/@aws-cdk/aws-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index e3484a83236fa..1d409cf098257 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -142,6 +142,22 @@ The most important properties to set while creating an Alarms are: - `evaluationPeriods`: how many consecutive periods the metric has to be breaching the the threshold for the alarm to trigger. +### Alarm Actions + +To add actions to an alarm, use the integration classes from the +`@aws-cdk/aws-cloudwatch-actions` package. For example, to post a message to +an SNS topic when an alarm breaches, do the following: + +```ts +import * as cw_actions from '@aws-cdk/aws-cloudwatch-actions'; + +// ... +const topic = new sns.Topic(stack, 'Topic'); +const alarm = new cloudwatch.Alarm(stack, 'Alarm', { /* ... */ }); + +alarm.addAlarmAction(new cw_actions.SnsAction(topic)); +``` + ### A note on units In CloudWatch, Metrics datums are emitted with units, such as `seconds` or From ea272fa270fd7dc09e0388a90e82bfb27a88491f Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Mon, 17 Feb 2020 19:11:02 +0200 Subject: [PATCH 05/18] feat(cfn): update CloudFormation spec to v11.0.0 (#6311) * updates to l1 generation tools * update cfn resource spec --- packages/@aws-cdk/aws-fms/.gitignore | 17 + packages/@aws-cdk/aws-fms/.npmignore | 20 + packages/@aws-cdk/aws-fms/LICENSE | 201 +++++++ packages/@aws-cdk/aws-fms/NOTICE | 2 + packages/@aws-cdk/aws-fms/README.md | 22 + packages/@aws-cdk/aws-fms/lib/index.ts | 2 + packages/@aws-cdk/aws-fms/package.json | 83 +++ packages/@aws-cdk/aws-fms/test/fms.test.ts | 6 + packages/@aws-cdk/cfnspec/CHANGELOG.md | 33 ++ .../build-tools/create-missing-libraries.ts | 8 +- .../cfnspec/build-tools/template/LICENSE | 2 +- .../cfnspec/build-tools/template/NOTICE | 2 +- .../@aws-cdk/cfnspec/build-tools/update.sh | 1 + ...0_CloudFormationResourceSpecification.json | 492 +++++++++++++++++- packages/decdk/deps.js | 7 +- packages/decdk/package.json | 1 + packages/monocdk-experiment/package.json | 3 +- 17 files changed, 891 insertions(+), 11 deletions(-) create mode 100644 packages/@aws-cdk/aws-fms/.gitignore create mode 100644 packages/@aws-cdk/aws-fms/.npmignore create mode 100644 packages/@aws-cdk/aws-fms/LICENSE create mode 100644 packages/@aws-cdk/aws-fms/NOTICE create mode 100644 packages/@aws-cdk/aws-fms/README.md create mode 100644 packages/@aws-cdk/aws-fms/lib/index.ts create mode 100644 packages/@aws-cdk/aws-fms/package.json create mode 100644 packages/@aws-cdk/aws-fms/test/fms.test.ts diff --git a/packages/@aws-cdk/aws-fms/.gitignore b/packages/@aws-cdk/aws-fms/.gitignore new file mode 100644 index 0000000000000..66bc512521d1f --- /dev/null +++ b/packages/@aws-cdk/aws-fms/.gitignore @@ -0,0 +1,17 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +tslint.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js diff --git a/packages/@aws-cdk/aws-fms/.npmignore b/packages/@aws-cdk/aws-fms/.npmignore new file mode 100644 index 0000000000000..c18b5f700c0c9 --- /dev/null +++ b/packages/@aws-cdk/aws-fms/.npmignore @@ -0,0 +1,20 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json diff --git a/packages/@aws-cdk/aws-fms/LICENSE b/packages/@aws-cdk/aws-fms/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-fms/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-fms/NOTICE b/packages/@aws-cdk/aws-fms/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-fms/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-fms/README.md b/packages/@aws-cdk/aws-fms/README.md new file mode 100644 index 0000000000000..9751ef6a59397 --- /dev/null +++ b/packages/@aws-cdk/aws-fms/README.md @@ -0,0 +1,22 @@ +## AWS::FMS Construct Library + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> **This is a _developer preview_ (public beta) module. Releases might lack important features and might have +> future breaking changes.** +> +> This API is still under active development and subject to non-backward +> compatible changes or removal in any future version. Use of the API is not recommended in production +> environments. Experimental APIs are not subject to the Semantic Versioning model. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import fms = require('@aws-cdk/aws-fms'); +``` diff --git a/packages/@aws-cdk/aws-fms/lib/index.ts b/packages/@aws-cdk/aws-fms/lib/index.ts new file mode 100644 index 0000000000000..26307081e095d --- /dev/null +++ b/packages/@aws-cdk/aws-fms/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::FMS CloudFormation Resources: +export * from './fms.generated'; diff --git a/packages/@aws-cdk/aws-fms/package.json b/packages/@aws-cdk/aws-fms/package.json new file mode 100644 index 0000000000000..c95ff4855d803 --- /dev/null +++ b/packages/@aws-cdk/aws-fms/package.json @@ -0,0 +1,83 @@ +{ + "name": "@aws-cdk/aws-fms", + "version": "1.24.0", + "description": "The CDK Construct Library for AWS::FMS", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.FMS", + "packageId": "Amazon.CDK.AWS.FMS", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.fms", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "fms" + } + }, + "python": { + "distName": "aws-cdk.aws-fms", + "module": "aws_cdk.aws_fms" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-fms" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "cdk-build": { + "cloudformation": "AWS::FMS" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::FMS", + "aws-fms" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "jest": {}, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "1.24.0", + "cdk-build-tools": "1.24.0", + "cfn2ts": "1.24.0", + "pkglint": "1.24.0" + }, + "dependencies": { + "@aws-cdk/core": "1.24.0" + }, + "peerDependencies": { + "@aws-cdk/core": "1.24.0" + }, + "engines": { + "node": ">= 10.3.0" + }, + "stability": "experimental" +} diff --git a/packages/@aws-cdk/aws-fms/test/fms.test.ts b/packages/@aws-cdk/aws-fms/test/fms.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-fms/test/fms.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 7f21cce74d58a..7345af3a796fe 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,36 @@ +# CloudFormation Resource Specification v11.0.0 + +## New Resource Types + +* AWS::Config::ConformancePack +* AWS::Config::OrganizationConformancePack +* AWS::EC2::LocalGatewayRoute +* AWS::EC2::LocalGatewayRouteTableVPCAssociation +* AWS::FMS::NotificationChannel +* AWS::FMS::Policy + +## Attribute Changes + + +## Property Changes + +* AWS::Neptune::DBCluster DeletionProtection (__added__) +* AWS::Neptune::DBCluster EngineVersion (__added__) + +## Property Type Changes + +* AWS::ElasticLoadBalancingV2::Listener.ForwardConfig (__added__) +* AWS::ElasticLoadBalancingV2::Listener.TargetGroupStickinessConfig (__added__) +* AWS::ElasticLoadBalancingV2::Listener.TargetGroupTuple (__added__) +* AWS::ElasticLoadBalancingV2::ListenerRule.ForwardConfig (__added__) +* AWS::ElasticLoadBalancingV2::ListenerRule.TargetGroupStickinessConfig (__added__) +* AWS::ElasticLoadBalancingV2::ListenerRule.TargetGroupTuple (__added__) +* AWS::ElasticLoadBalancingV2::Listener.Action ForwardConfig (__added__) +* AWS::ElasticLoadBalancingV2::ListenerRule.Action ForwardConfig (__added__) +* AWS::FSx::FileSystem.LustreConfiguration DeploymentType (__added__) +* AWS::FSx::FileSystem.LustreConfiguration PerUnitStorageThroughput (__added__) + + # CloudFormation Resource Specification v10.5.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index fd2085fb2aaf1..9633415b5e4d2 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -76,10 +76,7 @@ async function main() { : `${moduleFamily.toLocaleLowerCase()}-${lowcaseModuleName}`; // python names - const pythonDistSubName = moduleFamily === 'AWS' - ? lowcaseModuleName - : `${moduleFamily.toLocaleLowerCase()}.${lowcaseModuleName}`; - const pythonDistName = `aws-cdk.${pythonDistSubName}`; + const pythonDistName = `aws-cdk.${moduleName}`; const pythonModuleName = pythonDistName.replace(/-/g, "_"); async function write(relativePath: string, contents: string[] | string | object) { @@ -135,7 +132,7 @@ async function main() { repository: { type: "git", url: "https://github.com/aws/aws-cdk.git", - directory: `packages/@aws-cdk/${packageName}`, + directory: `packages/${packageName}`, }, homepage: "https://github.com/aws/aws-cdk", scripts: { @@ -204,6 +201,7 @@ async function main() { '.nycrc', '.LAST_PACKAGE', '*.snk', + 'nyc.config.js' ]); await write('.npmignore', [ diff --git a/packages/@aws-cdk/cfnspec/build-tools/template/LICENSE b/packages/@aws-cdk/cfnspec/build-tools/template/LICENSE index 1739faaebb745..b71ec1688783a 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/template/LICENSE +++ b/packages/@aws-cdk/cfnspec/build-tools/template/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/@aws-cdk/cfnspec/build-tools/template/NOTICE b/packages/@aws-cdk/cfnspec/build-tools/template/NOTICE index 95fd48569c743..bfccac9a7f69c 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/template/NOTICE +++ b/packages/@aws-cdk/cfnspec/build-tools/template/NOTICE @@ -1,2 +1,2 @@ AWS Cloud Development Kit (AWS CDK) -Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/cfnspec/build-tools/update.sh b/packages/@aws-cdk/cfnspec/build-tools/update.sh index 6aeca64980cea..8aae06c822601 100755 --- a/packages/@aws-cdk/cfnspec/build-tools/update.sh +++ b/packages/@aws-cdk/cfnspec/build-tools/update.sh @@ -67,6 +67,7 @@ node ${scriptdir}/create-missing-libraries.js || { # update decdk dep list (cd ${scriptdir}/../../../decdk && node ./deps.js || true) +(cd ${scriptdir}/../../../monocdk-experiment && node ./deps.js || true) # append old changelog after new and replace as the last step because otherwise we will not be idempotent cat CHANGELOG.md >> CHANGELOG.md.new diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 2794473f45c89..e1975a51b1890 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -7883,6 +7883,23 @@ } } }, + "AWS::Config::ConformancePack.ConformancePackInputParameter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-conformancepack-conformancepackinputparameter.html", + "Properties": { + "ParameterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-conformancepack-conformancepackinputparameter.html#cfn-config-conformancepack-conformancepackinputparameter-parametername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ParameterValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-conformancepack-conformancepackinputparameter.html#cfn-config-conformancepack-conformancepackinputparameter-parametervalue", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Config::DeliveryChannel.ConfigSnapshotDeliveryProperties": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-deliverychannel-configsnapshotdeliveryproperties.html", "Properties": { @@ -8009,6 +8026,23 @@ } } }, + "AWS::Config::OrganizationConformancePack.ConformancePackInputParameter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-organizationconformancepack-conformancepackinputparameter.html", + "Properties": { + "ParameterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-organizationconformancepack-conformancepackinputparameter.html#cfn-config-organizationconformancepack-conformancepackinputparameter-parametername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ParameterValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-organizationconformancepack-conformancepackinputparameter.html#cfn-config-organizationconformancepack-conformancepackinputparameter-parametervalue", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Config::RemediationConfiguration.ExecutionControls": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-remediationconfiguration-executioncontrols.html", "Properties": { @@ -10120,6 +10154,19 @@ } } }, + "AWS::EC2::LocalGatewayRouteTableVPCAssociation.Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-localgatewayroutetablevpcassociation-tags.html", + "Properties": { + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-localgatewayroutetablevpcassociation-tags.html#cfn-ec2-localgatewayroutetablevpcassociation-tags-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::NetworkAclEntry.Icmp": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkaclentry-icmp.html", "Properties": { @@ -13834,6 +13881,12 @@ "Type": "FixedResponseConfig", "UpdateType": "Mutable" }, + "ForwardConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-defaultactions.html#cfn-elasticloadbalancingv2-listener-action-forwardconfig", + "Required": false, + "Type": "ForwardConfig", + "UpdateType": "Mutable" + }, "Order": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-defaultactions.html#cfn-elasticloadbalancingv2-listener-action-order", "PrimitiveType": "Integer", @@ -14022,6 +14075,25 @@ } } }, + "AWS::ElasticLoadBalancingV2::Listener.ForwardConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-forwardconfig.html", + "Properties": { + "TargetGroupStickinessConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-forwardconfig.html#cfn-elasticloadbalancingv2-listener-forwardconfig-targetgroupstickinessconfig", + "Required": false, + "Type": "TargetGroupStickinessConfig", + "UpdateType": "Mutable" + }, + "TargetGroups": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-forwardconfig.html#cfn-elasticloadbalancingv2-listener-forwardconfig-targetgroups", + "DuplicatesAllowed": false, + "ItemType": "TargetGroupTuple", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::ElasticLoadBalancingV2::Listener.RedirectConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-redirectconfig.html", "Properties": { @@ -14063,6 +14135,40 @@ } } }, + "AWS::ElasticLoadBalancingV2::Listener.TargetGroupStickinessConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgroupstickinessconfig.html", + "Properties": { + "DurationSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgroupstickinessconfig.html#cfn-elasticloadbalancingv2-listener-targetgroupstickinessconfig-durationseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgroupstickinessconfig.html#cfn-elasticloadbalancingv2-listener-targetgroupstickinessconfig-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ElasticLoadBalancingV2::Listener.TargetGroupTuple": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgrouptuple.html", + "Properties": { + "TargetGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgrouptuple.html#cfn-elasticloadbalancingv2-listener-targetgrouptuple-targetgrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Weight": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-targetgrouptuple.html#cfn-elasticloadbalancingv2-listener-targetgrouptuple-weight", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ElasticLoadBalancingV2::ListenerCertificate.Certificate": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listener-certificates.html", "Properties": { @@ -14095,6 +14201,12 @@ "Type": "FixedResponseConfig", "UpdateType": "Mutable" }, + "ForwardConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-actions.html#cfn-elasticloadbalancingv2-listenerrule-action-forwardconfig", + "Required": false, + "Type": "ForwardConfig", + "UpdateType": "Mutable" + }, "Order": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-actions.html#cfn-elasticloadbalancingv2-listenerrule-action-order", "PrimitiveType": "Integer", @@ -14272,6 +14384,25 @@ } } }, + "AWS::ElasticLoadBalancingV2::ListenerRule.ForwardConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-forwardconfig.html", + "Properties": { + "TargetGroupStickinessConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-forwardconfig.html#cfn-elasticloadbalancingv2-listenerrule-forwardconfig-targetgroupstickinessconfig", + "Required": false, + "Type": "TargetGroupStickinessConfig", + "UpdateType": "Mutable" + }, + "TargetGroups": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-forwardconfig.html#cfn-elasticloadbalancingv2-listenerrule-forwardconfig-targetgroups", + "DuplicatesAllowed": false, + "ItemType": "TargetGroupTuple", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::ElasticLoadBalancingV2::ListenerRule.HostHeaderConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-hostheaderconfig.html", "Properties": { @@ -14469,6 +14600,40 @@ } } }, + "AWS::ElasticLoadBalancingV2::ListenerRule.TargetGroupStickinessConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgroupstickinessconfig.html", + "Properties": { + "DurationSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgroupstickinessconfig.html#cfn-elasticloadbalancingv2-listenerrule-targetgroupstickinessconfig-durationseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgroupstickinessconfig.html#cfn-elasticloadbalancingv2-listenerrule-targetgroupstickinessconfig-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ElasticLoadBalancingV2::ListenerRule.TargetGroupTuple": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgrouptuple.html", + "Properties": { + "TargetGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgrouptuple.html#cfn-elasticloadbalancingv2-listenerrule-targetgrouptuple-targetgrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Weight": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-targetgrouptuple.html#cfn-elasticloadbalancingv2-listenerrule-targetgrouptuple-weight", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ElasticLoadBalancingV2::LoadBalancer.LoadBalancerAttribute": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-loadbalancer-loadbalancerattributes.html", "Properties": { @@ -15095,9 +15260,61 @@ } } }, + "AWS::FMS::Policy.IEMap": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-iemap.html", + "Properties": { + "ACCOUNT": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-iemap.html#cfn-fms-policy-iemap-account", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::FMS::Policy.PolicyTag": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-policytag.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-policytag.html#cfn-fms-policy-policytag-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-policytag.html#cfn-fms-policy-policytag-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::FMS::Policy.ResourceTag": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-resourcetag.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-resourcetag.html#cfn-fms-policy-resourcetag-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fms-policy-resourcetag.html#cfn-fms-policy-resourcetag-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::FSx::FileSystem.LustreConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html", "Properties": { + "DeploymentType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-deploymenttype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "ExportPath": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-exportpath", "PrimitiveType": "String", @@ -15116,6 +15333,12 @@ "Required": false, "UpdateType": "Immutable" }, + "PerUnitStorageThroughput": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-perunitstoragethroughput", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, "WeeklyMaintenanceStartTime": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-weeklymaintenancestarttime", "PrimitiveType": "String", @@ -29931,7 +30154,7 @@ } } }, - "ResourceSpecificationVersion": "10.5.0", + "ResourceSpecificationVersion": "11.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -35988,6 +36211,48 @@ } } }, + "AWS::Config::ConformancePack": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html", + "Properties": { + "ConformancePackInputParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-conformancepackinputparameters", + "ItemType": "ConformancePackInputParameter", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ConformancePackName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-conformancepackname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DeliveryS3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-deliverys3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "DeliveryS3KeyPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-deliverys3keyprefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TemplateBody": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-templatebody", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TemplateS3Uri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-templates3uri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Config::DeliveryChannel": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-deliverychannel.html", "Properties": { @@ -36053,6 +36318,55 @@ } } }, + "AWS::Config::OrganizationConformancePack": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html", + "Properties": { + "ConformancePackInputParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-conformancepackinputparameters", + "ItemType": "ConformancePackInputParameter", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "DeliveryS3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-deliverys3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "DeliveryS3KeyPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-deliverys3keyprefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ExcludedAccounts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-excludedaccounts", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "OrganizationConformancePackName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-organizationconformancepackname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "TemplateBody": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-templatebody", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TemplateS3Uri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-templates3uri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Config::RemediationConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-remediationconfiguration.html", "Properties": { @@ -38079,6 +38393,71 @@ } } }, + "AWS::EC2::LocalGatewayRoute": { + "Attributes": { + "State": { + "PrimitiveType": "String" + }, + "Type": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroute.html", + "Properties": { + "DestinationCidrBlock": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroute.html#cfn-ec2-localgatewayroute-destinationcidrblock", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "LocalGatewayRouteTableId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroute.html#cfn-ec2-localgatewayroute-localgatewayroutetableid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "LocalGatewayVirtualInterfaceGroupId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroute.html#cfn-ec2-localgatewayroute-localgatewayvirtualinterfacegroupid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::LocalGatewayRouteTableVPCAssociation": { + "Attributes": { + "LocalGatewayId": { + "PrimitiveType": "String" + }, + "LocalGatewayRouteTableVpcAssociationId": { + "PrimitiveType": "String" + }, + "State": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroutetablevpcassociation.html", + "Properties": { + "LocalGatewayRouteTableId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroutetablevpcassociation.html#cfn-ec2-localgatewayroutetablevpcassociation-localgatewayroutetableid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroutetablevpcassociation.html#cfn-ec2-localgatewayroutetablevpcassociation-tags", + "Required": false, + "Type": "Tags", + "UpdateType": "Mutable" + }, + "VpcId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroutetablevpcassociation.html#cfn-ec2-localgatewayroutetablevpcassociation-vpcid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::NatGateway": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html", "Properties": { @@ -41929,6 +42308,105 @@ } } }, + "AWS::FMS::NotificationChannel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-notificationchannel.html", + "Properties": { + "SnsRoleName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-notificationchannel.html#cfn-fms-notificationchannel-snsrolename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SnsTopicArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-notificationchannel.html#cfn-fms-notificationchannel-snstopicarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::FMS::Policy": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html", + "Properties": { + "DeleteAllPolicyResources": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-deleteallpolicyresources", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ExcludeMap": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-excludemap", + "Required": false, + "Type": "IEMap", + "UpdateType": "Mutable" + }, + "ExcludeResourceTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-excluderesourcetags", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "IncludeMap": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-includemap", + "Required": false, + "Type": "IEMap", + "UpdateType": "Mutable" + }, + "PolicyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-policyname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RemediationEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-remediationenabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "ResourceTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-resourcetags", + "ItemType": "ResourceTag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ResourceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-resourcetype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ResourceTypeList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-resourcetypelist", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SecurityServicePolicyData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-securityservicepolicydata", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-tags", + "ItemType": "PolicyTag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::FSx::FileSystem": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html", "Properties": { @@ -46033,6 +46511,12 @@ "Required": false, "UpdateType": "Immutable" }, + "DeletionProtection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-deletionprotection", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "EnableCloudwatchLogsExports": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-enablecloudwatchlogsexports", "PrimitiveItemType": "String", @@ -46040,6 +46524,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "EngineVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-engineversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "IamAuthEnabled": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-iamauthenabled", "PrimitiveType": "Boolean", diff --git a/packages/decdk/deps.js b/packages/decdk/deps.js index b9b7850f965ad..3435c5e6a0acf 100644 --- a/packages/decdk/deps.js +++ b/packages/decdk/deps.js @@ -14,10 +14,13 @@ let errors = false; for (const dir of modules) { const module = path.resolve(root, dir); - if (!fs.existsSync(path.join(module, '.jsii'))) { + const meta = require(path.join(module, 'package.json')); + + // skip non-jsii modules + if (!meta.jsii) { continue; } - const meta = require(path.join(module, 'package.json')); + const exists = deps[meta.name]; if (meta.deprecated) { diff --git a/packages/decdk/package.json b/packages/decdk/package.json index dd54d59605b29..cc9ece81028be 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -90,6 +90,7 @@ "@aws-cdk/aws-events": "1.24.0", "@aws-cdk/aws-events-targets": "1.24.0", "@aws-cdk/aws-eventschemas": "1.24.0", + "@aws-cdk/aws-fms": "1.24.0", "@aws-cdk/aws-fsx": "1.24.0", "@aws-cdk/aws-gamelift": "1.24.0", "@aws-cdk/aws-glue": "1.24.0", diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index b6c198e0fc4e4..54f74e73abad5 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -156,7 +156,8 @@ "@aws-cdk/custom-resources": "1.24.0", "@aws-cdk/cx-api": "1.24.0", "@aws-cdk/region-info": "1.24.0", - "@types/node": "^10.17.15" + "@types/node": "^10.17.15", + "@aws-cdk/aws-fms": "1.24.0" }, "homepage": "https://github.com/aws/aws-cdk", "engines": { From c3584981b2a9a5946f70335551de03f4ee796d75 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Mon, 17 Feb 2020 09:51:11 -0800 Subject: [PATCH 06/18] chore(aws-iam): update aws-iam readme to include optional role description example (#6300) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-iam/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index ca2605963a10d..13ff25b951e71 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -87,6 +87,8 @@ permissions to trigger the expected targets, do the following: ```ts const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'), + // custom description if desired + description: 'This is a custom role...', }); new codepipeline.Pipeline(this, 'Pipeline', { From e9937d3717d07c679d7732db21231a6b4da80130 Mon Sep 17 00:00:00 2001 From: Duarte Nunes Date: Mon, 17 Feb 2020 16:21:33 -0300 Subject: [PATCH 07/18] feat(appsync): more general mapping template for DynamoDB PutItem (#6236) * feat(appsync): more general mapping template for DynamoDB PutItem Currently, the mapping template for DynamoDB PutItem doesn't support specifying a full primary key, and it doesn't support picking values to assign to attributes. This patch introduces a more general approach, which allows setting a full primary key as well as picking which values from the mutation argument to assign to table attributes. It keeps the previous convenient approach of projecting an object to a set of attribute value assignments via the `Values.projecting` function, which can then be augmented with more attributes. Example: ```ts // saveCustomerWithFirstOrder( // customer: SaveCustomerInput!, // order: FirstOrderInput!, // referral: String): Order MappingTemplate.dynamoDbPutItem( PrimaryKey .partition('order').auto() .sort('customer').is('customer.id'), Values .projecting('order') .attribute('referral').is('referral')); ``` BREAKING CHANGE: Changes `MappingTemplate.dynamoDbPutItem()` to accept `PrimaryKey` and `AttributeValues`, which allow configuring the primary key and to project an object to a set of attribute values. Fixes #6225 Signed-off-by: Duarte Nunes * test(integ.graphql.ts): add test case for dynamoDbPutItem() Signed-off-by: Duarte Nunes Co-authored-by: Mitchell Valine --- packages/@aws-cdk/aws-appsync/README.md | 31 ++- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 184 +++++++++++++++++- .../test/integ.graphql.expected.json | 27 ++- .../aws-appsync/test/integ.graphql.ts | 18 +- .../@aws-cdk/aws-appsync/test/schema.graphql | 6 + 5 files changed, 250 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index bb1bbfec6310d..56e08034ebb38 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -31,15 +31,26 @@ input SaveCustomerInput { name: String! } +type Order { + customer: String! + order: String! +} + type Query { getCustomers: [Customer] getCustomer(id: String): Customer } +input FirstOrderInput { + product: String! + quantity: Int! +} + type Mutation { addCustomer(customer: SaveCustomerInput!): Customer saveCustomer(id: String!, customer: SaveCustomerInput!): Customer removeCustomer(id: String!): Customer + saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order } ``` @@ -89,13 +100,29 @@ export class ApiStack extends Stack { customerDS.createResolver({ typeName: 'Mutation', fieldName: 'addCustomer', - requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer'), + requestMappingTemplate: MappingTemplate.dynamoDbPutItem( + PrimaryKey.partition('id').auto(), + Values.projecting('customer')), responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); customerDS.createResolver({ typeName: 'Mutation', fieldName: 'saveCustomer', - requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer', 'id'), + requestMappingTemplate: MappingTemplate.dynamoDbPutItem( + PrimaryKey.partition('id').is('id'), + Values.projecting('customer')), + responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), + }); + customerDS.createResolver({ + typeName: 'Mutation', + fieldName: 'saveCustomerWithFirstOrder', + requestMappingTemplate: MappingTemplate.dynamoDbPutItem( + PrimaryKey + .partition('order').auto() + .sort('customer').is('customer.id'), + Values + .projecting('order') + .attribute('referral').is('referral')), responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); customerDS.createResolver({ diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 62ae52d322147..eda082382bacf 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -611,6 +611,177 @@ export class KeyCondition { } } +/** + * Utility class representing the assigment of a value to an attribute. + */ +export class Assign { + constructor(private readonly attr: string, private readonly arg: string) { } + + /** + * Renders the assignment as a VTL string. + */ + public renderAsAssignment(): string { + return `"${this.attr}" : $util.dynamodb.toDynamoDBJson(${this.arg})`; + } + + /** + * Renders the assignment as a map element. + */ + public putInMap(map: string): string { + return `$util.qr($${map}.put("${this.attr}", "${this.arg}"))`; + } +} + +/** + * Utility class to allow assigning a value or an auto-generated id + * to a partition key. + */ +export class PartitionKeyStep { + constructor(private readonly key: string) { } + + /** + * Assign an auto-generated value to the partition key. + */ + public is(val: string): PartitionKey { + return new PartitionKey(new Assign(this.key, `$ctx.args.${val}`)); + } + + /** + * Assign an auto-generated value to the partition key. + */ + public auto(): PartitionKey { + return new PartitionKey(new Assign(this.key, '$util.autoId()')); + } +} + +/** + * Utility class to allow assigning a value or an auto-generated id + * to a sort key. + */ +export class SortKeyStep { + constructor(private readonly pkey: Assign, private readonly skey: string) { } + + /** + * Assign an auto-generated value to the sort key. + */ + public is(val: string): PrimaryKey { + return new PrimaryKey(this.pkey, new Assign(this.skey, `$ctx.args.${val}`)); + } + + /** + * Assign an auto-generated value to the sort key. + */ + public auto(): PrimaryKey { + return new PrimaryKey(this.pkey, new Assign(this.skey, '$util.autoId()')); + } +} + +/** + * Specifies the assignment to the primary key. It either + * contains the full primary key or only the partition key. + */ +export class PrimaryKey { + /** + * Allows assigning a value to the partition key. + */ + public static partition(key: string): PartitionKeyStep { + return new PartitionKeyStep(key); + } + + constructor(protected readonly pkey: Assign, private readonly skey?: Assign) { } + + /** + * Renders the key assignment to a VTL string. + */ + public renderTemplate(): string { + const assignments = [this.pkey.renderAsAssignment()]; + if (this.skey) { + assignments.push(this.skey.renderAsAssignment()); + } + return `"key" : { + ${assignments.join(",")} + }`; + } +} + +/** + * Specifies the assignment to the partition key. It can be + * enhanced with the assignment of the sort key. + */ +export class PartitionKey extends PrimaryKey { + constructor(pkey: Assign) { + super(pkey); + } + + /** + * Allows assigning a value to the sort key. + */ + public sort(key: string): SortKeyStep { + return new SortKeyStep(this.pkey, key); + } +} + +/** + * Specifies the attribute value assignments. + */ +export class AttributeValues { + constructor(private readonly container: string, private readonly assignments: Assign[] = []) { } + + /** + * Allows assigning a value to the specified attribute. + */ + public attribute(attr: string): AttributeValuesStep { + return new AttributeValuesStep(attr, this.container, this.assignments); + } + + /** + * Renders the attribute value assingments to a VTL string. + */ + public renderTemplate(): string { + return ` + #set($input = ${this.container}) + ${this.assignments.map(a => a.putInMap("input")).join("\n")} + "attributeValues": $util.dynamodb.toMapValuesJson($input)`; + } +} + +/** + * Utility class to allow assigning a value to an attribute. + */ +export class AttributeValuesStep { + constructor(private readonly attr: string, private readonly container: string, private readonly assignments: Assign[]) { } + + /** + * Assign the value to the current attribute. + */ + public is(val: string): AttributeValues { + this.assignments.push(new Assign(this.attr, val)); + return new AttributeValues(this.container, this.assignments); + } +} + +/** + * Factory class for attribute value assignments. + */ +export class Values { + /** + * Treats the specified object as a map of assignments, where the property + * names represent attribute names. It’s opinionated about how it represents + * some of the nested objects: e.g., it will use lists (“L”) rather than sets + * (“SS”, “NS”, “BS”). By default it projects the argument container ("$ctx.args"). + */ + public static projecting(arg?: string): AttributeValues { + return new AttributeValues('$ctx.args' + (arg ? `.${arg}` : '')); + } + + /** + * Allows assigning a value to the specified attribute. + */ + public static attribute(attr: string): AttributeValuesStep { + return new AttributeValues('{}').attribute(attr); + } +} + /** * MappingTemplates for AppSync resolvers */ @@ -683,18 +854,15 @@ export abstract class MappingTemplate { /** * Mapping template to save a single item to a DynamoDB table * - * @param keyName the name of the hash key field - * @param valueArg the name of the Mutation argument to use as attributes. By default it uses all arguments - * @param idArg the name of the Mutation argument to use as id value. By default it generates a new id + * @param key the assigment of Mutation values to the primary key + * @param values the assignment of Mutation values to the table attributes */ - public static dynamoDbPutItem(keyName: string, valueArg?: string, idArg?: string): MappingTemplate { + public static dynamoDbPutItem(key: PrimaryKey, values: AttributeValues): MappingTemplate { return this.fromString(`{ "version" : "2017-02-28", "operation" : "PutItem", - "key" : { - "${keyName}": $util.dynamodb.toDynamoDBJson(${idArg ? `$ctx.args.${idArg}` : '$util.autoId()'}), - }, - "attributeValues" : $util.dynamodb.toMapValuesJson(${valueArg ? `$ctx.args.${valueArg}` : '$ctx.args'}) + ${key.renderTemplate()}, + ${values.renderTemplate()} }`); } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index f5abb4ac28966..1fad1ba0f55a6 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -16,7 +16,7 @@ "ApiId" ] }, - "Definition": "type Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n}" + "Definition": "type Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n}" } }, "ApiCustomerDSServiceRoleA929BCF7": { @@ -161,7 +161,7 @@ "TypeName": "Mutation", "DataSourceName": "Customer", "Kind": "UNIT", - "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\": $util.dynamodb.toDynamoDBJson($util.autoId()),\n },\n \"attributeValues\" : $util.dynamodb.toMapValuesJson($ctx.args.customer)\n }", + "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ @@ -182,7 +182,28 @@ "TypeName": "Mutation", "DataSourceName": "Customer", "Kind": "UNIT", - "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id),\n },\n \"attributeValues\" : $util.dynamodb.toMapValuesJson($ctx.args.customer)\n }", + "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($ctx.args.id)\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "ResponseMappingTemplate": "$util.toJson($ctx.result)" + }, + "DependsOn": [ + "ApiCustomerDS8C23CB2D", + "ApiSchema510EECD7" + ] + }, + "ApiCustomerDSMutationsaveCustomerWithFirstOrderResolver8B1277A8": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "saveCustomerWithFirstOrder", + "TypeName": "Mutation", + "DataSourceName": "Customer", + "Kind": "UNIT", + "RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"order\" : $util.dynamodb.toDynamoDBJson($util.autoId()),\"customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer.id)\n },\n \n #set($input = $ctx.args.order)\n $util.qr($input.put(\"referral\", \"referral\"))\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index d4d87141f114b..a3d1cee096029 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -1,7 +1,7 @@ import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb'; import { App, Stack } from '@aws-cdk/core'; import { join } from 'path'; -import { GraphQLApi, KeyCondition, MappingTemplate } from '../lib'; +import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, Values } from '../lib'; const app = new App(); const stack = new Stack(app, 'aws-appsync-integ'); @@ -48,13 +48,25 @@ customerDS.createResolver({ customerDS.createResolver({ typeName: 'Mutation', fieldName: 'addCustomer', - requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer'), + requestMappingTemplate: MappingTemplate.dynamoDbPutItem(PrimaryKey.partition('id').auto(), Values.projecting('customer')), responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); customerDS.createResolver({ typeName: 'Mutation', fieldName: 'saveCustomer', - requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer', 'id'), + requestMappingTemplate: MappingTemplate.dynamoDbPutItem(PrimaryKey.partition('id').is('id'), Values.projecting('customer')), + responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), +}); +customerDS.createResolver({ + typeName: 'Mutation', + fieldName: 'saveCustomerWithFirstOrder', + requestMappingTemplate: MappingTemplate.dynamoDbPutItem( + PrimaryKey + .partition('order').auto() + .sort('customer').is('customer.id'), + Values + .projecting('order') + .attribute('referral').is('referral')), responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); customerDS.createResolver({ diff --git a/packages/@aws-cdk/aws-appsync/test/schema.graphql b/packages/@aws-cdk/aws-appsync/test/schema.graphql index 86e8371d3c3b9..14d6497cb6a2d 100644 --- a/packages/@aws-cdk/aws-appsync/test/schema.graphql +++ b/packages/@aws-cdk/aws-appsync/test/schema.graphql @@ -24,8 +24,14 @@ type Query { getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order } +input FirstOrderInput { + product: String! + quantity: Int! +} + type Mutation { addCustomer(customer: SaveCustomerInput!): Customer saveCustomer(id: String!, customer: SaveCustomerInput!): Customer removeCustomer(id: String!): Customer + saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order } \ No newline at end of file From 7fcbc48adec5b4e883aafdd29a388c21077a3432 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Feb 2020 21:04:11 +0100 Subject: [PATCH 08/18] chore(dynamodb): deploy time region check for replica (#6306) Add a deploy time region check for environment agnostic stacks to prevent from creating replica in the region where the stack is deployed. Closes #6287 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 15 +++- .../aws-dynamodb/test/test.dynamodb.ts | 75 +++++++++++++++++-- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index f9cd8535a9d86..bfe9d69b5f85b 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1,8 +1,8 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; -import { CustomResource } from '@aws-cdk/aws-cloudformation'; +import { CfnCustomResource, CustomResource } from '@aws-cdk/aws-cloudformation'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { Aws, Construct, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Aws, CfnCondition, Construct, Fn, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { CfnTable } from './dynamodb.generated'; import { ReplicaProvider } from './replica-provider'; import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api'; @@ -1058,6 +1058,17 @@ export class Table extends TableBase { } }); + // Deploy time check to prevent from creating a replica in the region + // where this stack is deployed. Only needed for environment agnostic + // stacks. + if (Token.isUnresolved(stack.region)) { + const createReplica = new CfnCondition(this, `StackRegionNotEquals${region}`, { + expression: Fn.conditionNot(Fn.conditionEquals(region, Aws.REGION)) + }); + const cfnCustomResource = currentRegion.node.defaultChild as CfnCustomResource; + cfnCustomResource.cfnOptions.condition = createReplica; + } + // We need to create/delete regions sequentially because we cannot // have multiple table updates at the same time. The `isCompleteHandler` // of the provider waits until the replica is an ACTIVE state. diff --git a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts index 3b7a5f91153e2..dd4fc1c30d54d 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as iam from '@aws-cdk/aws-iam'; import { App, CfnDeletionPolicy, ConstructNode, RemovalPolicy, Stack, Tag } from '@aws-cdk/core'; @@ -1589,11 +1589,49 @@ export = { // THEN expect(stack).to(haveResource('Custom::DynamoDBReplica', { - Region: 'eu-west-2' - })); + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D', + 'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn' + ] + }, + TableName: { + Ref: 'TableCD117FA1' + }, + Region: 'eu-west-2' + }, + Condition: 'TableStackRegionNotEqualseuwest2A03859E7' + }, ResourcePart.CompleteDefinition)); + expect(stack).to(haveResource('Custom::DynamoDBReplica', { - Region: 'eu-central-1' - })); + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D', + 'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn' + ] + }, + TableName: { + Ref: 'TableCD117FA1' + }, + Region: 'eu-central-1' + }, + Condition: 'TableStackRegionNotEqualseucentral199D46FC0' + }, ResourcePart.CompleteDefinition)); + + test.deepEqual(SynthUtils.toCloudFormation(stack).Conditions, { + TableStackRegionNotEqualseuwest2A03859E7: { + 'Fn::Not': [ + { 'Fn::Equals': [ 'eu-west-2', { Ref: 'AWS::Region' } ] } + ] + }, + TableStackRegionNotEqualseucentral199D46FC0: { + 'Fn::Not': [ + { 'Fn::Equals': [ 'eu-central-1', { Ref: 'AWS::Region' } ] } + ] + } + }); test.done(); }, @@ -1659,7 +1697,32 @@ export = { }), /`replicationRegions` cannot include the region where this stack is deployed/); test.done(); - } + }, + + 'no conditions when region is known'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Stack', { + env: { region: 'eu-west-1' } + }); + + // WHEN + new Table(stack, 'Table', { + partitionKey: { + name: 'id', + type: AttributeType.STRING + }, + replicationRegions: [ + 'eu-west-2', + 'eu-central-1' + ], + }); + + // THEN + test.equal(SynthUtils.toCloudFormation(stack).Conditions, undefined); + + test.done(); + }, } }; From de48222528cb95395732135367d8ee9c6544072a Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 17 Feb 2020 13:24:41 -0800 Subject: [PATCH 09/18] chore(cli): implementation of the new bootstrap logic (#5856) This adds a new `cdk bootstrap` mode, hidden behind the environment variable CDK_NEW_BOOTSTRAP, that adds the necessary resources (ECR repository, IAM roles, etc.) for our "CI/CD for CDK apps" epic, as well as 2 new (hidden for now) options: --trust (for specifying the accounts that are trusted to deploy into this environment) and --cloudformation-execution-policies (for specifying the ARNs of the Managed Policis that will be attached to the CFN deploying role). --- design/cdk-bootstrap.md | 312 +------------- packages/aws-cdk/bin/cdk.ts | 16 +- .../aws-cdk/lib/api/bootstrap-environment.ts | 22 + .../api/bootstrap/bootstrap-environment2.ts | 44 ++ .../lib/api/bootstrap/bootstrap-template.json | 408 ++++++++++++++++++ packages/aws-cdk/lib/api/deploy-stack.ts | 26 +- packages/aws-cdk/package.json | 2 + packages/aws-cdk/test/api/bootstrap.test.ts | 50 ++- packages/aws-cdk/test/api/bootstrap2.test.ts | 53 +++ .../aws-cdk/test/api/deploy-stack.test.ts | 47 ++ packages/aws-cdk/test/cdk-toolkit.test.ts | 1 + packages/aws-cdk/test/diff.test.ts | 12 +- .../integ/bootstrap/bootstrap.integ-test.ts | 150 +++++++ .../bootstrap/example-cdk-app/.gitignore | 2 + .../example-cdk-app/assets/asset1.txt | 1 + .../example-cdk-app/assets/asset2.txt | 3 + .../example-cdk-app/my-test-cdk-stack.d.ts | 14 + .../example-cdk-app/my-test-cdk-stack.js | 34 ++ .../aws-cdk/test/integ/cli/test-npm-integ.sh | 8 + 19 files changed, 863 insertions(+), 342 deletions(-) create mode 100644 packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/bootstrap-template.json create mode 100644 packages/aws-cdk/test/api/bootstrap2.test.ts create mode 100644 packages/aws-cdk/test/integ/bootstrap/bootstrap.integ-test.ts create mode 100644 packages/aws-cdk/test/integ/bootstrap/example-cdk-app/.gitignore create mode 100644 packages/aws-cdk/test/integ/bootstrap/example-cdk-app/assets/asset1.txt create mode 100644 packages/aws-cdk/test/integ/bootstrap/example-cdk-app/assets/asset2.txt create mode 100644 packages/aws-cdk/test/integ/bootstrap/example-cdk-app/my-test-cdk-stack.d.ts create mode 100644 packages/aws-cdk/test/integ/bootstrap/example-cdk-app/my-test-cdk-stack.js create mode 100755 packages/aws-cdk/test/integ/cli/test-npm-integ.sh diff --git a/design/cdk-bootstrap.md b/design/cdk-bootstrap.md index 794abef43c14f..ddabe43b43caa 100644 --- a/design/cdk-bootstrap.md +++ b/design/cdk-bootstrap.md @@ -337,313 +337,5 @@ This should make sure the CFN update succeeds. ## Bootstrap template -Here is the JSON of the bootstrap CloudFormation template: - -```json -{ - "Description": "The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.", - "Parameters": { - "TrustedPrincipals": { - "Description": "List of AWS principals that the publish and action roles should trust to be assumed from", - "Default": "", - "Type": "CommaDelimitedList" - }, - "CloudFormationExecutionPolicies": { - "Description": "List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role", - "Default": "", - "Type": "CommaDelimitedList" - } - }, - "Conditions": { - "HasTrustedPrincipals": { - "Fn::Not": [ - { - "Fn::Equals": [ - "", - { - "Fn::Join": [ - "", - { - "Ref": "TrustedPrincipals" - } - ] - } - ] - } - ] - } - }, - "Resources": { - "FileAssetsBucketEncryptionKey": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", - "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", - "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion", "kms:GenerateDataKey" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" - } - }, - "Resource": "*" - }, - { - "Action": [ - "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", - "kms:ReEncrypt*", "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "${PublishingRole.Arn}" - } - }, - "Resource": "*" - } - ] - } - } - }, - "StagingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" - }, - "AccessControl": "Private", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [{ - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "aws:kms", - "KMSMasterKeyID": { - "Fn::Sub": "${FileAssetsBucketEncryptionKey.Arn}" - } - } - }] - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true - } - }, - "UpdateReplacePolicy": "Retain" - }, - "ContainerAssetsRepository": { - "Type": "AWS::ECR::Repository", - "Properties": { - "RepositoryName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}" - } - } - }, - "PublishingRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Ref": "AWS::AccountId" - } - } - }, - { - "Fn::If": [ - "HasTrustedPrincipals", - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Ref": "TrustedPrincipals" - } - } - }, - { - "Ref": "AWS::NoValue" - } - ] - } - ] - }, - "RoleName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-publishing-role-${AWS::AccountId}-${AWS::Region}" - } - } - }, - "PublishingRoleDefaultPolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", "s3:GetBucket*", "s3:List*", - "s3:DeleteObject*", "s3:PutObject*", "s3:Abort*" - ], - "Resource": [ - { - "Fn::Sub": "${StagingBucket.Arn}" - }, - { - "Fn::Sub": "${StagingBucket.Arn}/*" - } - ] - }, - { - "Action": [ - "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", - "kms:ReEncrypt*", "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Resource": { - "Fn::Sub": "${FileAssetsBucketEncryptionKey.Arn}" - } - }, - { - "Action": [ - "ecr:PutImage", "ecr:InitiateLayerUpload", - "ecr:UploadLayerPart", "ecr:CompleteLayerUpload" - ], - "Resource": { - "Fn::Sub": "${ContainerAssetsRepository.Arn}" - } - } - ], - "Version": "2012-10-17" - }, - "Roles": [{ - "Ref": "PublishingRole" - }], - "PolicyName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region}" - } - } - }, - "DeploymentActionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Ref": "AWS::AccountId" - } - } - }, - { - "Fn::If": [ - "HasTrustedPrincipals", - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Ref": "TrustedPrincipals" - } - } - }, - { - "Ref": "AWS::NoValue" - } - ] - } - ] - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "cloudformation:CreateChangeSet", "cloudformation:DeleteChangeSet", - "cloudformation:DescribeChangeSet", "cloudformation:DescribeStacks", - "cloudformation:ExecuteChangeSet", - "s3:GetObject*", "s3:GetBucket*", - "s3:List*", "s3:Abort*", - "s3:DeleteObject*", "s3:PutObject*", - "kms:Decrypt", "kms:DescribeKey" - ], - "Resource": "*" - }, - { - "Action": "iam:PassRole", - "Resource": { - "Fn::Sub": "${CloudFormationExecutionRole.Arn}" - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "default" - } - ], - "RoleName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-deployment-action-role-${AWS::AccountId}-${AWS::Region}" - }, - "Condition": "HasTrustedPrincipals" - } - }, - "CloudFormationExecutionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com" - } - } - ] - }, - "ManagedPolicyArns": { - "Ref": "CloudFormationExecutionPolicies" - }, - "RoleName": { - "Fn::Sub": "cdk-bootstrap-hnb659fds-cloudformation-execution-role-${AWS::AccountId}-${AWS::Region}" - }, - "Condition": "HasTrustedPrincipals" - } - } - }, - "Outputs": { - "BucketName": { - "Description": "The name of the S3 bucket owned by the CDK toolkit stack", - "Value": { "Fn::Sub": "${StagingBucket.Arn}" }, - "Export": { - "Name": { "Fn::Sub": "${AWS::StackName}:BucketName" } - } - }, - "BucketDomainName": { - "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", - "Value": { "Fn::Sub": "${StagingBucket.RegionalDomainName}" }, - "Export": { - "Name": { "Fn::Sub": "${AWS::StackName}:BucketDomainName" } - } - }, - "BootstrapVersion": { - "Description": "The version of the bootstrap resources that are currently mastered in this stack", - "Value": "1", - "Export": { - "Name": { "Fn::Sub": "AwsCdkBootstrapVersion" } - } - } - } -} -``` +The bootstrap template used by the CLI command can be found in the +[aws-cdk package](../packages/aws-cdk/lib/api/bootstrap/bootstrap-template.json). diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 424199286e29a..9739c234e98f7 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as yargs from 'yargs'; import { bootstrapEnvironment, BootstrapEnvironmentProps, SDK } from '../lib'; +import { bootstrapEnvironment2 } from '../lib/api/bootstrap/bootstrap-environment2'; import { environmentsFromDescriptors, globEnvironmentsFromStacks } from '../lib/api/cxapp/environments'; import { execProgram } from '../lib/api/cxapp/exec'; import { AppStacks, DefaultSelection, ExtendedStackSelection } from '../lib/api/cxapp/stacks'; @@ -56,6 +57,8 @@ async function parseCommandLineArguments() { .option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined }) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true}) + .option('trust', { type: 'array', desc: 'The (space-separated) list of AWS account IDs that should be trusted to perform deployments into this environment', default: [], hidden: true }) + .option('cloudformation-execution-policies', { type: 'array', desc: 'The (space-separated) list of Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed', default: [], hidden: true }) ) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times.', default: [] }) @@ -201,11 +204,13 @@ async function initCommandLine() { }); case 'bootstrap': - return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, { + return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, !!process.env.CDK_NEW_BOOTSTRAP, { bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']), tags: configuration.settings.get(['tags']), - execute: args.execute + execute: args.execute, + trustedAccounts: args.trust, + cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies, }); case 'deploy': @@ -266,7 +271,8 @@ async function initCommandLine() { * all stacks are implicitly selected. * @param toolkitStackName the name to be used for the CDK Toolkit stack. */ - async function cliBootstrap(environmentGlobs: string[], toolkitStackName: string, roleArn: string | undefined, props: BootstrapEnvironmentProps): Promise { + async function cliBootstrap(environmentGlobs: string[], toolkitStackName: string, roleArn: string | undefined, + useNewBootstrapping: boolean, props: BootstrapEnvironmentProps): Promise { // Two modes of operation. // // If there is an '--app' argument, we select the environments from the app. Otherwise we just take the user @@ -279,7 +285,9 @@ async function initCommandLine() { await Promise.all(environments.map(async (environment) => { success(' ⏳ Bootstrapping environment %s...', colors.blue(environment.name)); try { - const result = await bootstrapEnvironment(environment, aws, toolkitStackName, roleArn, props); + const result = useNewBootstrapping + ? await bootstrapEnvironment2(environment, aws, toolkitStackName, roleArn, props) + : await bootstrapEnvironment(environment, aws, toolkitStackName, roleArn, props); const message = result.noOp ? ' ✅ Environment %s bootstrapped (no changes).' : ' ✅ Environment %s bootstrapped.'; success(message, colors.blue(environment.name)); diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts index 0d7980ae1a2d5..e1272e8e6ddc7 100644 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap-environment.ts @@ -38,10 +38,32 @@ export interface BootstrapEnvironmentProps { * @default true */ readonly execute?: boolean; + + /** + * The list of AWS account IDs that are trusted to deploy into the environment being bootstrapped. + * + * @default - only the bootstrapped account can deploy into this environment + */ + readonly trustedAccounts?: string[]; + + /** + * The ARNs of the IAM managed policies that should be attached to the role performing CloudFormation deployments. + * In most cases, this will be the AdministratorAccess policy. + * At least one policy is required if {@link trustedAccounts} were passed. + * + * @default - the role will have no policies attached + */ + readonly cloudFormationExecutionPolicies?: string[]; } /** @experimental */ export async function bootstrapEnvironment(environment: cxapi.Environment, aws: ISDK, toolkitStackName: string, roleArn: string | undefined, props: BootstrapEnvironmentProps = {}): Promise { + if (props.trustedAccounts?.length) { + throw new Error('--trust can only be passed for the new bootstrap experience!'); + } + if (props.cloudFormationExecutionPolicies?.length) { + throw new Error('--cloudformation-execution-policies can only be passed for the new bootstrap experience!'); + } const template = { Description: "The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.", diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts new file mode 100644 index 0000000000000..596450cd16939 --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts @@ -0,0 +1,44 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; +import { BootstrapEnvironmentProps, deployStack, DeployStackResult, ISDK } from '..'; + +export async function bootstrapEnvironment2(environment: cxapi.Environment, sdk: ISDK, + toolkitStackName: string, roleArn: string | undefined, + props: BootstrapEnvironmentProps = {}): Promise { + if (props.trustedAccounts?.length && !props.cloudFormationExecutionPolicies?.length) { + throw new Error('--cloudformation-execution-policies are required if --trust has been passed!'); + } + + const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap-new')); + const builder = new cxapi.CloudAssemblyBuilder(outdir); + const templateFile = `${toolkitStackName}.template.json`; + + await fs.copy( + path.join(__dirname, 'bootstrap-template.json'), + path.join(builder.outdir, templateFile)); + + builder.addArtifact(toolkitStackName, { + type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), + properties: { + templateFile, + }, + }); + + const assembly = builder.buildAssembly(); + return await deployStack({ + stack: assembly.getStackByName(toolkitStackName), + sdk, + roleArn, + tags: props.tags, + execute: props.execute, + parameters: { + FileAssetsBucketName: props.bucketName, + FileAssetsBucketKmsKeyId: props.kmsKeyId, + TrustedAccounts: props.trustedAccounts?.join(','), + CloudFormationExecutionPolicies: props.cloudFormationExecutionPolicies?.join(','), + }, + }); +} diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.json b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.json new file mode 100644 index 0000000000000..1a448254a0cfd --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.json @@ -0,0 +1,408 @@ +{ + "Description": "This stack includes resources needed to deploy AWS CDK apps into this environment", + "Parameters": { + "TrustedAccounts": { + "Description": "List of AWS accounts that are trusted to publish assets and deploy stacks to this environment", + "Default": "", + "Type": "CommaDelimitedList" + }, + "CloudFormationExecutionPolicies": { + "Description": "List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role", + "Default": "", + "Type": "CommaDelimitedList" + }, + "FileAssetsBucketName": { + "Description": "The name of the S3 bucket used for file assets", + "Default": "", + "Type": "String" + }, + "FileAssetsBucketKmsKeyId": { + "Description": "Custom KMS key ID to use for encrypting file assets (by default a KMS key will be automatically defined)", + "Default": "", + "Type": "String" + }, + "ContainerAssetsRepositoryName": { + "Description": "A user-provided custom name to use for the container assets ECR repository", + "Default": "", + "Type": "String" + } + }, + "Conditions": { + "HasTrustedAccounts": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Fn::Join": [ + "", + { + "Ref": "TrustedAccounts" + } + ] + } + ] + } + ] + }, + "HasCloudFormationExecutionPolicies": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Fn::Join": [ + "", + { + "Ref": "CloudFormationExecutionPolicies" + } + ] + } + ] + } + ] + }, + "HasCustomFileAssetsBucketName": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "FileAssetsBucketName" + } + ] + } + ] + }, + "CreateNewKey": { + "Fn::Equals": [ + "", + { + "Ref": "FileAssetsBucketKmsKeyId" + } + ] + }, + "HasCustomContainerAssetsRepositoryName": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "ContainerAssetsRepositoryName" + } + ] + } + ] + } + }, + "Resources": { + "FileAssetsBucketEncryptionKey": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", + "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", + "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion", "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "AWS::AccountId" + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", + "kms:ReEncrypt*", "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "AWS::AccountId" + } + }, + "Resource": "*", + "Condition": { + "StringEquals": { + "kms:ViaService": [ + { "Fn::Sub": "s3.${AWS::Region}.amazonaws.com" } + ] + } + } + }, + { + "Action": [ + "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", + "kms:ReEncrypt*", "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Sub": "${PublishingRole.Arn}" + } + }, + "Resource": "*" + } + ] + } + }, + "Condition": "CreateNewKey" + }, + "StagingBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "HasCustomFileAssetsBucketName", + { "Fn::Sub": "${FileAssetsBucketName}" }, + { "Fn::Sub": "cdk-bootstrap-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" } + ] + }, + "AccessControl": "Private", + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [{ + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms", + "KMSMasterKeyID": { + "Fn::If": [ + "CreateNewKey", + { "Fn::Sub": "${FileAssetsBucketEncryptionKey.Arn}" }, + { "Fn::Sub": "${FileAssetsBucketKmsKeyId}" } + ] + } + } + }] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain" + }, + "ContainerAssetsRepository": { + "Type": "AWS::ECR::Repository", + "Properties": { + "RepositoryName": { + "Fn::If": [ + "HasCustomContainerAssetsRepositoryName", + { "Fn::Sub": "${ContainerAssetsRepositoryName}" }, + { "Fn::Sub": "cdk-bootstrap-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}" } + ] + } + } + }, + "PublishingRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "AWS::AccountId" + } + } + }, + { + "Fn::If": [ + "HasTrustedAccounts", + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "TrustedAccounts" + } + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + }, + "RoleName": { + "Fn::Sub": "cdk-bootstrap-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "PublishingRoleDefaultPolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", "s3:GetBucket*", "s3:List*", + "s3:DeleteObject*", "s3:PutObject*", "s3:Abort*" + ], + "Resource": [ + { + "Fn::Sub": "${StagingBucket.Arn}" + }, + { + "Fn::Sub": "${StagingBucket.Arn}/*" + } + ], + "Effect": "Allow" + }, + { + "Action": [ + "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", + "kms:ReEncrypt*", "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::If": [ + "CreateNewKey", + { "Fn::Sub": "${FileAssetsBucketEncryptionKey.Arn}" }, + { "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId}" } + ] + } + }, + { + "Action": [ + "ecr:PutImage", "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", "ecr:CompleteLayerUpload" + ], + "Resource": { + "Fn::Sub": "${ContainerAssetsRepository.Arn}" + }, + "Effect": "Allow" + } + ], + "Version": "2012-10-17" + }, + "Roles": [ + { "Ref": "PublishingRole" } + ], + "PolicyName": { + "Fn::Sub": "cdk-bootstrap-hnb659fds-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "DeploymentActionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "AWS::AccountId" + } + } + }, + { + "Fn::If": [ + "HasTrustedAccounts", + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Ref": "TrustedAccounts" + } + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudformation:CreateChangeSet", "cloudformation:DeleteChangeSet", + "cloudformation:DescribeChangeSet", "cloudformation:DescribeStacks", + "cloudformation:ExecuteChangeSet", + "s3:GetObject*", "s3:GetBucket*", + "s3:List*", "s3:Abort*", + "s3:DeleteObject*", "s3:PutObject*", + "kms:Decrypt", "kms:DescribeKey" + ], + "Resource": "*", + "Effect": "Allow" + }, + { + "Action": "iam:PassRole", + "Resource": { + "Fn::Sub": "${CloudFormationExecutionRole.Arn}" + }, + "Effect": "Allow" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "default" + } + ], + "RoleName": { + "Fn::Sub": "cdk-bootstrap-deploy-action-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "CloudFormationExecutionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": { + "Fn::If": [ + "HasCloudFormationExecutionPolicies", + { "Ref": "CloudFormationExecutionPolicies" }, + { "Ref": "AWS::NoValue" } + ] + }, + "RoleName": { + "Fn::Sub": "cdk-bootstrap-cfn-exec-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "Outputs": { + "BucketName": { + "Description": "The name of the S3 bucket owned by the CDK toolkit stack", + "Value": { "Fn::Sub": "${StagingBucket}" } + }, + "BucketDomainName": { + "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", + "Value": { "Fn::Sub": "${StagingBucket.RegionalDomainName}" } + }, + "BootstrapVersion": { + "Description": "The version of the bootstrap resources that are currently mastered in this stack", + "Value": "1", + "Export": { + "Name": { "Fn::Sub": "AwsCdkBootstrapVersion" } + } + } + } +} diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index a9fcd8cff4763..d730ea84eb725 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -23,6 +23,7 @@ export interface DeployStackResult { readonly noOp: boolean; readonly outputs: { [name: string]: string }; readonly stackArn: string; + readonly stackArtifact: cxapi.CloudFormationStackArtifact; } /** @experimental */ @@ -42,6 +43,17 @@ export interface DeployStackOptions { * @default true */ execute?: boolean; + + /** + * The collection of extra parameters + * (in addition to those used for assets) + * to pass to the deployed template. + * Note that parameters with `undefined` or empty values will be ignored, + * and not passed to the template. + * + * @default - no additional parameters will be passed to the template + */ + parameters?: { [name: string]: string | undefined }; } const LARGE_TEMPLATE_SIZE_KB = 50; @@ -54,6 +66,16 @@ export async function deployStack(options: DeployStackOptions): Promise { // GIVEN const sdk = new MockSDK(); @@ -39,11 +45,7 @@ test('do bootstrap', async () => { }); // WHEN - const ret = await bootstrapEnvironment({ - account: '123456789012', - region: 'us-east-1', - name: 'mock', - }, sdk, 'mockStack', undefined); + const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined); // THEN expect(ret.noOp).toBeFalsy(); @@ -86,11 +88,7 @@ test('do bootstrap using custom bucket name', async () => { }); // WHEN - const ret = await bootstrapEnvironment({ - account: '123456789012', - region: 'us-east-1', - name: 'mock', - }, sdk, 'mockStack', undefined, { + const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { bucketName: 'foobar', }); @@ -134,11 +132,7 @@ test('do bootstrap using KMS CMK', async () => { }); // WHEN - const ret = await bootstrapEnvironment({ - account: '123456789012', - region: 'us-east-1', - name: 'mock', - }, sdk, 'mockStack', undefined, { + const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { kmsKeyId: 'myKmsKey', }); @@ -182,11 +176,7 @@ test('do bootstrap with custom tags for toolkit stack', async () => { }); // WHEN - const ret = await bootstrapEnvironment({ - account: '123456789012', - region: 'us-east-1', - name: 'mock', - }, sdk, 'mockStack', undefined, { + const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { tags: [{ Key: 'Foo', Value: 'Bar' }] }); @@ -194,3 +184,23 @@ test('do bootstrap with custom tags for toolkit stack', async () => { expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); + +test('passing trusted accounts to the old bootstrapping results in an error', async () => { + const sdk = new MockSDK(); + + await expect(bootstrapEnvironment(env, sdk, 'mockStack', undefined, { + trustedAccounts: ['0123456789012'], + })) + .rejects + .toThrow('--trust can only be passed for the new bootstrap experience!'); +}); + +test('passing CFN execution policies to the old bootstrapping results in an error', async () => { + const sdk = new MockSDK(); + + await expect(bootstrapEnvironment(env, sdk, 'mockStack', undefined, { + cloudFormationExecutionPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], + })) + .rejects + .toThrow('--cloudformation-execution-policies can only be passed for the new bootstrap experience!'); +}); diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts new file mode 100644 index 0000000000000..ddf0458b78fc5 --- /dev/null +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -0,0 +1,53 @@ +const mockDeployStack = jest.fn(); + +jest.mock('../../lib/api/deploy-stack', () => ({ + deployStack: mockDeployStack, +})); + +import { bootstrapEnvironment2 } from '../../lib/api/bootstrap/bootstrap-environment2'; +import { MockSDK } from '../util/mock-sdk'; + +describe('Bootstrapping v2', () => { + const env = { + account: '123456789012', + region: 'us-east-1', + name: 'mock', + }; + const sdk = new MockSDK(); + + test('passes the bucket name as a CFN parameter', async () => { + await bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { + bucketName: 'my-bucket-name', + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: { + FileAssetsBucketName: 'my-bucket-name', + }, + })); + }); + + test('passes the KMS key ID as a CFN parameter', async () => { + await bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { + kmsKeyId: 'my-kms-key-id', + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: { + FileAssetsBucketKmsKeyId: 'my-kms-key-id', + }, + })); + }); + + test('passing trusted accounts without CFN managed policies results in an error', async () => { + await expect(bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { + trustedAccounts: ['123456789012'], + })) + .rejects + .toThrow('--cloudformation-execution-policies are required if --trust has been passed!'); + }); + + afterEach(() => { + mockDeployStack.mockClear(); + }); +}); diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index a52ba36702eae..ab3233358e229 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -47,3 +47,50 @@ test('do deploy executable change set with 0 changes', async () => { expect(ret.noOp).toBeFalsy(); expect(executed).toBeTruthy(); }); + +test('correctly passes CFN parameters, ignoring ones with empty values', async () => { + // GIVEN + const sdk = new MockSDK(); + + let parameters: any[] | undefined; + + sdk.stubCloudFormation({ + describeStacks() { + return { + Stacks: [] + }; + }, + + createChangeSet(options) { + parameters = options.Parameters; + return {}; + }, + + describeChangeSet() { + return { + Status: 'CREATE_COMPLETE', + Changes: [], + }; + }, + + executeChangeSet() { + return {}; + } + }); + + // WHEN + await deployStack({ + stack: FAKE_STACK, + sdk, + parameters: { + A: 'A-value', + B: undefined, + C: '', + }, + }); + + // THEN + expect(parameters).toStrictEqual([ + { ParameterKey: 'A', ParameterValue: 'A-value' }, + ]); +}); diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index 2ae9897ca0078..4f3e78782a83a 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -126,6 +126,7 @@ class TestProvisioner implements IDeploymentTarget { stackArn: `arn:aws:cloudformation:::stack/${options.stack.stackName}/MockedOut`, noOp: false, outputs: { StackName: options.stack.stackName }, + stackArtifact: options.stack, }); } diff --git a/packages/aws-cdk/test/diff.test.ts b/packages/aws-cdk/test/diff.test.ts index 588478b6ce7a4..b5054adaf543c 100644 --- a/packages/aws-cdk/test/diff.test.ts +++ b/packages/aws-cdk/test/diff.test.ts @@ -46,8 +46,8 @@ test('diff can diff multiple stacks', async () => { async readCurrentTemplate(_stack: cxapi.CloudFormationStackArtifact): Promise