From 27164608f3fb67dd52b130631308ca3f72d6882f Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Thu, 21 May 2020 09:37:56 +0000 Subject: [PATCH 1/8] chore(release): 1.41.0 --- CHANGELOG.md | 18 ++++++++++++++++++ lerna.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fbd89a09be23..0386d2dbfb9a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.41.0](https://github.com/aws/aws-cdk/compare/v1.40.0...v1.41.0) (2020-05-21) + + +### Features + +* **cloudtrail:** create cloudwatch event without needing to create a Trail ([#8076](https://github.com/aws/aws-cdk/issues/8076)) ([0567a23](https://github.com/aws/aws-cdk/commit/0567a2360ac713e3171c9a82767611174dadb6c6)), closes [#6716](https://github.com/aws/aws-cdk/issues/6716) +* **cognito:** user pool - case sensitivity for sign in ([460394f](https://github.com/aws/aws-cdk/commit/460394f3dc4737cee80504d6c8ef106ecc3b67d5)), closes [#7988](https://github.com/aws/aws-cdk/issues/7988) [#7235](https://github.com/aws/aws-cdk/issues/7235) +* **core:** CfnJson enables intrinsics in hash keys ([#8099](https://github.com/aws/aws-cdk/issues/8099)) ([195cd40](https://github.com/aws/aws-cdk/commit/195cd405d9f0869875de2ec78661aee3af2c7c7d)), closes [#8084](https://github.com/aws/aws-cdk/issues/8084) +* **secretsmanager:** adds grantWrite to Secret ([#7858](https://github.com/aws/aws-cdk/issues/7858)) ([3fed84b](https://github.com/aws/aws-cdk/commit/3fed84ba9eec3f53c662966e366aa629209b7bf5)) +* **sns:** add support for subscription DLQ in SNS ([383cdb8](https://github.com/aws/aws-cdk/commit/383cdb86effeafdf5d0767ed379b16b3d78a933b)) +* **stepfunctions:** new service integration classes for Lambda, SNS, and SQS ([#7946](https://github.com/aws/aws-cdk/issues/7946)) ([c038848](https://github.com/aws/aws-cdk/commit/c0388483524832ca7863de4ee9c472b8ab39de8e)), closes [#6715](https://github.com/aws/aws-cdk/issues/6715) [#6489](https://github.com/aws/aws-cdk/issues/6489) + + +### Bug Fixes + +* **apigateway:** contextAccountId in AccessLogField incorrectly resolves to requestId ([7b89e80](https://github.com/aws/aws-cdk/commit/7b89e805c716fa73d41cc97fcb728634e7a59136)), closes [#7952](https://github.com/aws/aws-cdk/issues/7952) [#7951](https://github.com/aws/aws-cdk/issues/7951) +* **autoscaling:** add noDevice as a volume type ([#7253](https://github.com/aws/aws-cdk/issues/7253)) ([751958b](https://github.com/aws/aws-cdk/commit/751958b69225fdfc52622781c618f5a77f881fb6)), closes [#7242](https://github.com/aws/aws-cdk/issues/7242) + ## [1.40.0](https://github.com/aws/aws-cdk/compare/v1.39.0...v1.40.0) (2020-05-20) diff --git a/lerna.json b/lerna.json index b32aeb94bcf30..baa3940d032fb 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.40.0" + "version": "1.41.0" } From facf3a2c6e5ad59de5b52926cdce7e6bc2730ddd Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Wed, 27 May 2020 10:00:35 +0000 Subject: [PATCH 2/8] chore(release): 1.42.0 --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0386d2dbfb9a6..e4f1ecfdf2b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.42.0](https://github.com/aws/aws-cdk/compare/v1.40.0...v1.42.0) (2020-05-27) + + +### ⚠ BREAKING CHANGES + +* **cloudtrail:** API signatures of `addS3EventSelectors` and +`addLambdaEventSelectors` have changed. Their parameters are now +strongly typed to accept `IBucket` and `IFunction` respectively. +* **cloudtrail:** `addS3EventSelectors` and `addLambdaEventSelectors` +can no longer be used to configure all S3 data events or all Lambda data +events. Two new APIs `logAllS3DataEvents()` and +`logAllLambdaDataEvents()` have been introduced to achieve this. +* **cloudtrail:** The property `snsTopic` is now of the type `ITopic`. +## End Commit Message + +### Features + +* **cfnspec:** cloudformation spec v14.4.0 ([#8195](https://github.com/aws/aws-cdk/issues/8195)) ([99e7330](https://github.com/aws/aws-cdk/commit/99e7330fc5fc140964c47d8c6dbaee2b46b382e1)) +* **cloudtrail:** create cloudwatch event without needing to create a Trail ([#8076](https://github.com/aws/aws-cdk/issues/8076)) ([0567a23](https://github.com/aws/aws-cdk/commit/0567a2360ac713e3171c9a82767611174dadb6c6)), closes [#6716](https://github.com/aws/aws-cdk/issues/6716) +* **cloudtrail:** user specified log group ([#8079](https://github.com/aws/aws-cdk/issues/8079)) ([0a3785b](https://github.com/aws/aws-cdk/commit/0a3785b7626633fcbdf26ab793c70f2bc017314b)), closes [#6162](https://github.com/aws/aws-cdk/issues/6162) +* **codeguruprofiler:** ProfilingGroup ([#7895](https://github.com/aws/aws-cdk/issues/7895)) ([995088a](https://github.com/aws/aws-cdk/commit/995088abb00d9c75adbb65845998a8328bb5ba14)) +* **codepipeline:** use a special bootstrapless synthesizer for cross-region support Stacks ([#8091](https://github.com/aws/aws-cdk/issues/8091)) ([575f1db](https://github.com/aws/aws-cdk/commit/575f1db0474327c61c4ac626608c9f443ce231d2)), closes [#8082](https://github.com/aws/aws-cdk/issues/8082) +* **cognito:** user pool - case sensitivity for sign in ([460394f](https://github.com/aws/aws-cdk/commit/460394f3dc4737cee80504d6c8ef106ecc3b67d5)), closes [#7988](https://github.com/aws/aws-cdk/issues/7988) [#7235](https://github.com/aws/aws-cdk/issues/7235) +* **core:** CfnJson enables intrinsics in hash keys ([#8099](https://github.com/aws/aws-cdk/issues/8099)) ([195cd40](https://github.com/aws/aws-cdk/commit/195cd405d9f0869875de2ec78661aee3af2c7c7d)), closes [#8084](https://github.com/aws/aws-cdk/issues/8084) +* **eks:** improve security using IRSA conditions ([#8084](https://github.com/aws/aws-cdk/issues/8084)) ([35a01a0](https://github.com/aws/aws-cdk/commit/35a01a079af40da291007da08af6690c9a81c101)) +* **elbv2:** Supports new types of listener rule conditions ([#7848](https://github.com/aws/aws-cdk/issues/7848)) ([3d30ffa](https://github.com/aws/aws-cdk/commit/3d30ffa38c51ae26686287e993af445ea3067766)), closes [#3888](https://github.com/aws/aws-cdk/issues/3888) +* **secretsmanager:** adds grantWrite to Secret ([#7858](https://github.com/aws/aws-cdk/issues/7858)) ([3fed84b](https://github.com/aws/aws-cdk/commit/3fed84ba9eec3f53c662966e366aa629209b7bf5)) +* **sns:** add support for subscription DLQ in SNS ([383cdb8](https://github.com/aws/aws-cdk/commit/383cdb86effeafdf5d0767ed379b16b3d78a933b)) +* **stepfunctions:** new service integration classes for Lambda, SNS, and SQS ([#7946](https://github.com/aws/aws-cdk/issues/7946)) ([c038848](https://github.com/aws/aws-cdk/commit/c0388483524832ca7863de4ee9c472b8ab39de8e)), closes [#6715](https://github.com/aws/aws-cdk/issues/6715) [#6489](https://github.com/aws/aws-cdk/issues/6489) +* **stepfunctions:** support paths in Pass state ([#8070](https://github.com/aws/aws-cdk/issues/8070)) ([86eac6a](https://github.com/aws/aws-cdk/commit/86eac6af074bf78a921c52d613eca0dd4a514a49)), closes [#7181](https://github.com/aws/aws-cdk/issues/7181) +* **stepfunctions-tasks:** task for starting a job run in AWS Glue ([#8143](https://github.com/aws/aws-cdk/issues/8143)) ([a721e67](https://github.com/aws/aws-cdk/commit/a721e670cdc9888cd67ef1a24021004e18bfd23c)) + + +### Bug Fixes + +* **apigateway:** contextAccountId in AccessLogField incorrectly resolves to requestId ([7b89e80](https://github.com/aws/aws-cdk/commit/7b89e805c716fa73d41cc97fcb728634e7a59136)), closes [#7952](https://github.com/aws/aws-cdk/issues/7952) [#7951](https://github.com/aws/aws-cdk/issues/7951) +* **autoscaling:** add noDevice as a volume type ([#7253](https://github.com/aws/aws-cdk/issues/7253)) ([751958b](https://github.com/aws/aws-cdk/commit/751958b69225fdfc52622781c618f5a77f881fb6)), closes [#7242](https://github.com/aws/aws-cdk/issues/7242) +* **aws-eks:** kubectlEnabled: false conflicts with addNodegroup ([#8119](https://github.com/aws/aws-cdk/issues/8119)) ([8610889](https://github.com/aws/aws-cdk/commit/86108890a51443dc06ec6325038c7b19cbdaee76)), closes [#7993](https://github.com/aws/aws-cdk/issues/7993) +* **cli:** paper cuts ([#8164](https://github.com/aws/aws-cdk/issues/8164)) ([af2ea60](https://github.com/aws/aws-cdk/commit/af2ea60e7ae4aaab17ddd10a9142e1809b4c8246)) +* **dynamodb:** the maximum number of nonKeyAttributes is 100, not 20 ([#8186](https://github.com/aws/aws-cdk/issues/8186)) ([0393528](https://github.com/aws/aws-cdk/commit/03935280f1addef392c9b4460737cce8bb2eb8c9)), closes [#8095](https://github.com/aws/aws-cdk/issues/8095) +* **eks:** unable to add multiple service accounts ([#8122](https://github.com/aws/aws-cdk/issues/8122)) ([524440c](https://github.com/aws/aws-cdk/commit/524440c5454d15276c92581a08d4ee7cad1790eb)) +* **events:** cannot use the same target account for 2 cross-account event sources ([#8068](https://github.com/aws/aws-cdk/issues/8068)) ([395c07c](https://github.com/aws/aws-cdk/commit/395c07c0cac7739743fc71d71fddd8880b608ead)), closes [#8010](https://github.com/aws/aws-cdk/issues/8010) +* **lambda-nodejs:** build fails on Windows ([#8140](https://github.com/aws/aws-cdk/issues/8140)) ([04490b1](https://github.com/aws/aws-cdk/commit/04490b134a05ec34523541a3ca282ba8957a7964)), closes [#8107](https://github.com/aws/aws-cdk/issues/8107) + + +* **cloudtrail:** better typed event selector apis ([#8097](https://github.com/aws/aws-cdk/issues/8097)) ([0028778](https://github.com/aws/aws-cdk/commit/0028778c0f00f2faa8dad25345cd17f311fad5da)) + ## [1.41.0](https://github.com/aws/aws-cdk/compare/v1.40.0...v1.41.0) (2020-05-21) diff --git a/lerna.json b/lerna.json index baa3940d032fb..b533a6ac4d33c 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.41.0" + "version": "1.42.0" } From 10dd0d7b4d2dbebd4e54583885fef56c951d91d2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 27 May 2020 21:05:33 +0000 Subject: [PATCH 3/8] chore(deps): bump @typescript-eslint/eslint-plugin from 3.0.0 to 3.0.2 (#8237) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 3.0.0 to 3.0.2. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v3.0.2/packages/eslint-plugin) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- tools/cdk-build-tools/package.json | 2 +- yarn.lock | 170 +++++------------------------ 2 files changed, 26 insertions(+), 146 deletions(-) diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 0ddee7d957395..09a93983b382d 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -39,7 +39,7 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^3.0.2", "@typescript-eslint/parser": "^2.19.2", "awslint": "0.0.0", "colors": "^1.4.0", diff --git a/yarn.lock b/yarn.lock index 1b23abd893193..dfaf923f05271 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1658,12 +1658,12 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.3.tgz#38fb31d82ed07dea87df6bd565721d11979fd761" integrity sha512-mhdQq10tYpiNncMkg1vovCud5jQm+rWeRVz6fxjCJlY6uhDlAn9GnMSmBa2DQwqPf/jS5YR0K/xChDEh1jdOQg== -"@typescript-eslint/eslint-plugin@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.0.tgz#02f8ec6b5ce814bda80dfc22463f108bed1f699b" - integrity sha512-lcZ0M6jD4cqGccYOERKdMtg+VWpoq3NSnWVxpc/AwAy0zhkUYVioOUZmfNqiNH8/eBNGhCn6HXd6mKIGRgNc1Q== +"@typescript-eslint/eslint-plugin@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.2.tgz#4a114a066e2f9659b25682ee59d4866e15a17ec3" + integrity sha512-ER3bSS/A/pKQT/hjMGCK8UQzlL0yLjuCZ/G8CDFJFVTfl3X65fvq2lNYqOG8JPTfrPa2RULCdwfOyFjZEMNExQ== dependencies: - "@typescript-eslint/experimental-utils" "3.0.0" + "@typescript-eslint/experimental-utils" "3.0.2" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" @@ -1679,13 +1679,13 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.0.tgz#1ddf53eeb61ac8eaa9a77072722790ac4f641c03" - integrity sha512-BN0vmr9N79M9s2ctITtChRuP1+Dls0x/wlg0RXW1yQ7WJKPurg6X3Xirv61J2sjPif4F8SLsFMs5Nzte0WYoTQ== +"@typescript-eslint/experimental-utils@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.2.tgz#bb2131baede8df28ec5eacfa540308ca895e5fee" + integrity sha512-4Wc4EczvoY183SSEnKgqAfkj1eLtRgBQ04AAeG+m4RhTVyaazxc1uI8IHf0qLmu7xXe9j1nn+UoDJjbmGmuqXQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "3.0.0" + "@typescript-eslint/typescript-estree" "3.0.2" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1712,10 +1712,10 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.0.tgz#fa40e1b76ccff880130be054d9c398e96004bf42" - integrity sha512-nevQvHyNghsfLrrByzVIH4ZG3NROgJ8LZlfh3ddwPPH4CH7W4GAiSx5qu+xHuX5pWsq6q/eqMc1io840ZhAnUg== +"@typescript-eslint/typescript-estree@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.2.tgz#67a1ce4307ebaea43443fbf3f3be7e2627157293" + integrity sha512-cs84mxgC9zQ6viV8MEcigfIKQmKtBkZNDYf8Gru2M+MhnA6z9q0NFMZm2IEzKqAwN8lY5mFVd1Z8DiHj6zQ3Tw== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -1897,11 +1897,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2130,7 +2125,7 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: +aws-sdk-mock@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== @@ -2139,21 +2134,6 @@ aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.596.0: - version "2.684.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.684.0.tgz#14e313893b3225511d12f4a0985e7b0613603006" - integrity sha512-OkSMKIbRTBd3YV5iAklJxZFyLg0jRO2XW6+WhMuDBHMEs8aRbZw4iAD85wFC8tG7X9o0kcjnWfZUDq7MK1dcDg== - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - aws-sdk@^2.637.0, aws-sdk@^2.681.0: version "2.681.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.681.0.tgz#09eeedb5ca49813dfc637908abe408ae114a6824" @@ -3671,16 +3651,6 @@ dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dotenv-json@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" - integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== - -dotenv@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== - dotgitignore@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -3863,11 +3833,6 @@ escodegen@1.x.x, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^14.1.0: - version "14.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" - integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== - eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" @@ -3895,15 +3860,7 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== - dependencies: - eslint-utils "^1.4.2" - regexpp "^3.0.0" - -eslint-plugin-import@^2.19.1, eslint-plugin-import@^2.20.2: +eslint-plugin-import@^2.20.2: version "2.20.2" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== @@ -3921,28 +3878,6 @@ eslint-plugin-import@^2.19.1, eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== - dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-promise@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" - integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== - -eslint-plugin-standard@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" - integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== - eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -3951,7 +3886,7 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2, eslint-utils@^1.4.3: +eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== @@ -5009,11 +4944,6 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.6.tgz#643194ad4bf2712f37852e386b6998eff0db2106" - integrity sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -5976,7 +5906,7 @@ jest-worker@^25.5.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^25.4.0, jest@^25.5.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: +jest@^25.4.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: version "25.5.4" resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ== @@ -6261,24 +6191,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lambda-leak@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" - integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= - -lambda-tester@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" - integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== - dependencies: - app-root-path "^2.2.1" - dotenv "^8.0.0" - dotenv-json "^1.0.0" - lambda-leak "^2.0.0" - semver "^6.1.1" - uuid "^3.3.2" - vandium-utils "^1.1.1" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -6884,7 +6796,7 @@ mkdirp@*, mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -7015,17 +6927,6 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^11.7.0: - version "11.9.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-11.9.1.tgz#2b026c5beb6d0dbcb41e7e4cefa671bc36db9c61" - integrity sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.13" - mkdirp "^0.5.0" - propagate "^2.0.0" - nock@^12.0.3: version "12.0.3" resolved "https://registry.yarnpkg.com/nock/-/nock-12.0.3.tgz#83f25076dbc4c9aa82b5cdf54c9604c7a778d1c9" @@ -8395,7 +8296,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.10.1, resolve@^1.17.0: +resolve@^1.1.6, resolve@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -8553,11 +8454,6 @@ semver-intersect@^1.4.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - semver@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" @@ -8568,6 +8464,11 @@ semver@7.x, semver@^7.2.2, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -9541,22 +9442,6 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= -ts-jest@^25.3.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" - integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - micromatch "4.x" - mkdirp "0.x" - semver "6.x" - yargs-parser "18.x" - ts-jest@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.0.0.tgz#957b802978249aaf74180b9dcb17b4fd787ad6f3" @@ -9907,11 +9792,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vandium-utils@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" - integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" From 703f0fa6e2ba5e668d6a92200493d19d2af626c0 Mon Sep 17 00:00:00 2001 From: Benjamin Genz Date: Wed, 27 May 2020 23:58:33 +0200 Subject: [PATCH 4/8] feat(redshift): add initial L2 Redshift construct (#5730) Initial commit to support Redshift as an L2 construct. This introduces the `RedshiftCluster` construct. It is by and large copy-pasted from `@aws-cdk/aws-rds` and adheres to the same functionality. **Purposeful Design Desicions** - no non-VPC clusters (a cluster will only be launched in a VPC; no L2 construct for `AWS::Redshift::ClusterSecurityGroup` or `AWS::Redshift::ClusterSecurityGroupIngress`) - cluster can only be launched in private Subnets - nodes will be encrypted - cluster will not be public and won't have an elastic IP - HSM cannot be configured **Checklist** - [x] Implementation on L2 construct for Redshift - [x] initial Unit tests - [x] additional Unit tests to satisfy coverage requirements - [x] Documentation in README.md This PR closes #5711 *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-redshift/README.md | 48 ++ packages/@aws-cdk/aws-redshift/lib/cluster.ts | 540 ++++++++++++++++++ .../aws-redshift/lib/database-secret.ts | 39 ++ .../@aws-cdk/aws-redshift/lib/endpoint.ts | 31 + packages/@aws-cdk/aws-redshift/lib/index.ts | 5 + .../aws-redshift/lib/parameter-group.ts | 77 +++ packages/@aws-cdk/aws-redshift/package.json | 19 + .../aws-redshift/test/cluster.test.ts | 329 +++++++++++ .../aws-redshift/test/parameter-group.test.ts | 29 + .../aws-redshift/test/redshift.test.ts | 6 - 10 files changed, 1117 insertions(+), 6 deletions(-) create mode 100644 packages/@aws-cdk/aws-redshift/lib/cluster.ts create mode 100644 packages/@aws-cdk/aws-redshift/lib/database-secret.ts create mode 100644 packages/@aws-cdk/aws-redshift/lib/endpoint.ts create mode 100644 packages/@aws-cdk/aws-redshift/lib/parameter-group.ts create mode 100644 packages/@aws-cdk/aws-redshift/test/cluster.test.ts create mode 100644 packages/@aws-cdk/aws-redshift/test/parameter-group.test.ts delete mode 100644 packages/@aws-cdk/aws-redshift/test/redshift.test.ts diff --git a/packages/@aws-cdk/aws-redshift/README.md b/packages/@aws-cdk/aws-redshift/README.md index 2bf53e2033f34..05736c4c15c2c 100644 --- a/packages/@aws-cdk/aws-redshift/README.md +++ b/packages/@aws-cdk/aws-redshift/README.md @@ -9,4 +9,52 @@ --- +### Starting a Redshift Cluster Database + +To set up a Redshift cluster, define a `Cluster`. It will be launched in a VPC. +You can specify a VPC, otherwise one will be created. The nodes are always launched in private subnets and are encrypted by default. + +``` typescript +import redshift = require('@aws-cdk/aws-redshift'); +... +const cluster = new redshift.Cluster(this, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc + }); +``` +By default, the master password will be generated and stored in AWS Secrets Manager. + +A default database named `default_db` will be created in the cluster. To change the name of this database set the `defaultDatabaseName` attribute in the constructor properties. + +### Connecting + +To control who can access the cluster, use the `.connections` attribute. Redshift Clusters have +a default port, so you don't need to specify the port: + +```ts +cluster.connections.allowFromAnyIpv4('Open to the world'); +``` + +The endpoint to access your database cluster will be available as the `.clusterEndpoint` attribute: + +```ts +cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT" +``` + +### Rotating credentials + +When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: +```ts +cluster.addRotationSingleUser(); // Will rotate automatically after 30 days +``` + +The multi user rotation scheme is also available: +```ts +cluster.addRotationMultiUser('MyUser', { + secret: myImportedSecret +}); +``` + This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts new file mode 100644 index 0000000000000..48caa7aabf1db --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -0,0 +1,540 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import { Construct, Duration, IResource, RemovalPolicy, Resource, SecretValue, Token } from '@aws-cdk/core'; +import { DatabaseSecret } from './database-secret'; +import { Endpoint } from './endpoint'; +import { IClusterParameterGroup } from './parameter-group'; +import { CfnCluster, CfnClusterSubnetGroup } from './redshift.generated'; + +/** + * Possible Node Types to use in the cluster + * used for defining {@link ClusterProps.nodeType}. + */ +export enum NodeType { + /** + * ds2.xlarge + */ + DS2_XLARGE = 'ds2.xlarge', + /** + * ds2.8xlarge + */ + DS2_8XLARGE = 'ds2.8xlarge', + /** + * dc1.large + */ + DC1_LARGE = 'dc1.large', + /** + * dc1.8xlarge + */ + DC1_8XLARGE = 'dc1.8xlarge', + /** + * dc2.large + */ + DC2_LARGE = 'dc2.large', + /** + * dc2.8xlarge + */ + DC2_8XLARGE = 'dc2.8xlarge', + /** + * ra3.16xlarge + */ + RA3_16XLARGE = 'ra3.16xlarge', +} + +/** + * What cluster type to use. + * Used by {@link ClusterProps.clusterType} + */ +export enum ClusterType { + /** + * single-node cluster, the {@link ClusterProps.numberOfNodes} parameter is not required + */ + SINGLE_NODE = 'single-node', + /** + * multi-node cluster, set the amount of nodes using {@link ClusterProps.numberOfNodes} parameter + */ + MULTI_NODE = 'multi-node', +} + +/** + * Username and password combination + */ +export interface Login { + /** + * Username + */ + readonly masterUsername: string; + + /** + * Password + * + * Do not put passwords in your CDK code directly. + * + * @default a Secrets Manager generated password + */ + readonly masterPassword?: SecretValue; + + /** + * KMS encryption key to encrypt the generated secret. + * + * @default default master key + */ + readonly encryptionKey?: kms.IKey; +} + +/** + * Options to add the multi user rotation + */ +export interface RotationMultiUserOptions { + /** + * The secret to rotate. It must be a JSON string with the following format: + * ``` + * { + * "engine": , + * "host": , + * "username": , + * "password": , + * "dbname": , + * "port": , + * "masterarn": + * } + * ``` + */ + readonly secret: secretsmanager.ISecret; + + /** + * Specifies the number of days after the previous rotation before + * Secrets Manager triggers the next automatic rotation. + * + * @default Duration.days(30) + */ + readonly automaticallyAfter?: Duration; +} + +/** + * Create a Redshift Cluster with a given number of nodes. + * Implemented by {@link Cluster} via {@link ClusterBase}. + */ +export interface ICluster extends IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { + /** + * Name of the cluster + * + * @attribute ClusterName + */ + readonly clusterName: string; + + /** + * The endpoint to use for read/write operations + * + * @attribute EndpointAddress,EndpointPort + */ + readonly clusterEndpoint: Endpoint; +} + +/** + * Properties that describe an existing cluster instance + */ +export interface ClusterAttributes { + /** + * The security groups of the redshift cluster + * + * @default no security groups will be attached to the import + */ + readonly securityGroups?: ec2.ISecurityGroup[]; + + /** + * Identifier for the cluster + */ + readonly clusterName: string; + + /** + * Cluster endpoint address + */ + readonly clusterEndpointAddress: string; + + /** + * Cluster endpoint port + */ + readonly clusterEndpointPort: number; + +} + +/** + * Properties for a new database cluster + */ +export interface ClusterProps { + + /** + * An optional identifier for the cluster + * + * @default - A name is automatically generated. + */ + readonly clusterName?: string; + + /** + * Additional parameters to pass to the database engine + * https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-parameter-groups.html + * + * @default - No parameter group. + */ + readonly parameterGroup?: IClusterParameterGroup; + + /** + * Number of compute nodes in the cluster + * + * Value must be at least 1 and no more than 100. + * + * @default 1 + */ + readonly numberOfNodes?: number; + + /** + * The node type to be provisioned for the cluster. + * + * @default {@link NodeType.DC2_LARGE} + */ + readonly nodeType?: NodeType; + + /** + * Settings for the individual instances that are launched + * + * @default {@link ClusterType.MULTI_NODE} + */ + readonly clusterType?: ClusterType; + + /** + * What port to listen on + * + * @default - The default for the engine is used. + */ + readonly port?: number; + + /** + * Whether to enable encryption of data at rest in the cluster. + * + * @default true + */ + readonly encrypted?: boolean + + /** + * The KMS key to use for encryption of data at rest. + * + * @default - AWS-managed key, if encryption at rest is enabled + */ + readonly encryptionKey?: kms.IKey; + + /** + * A preferred maintenance window day/time range. Should be specified as a range ddd:hh24:mi-ddd:hh24:mi (24H Clock UTC). + * + * Example: 'Sun:23:45-Mon:00:15' + * + * @default - 30-minute window selected at random from an 8-hour block of time for + * each AWS Region, occurring on a random day of the week. + * @see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_UpgradeDBInstance.Maintenance.html#Concepts.DBMaintenance + */ + readonly preferredMaintenanceWindow?: string; + + /** + * The VPC to place the cluster in. + */ + readonly vpc: ec2.IVpc; + + /** + * Where to place the instances within the VPC + * + * @default private subnets + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * Security group. + * + * @default a new security group is created. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; + + /** + * Username and password for the administrative user + */ + readonly masterUser: Login; + + /** + * A list of AWS Identity and Access Management (IAM) role that can be used by the cluster to access other AWS services. + * Specify a maximum of 10 roles. + * + * @default - No role is attached to the cluster. + */ + readonly roles?: iam.IRole[]; + + /** + * Name of a database which is automatically created inside the cluster + * + * @default - default_db + */ + readonly defaultDatabaseName?: string; + + /** + * Bucket to send logs to. + * Logging information includes queries and connection attempts, for the specified Amazon Redshift cluster. + * + * @default - No Logs + */ + readonly loggingBucket?: s3.IBucket + + /** + * Prefix used for logging + * + * @default - no prefix + */ + readonly loggingKeyPrefix?: string + + /** + * The removal policy to apply when the cluster and its instances are removed + * from the stack or replaced during an update. + * + * @default RemovalPolicy.RETAIN + */ + readonly removalPolicy?: RemovalPolicy +} + +/** + * A new or imported clustered database. + */ +abstract class ClusterBase extends Resource implements ICluster { + /** + * Name of the cluster + */ + public abstract readonly clusterName: string; + + /** + * The endpoint to use for read/write operations + */ + public abstract readonly clusterEndpoint: Endpoint; + + /** + * Access to the network connections + */ + public abstract readonly connections: ec2.Connections; + + /** + * Renders the secret attachment target specifications. + */ + public asSecretAttachmentTarget(): secretsmanager.SecretAttachmentTargetProps { + return { + targetId: this.clusterName, + targetType: secretsmanager.AttachmentTargetType.REDSHIFT_CLUSTER, + }; + } +} + +/** + * Create a Redshift cluster a given number of nodes. + * + * @resource AWS::Redshift::Cluster + */ +export class Cluster extends ClusterBase { + /** + * Import an existing DatabaseCluster from properties + */ + public static fromClusterAttributes(scope: Construct, id: string, attrs: ClusterAttributes): ICluster { + class Import extends ClusterBase { + public readonly connections = new ec2.Connections({ + securityGroups: attrs.securityGroups, + defaultPort: ec2.Port.tcp(attrs.clusterEndpointPort), + }); + public readonly clusterName = attrs.clusterName; + public readonly instanceIdentifiers: string[] = []; + public readonly clusterEndpoint = new Endpoint(attrs.clusterEndpointAddress, attrs.clusterEndpointPort); + } + + return new Import(scope, id); + } + + /** + * Identifier of the cluster + */ + public readonly clusterName: string; + + /** + * The endpoint to use for read/write operations + */ + public readonly clusterEndpoint: Endpoint; + + /** + * Access to the network connections + */ + public readonly connections: ec2.Connections; + + /** + * The secret attached to this cluster + */ + public readonly secret?: secretsmanager.ISecret; + + private readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; + private readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + + /** + * The VPC where the DB subnet group is created. + */ + private readonly vpc: ec2.IVpc; + + /** + * The subnets used by the DB subnet group. + */ + private readonly vpcSubnets?: ec2.SubnetSelection; + + constructor(scope: Construct, id: string, props: ClusterProps) { + super(scope, id); + + this.vpc = props.vpc; + this.vpcSubnets = props.vpcSubnets ? props.vpcSubnets : { + subnetType: ec2.SubnetType.PRIVATE, + }; + + const removalPolicy = props.removalPolicy ? props.removalPolicy : RemovalPolicy.RETAIN; + + const { subnetIds } = this.vpc.selectSubnets(this.vpcSubnets); + + const subnetGroup = new CfnClusterSubnetGroup(this, 'Subnets', { + description: `Subnets for ${id} Redshift cluster`, + subnetIds, + }); + + subnetGroup.applyRemovalPolicy(removalPolicy, { + applyToUpdateReplacePolicy: true, + }); + + const securityGroups = props.securityGroups !== undefined ? + props.securityGroups : [new ec2.SecurityGroup(this, 'SecurityGroup', { + description: 'Redshift security group', + vpc: this.vpc, + securityGroupName: 'redshift SG', + })]; + + const securityGroupIds = securityGroups.map(sg => sg.securityGroupId); + + let secret: DatabaseSecret | undefined; + if (!props.masterUser.masterPassword) { + secret = new DatabaseSecret(this, 'Secret', { + username: props.masterUser.masterUsername, + encryptionKey: props.masterUser.encryptionKey, + }); + } + + const clusterType = props.clusterType || ClusterType.MULTI_NODE; + const nodeCount = props.numberOfNodes !== undefined ? props.numberOfNodes : (clusterType === ClusterType.MULTI_NODE ? 2 : 1); + + if (clusterType === ClusterType.MULTI_NODE && nodeCount < 2) { + throw new Error('Number of nodes for cluster type multi-node must be at least 2'); + } + + if (props.encrypted === false && props.encryptionKey !== undefined) { + throw new Error('Cannot set property encryptionKey without enabling encryption!'); + } + + this.singleUserRotationApplication = secretsmanager.SecretRotationApplication.REDSHIFT_ROTATION_SINGLE_USER; + this.multiUserRotationApplication = secretsmanager.SecretRotationApplication.REDSHIFT_ROTATION_MULTI_USER; + + let loggingProperties; + if (props.loggingBucket) { + loggingProperties = { + bucketName: props.loggingBucket.bucketName, + s3KeyPrefix: props.loggingKeyPrefix, + }; + } + + const cluster = new CfnCluster(this, 'Resource', { + // Basic + allowVersionUpgrade: true, + automatedSnapshotRetentionPeriod: 1, + clusterType, + clusterIdentifier: props.clusterName, + clusterSubnetGroupName: subnetGroup.ref, + vpcSecurityGroupIds: securityGroupIds, + port: props.port, + clusterParameterGroupName: props.parameterGroup && props.parameterGroup.clusterParameterGroupName, + // Admin + masterUsername: secret ? secret.secretValueFromJson('username').toString() : props.masterUser.masterUsername, + masterUserPassword: secret + ? secret.secretValueFromJson('password').toString() + : (props.masterUser.masterPassword + ? props.masterUser.masterPassword.toString() + : 'default'), + preferredMaintenanceWindow: props.preferredMaintenanceWindow, + nodeType: props.nodeType || NodeType.DC2_LARGE, + numberOfNodes: nodeCount, + loggingProperties, + iamRoles: props.roles ? props.roles.map(role => role.roleArn) : undefined, + dbName: props.defaultDatabaseName || 'default_db', + publiclyAccessible: false, + // Encryption + kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, + encrypted: props.encrypted !== undefined ? props.encrypted : true, + }); + + cluster.applyRemovalPolicy(removalPolicy, { + applyToUpdateReplacePolicy: true, + }); + + this.clusterName = cluster.ref; + + // create a number token that represents the port of the cluster + const portAttribute = Token.asNumber(cluster.attrEndpointPort); + this.clusterEndpoint = new Endpoint(cluster.attrEndpointAddress, portAttribute); + + if (secret) { + this.secret = secret.attach(this); + } + + const defaultPort = ec2.Port.tcp(this.clusterEndpoint.port); + this.connections = new ec2.Connections({ securityGroups, defaultPort }); + } + + /** + * Adds the single user rotation of the master password to this cluster. + * + * @param [automaticallyAfter=Duration.days(30)] Specifies the number of days after the previous rotation + * before Secrets Manager triggers the next automatic rotation. + */ + public addRotationSingleUser(automaticallyAfter?: Duration): secretsmanager.SecretRotation { + if (!this.secret) { + throw new Error('Cannot add single user rotation for a cluster without secret.'); + } + + const id = 'RotationSingleUser'; + const existing = this.node.tryFindChild(id); + if (existing) { + throw new Error('A single user rotation was already added to this cluster.'); + } + + return new secretsmanager.SecretRotation(this, id, { + secret: this.secret, + automaticallyAfter, + application: this.singleUserRotationApplication, + vpc: this.vpc, + vpcSubnets: this.vpcSubnets, + target: this, + }); + } + + /** + * Adds the multi user rotation to this cluster. + */ + public addRotationMultiUser(id: string, options: RotationMultiUserOptions): secretsmanager.SecretRotation { + if (!this.secret) { + throw new Error('Cannot add multi user rotation for a cluster without secret.'); + } + return new secretsmanager.SecretRotation(this, id, { + secret: options.secret, + masterSecret: this.secret, + automaticallyAfter: options.automaticallyAfter, + application: this.multiUserRotationApplication, + vpc: this.vpc, + vpcSubnets: this.vpcSubnets, + target: this, + }); + } +} diff --git a/packages/@aws-cdk/aws-redshift/lib/database-secret.ts b/packages/@aws-cdk/aws-redshift/lib/database-secret.ts new file mode 100644 index 0000000000000..7e7617be2be83 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/database-secret.ts @@ -0,0 +1,39 @@ +import * as kms from '@aws-cdk/aws-kms'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import { Construct } from '@aws-cdk/core'; + +/** + * Construction properties for a DatabaseSecret. + */ +export interface DatabaseSecretProps { + /** + * The username. + */ + readonly username: string; + + /** + * The KMS key to use to encrypt the secret. + * + * @default default master key + */ + readonly encryptionKey?: kms.IKey; +} + +/** + * A database secret. + * + * @resource AWS::SecretsManager::Secret + */ +export class DatabaseSecret extends secretsmanager.Secret { + constructor(scope: Construct, id: string, props: DatabaseSecretProps) { + super(scope, id, { + encryptionKey: props.encryptionKey, + generateSecretString: { + passwordLength: 30, // Redshift password could be up to 64 characters + secretStringTemplate: JSON.stringify({ username: props.username }), + generateStringKey: 'password', + excludeCharacters: '"@/\\\ \'', + }, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/lib/endpoint.ts b/packages/@aws-cdk/aws-redshift/lib/endpoint.ts new file mode 100644 index 0000000000000..0ee19b8d82113 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/endpoint.ts @@ -0,0 +1,31 @@ +import { Token } from '@aws-cdk/core'; + +/** + * Connection endpoint of a redshift cluster + * + * Consists of a combination of hostname and port. + */ +export class Endpoint { + /** + * The hostname of the endpoint + */ + public readonly hostname: string; + + /** + * The port of the endpoint + */ + public readonly port: number; + + /** + * The combination of "HOSTNAME:PORT" for this endpoint + */ + public readonly socketAddress: string; + + constructor(address: string, port: number) { + this.hostname = address; + this.port = port; + + const portDesc = Token.isUnresolved(port) ? Token.asString(port) : port; + this.socketAddress = `${address}:${portDesc}`; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/lib/index.ts b/packages/@aws-cdk/aws-redshift/lib/index.ts index e1441fcf6bb03..6d5e5d00bb134 100644 --- a/packages/@aws-cdk/aws-redshift/lib/index.ts +++ b/packages/@aws-cdk/aws-redshift/lib/index.ts @@ -1,2 +1,7 @@ +export * from './cluster'; +export * from './parameter-group'; +export * from './database-secret'; +export * from './endpoint'; + // AWS::Redshift CloudFormation Resources: export * from './redshift.generated'; diff --git a/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts b/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts new file mode 100644 index 0000000000000..ea5698b235628 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/parameter-group.ts @@ -0,0 +1,77 @@ +import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { CfnClusterParameterGroup } from './redshift.generated'; + +/** + * A parameter group + */ +export interface IClusterParameterGroup extends IResource { + /** + * The name of this parameter group + * + * @attribute + */ + readonly clusterParameterGroupName: string; +} + +/** + * A new cluster or instance parameter group + */ +abstract class ClusterParameterGroupBase extends Resource implements IClusterParameterGroup { + /** + * The name of the parameter group + */ + public abstract readonly clusterParameterGroupName: string; +} + +/** + * Properties for a parameter group + */ +export interface ClusterParameterGroupProps { + /** + * Description for this parameter group + * + * @default a CDK generated description + */ + readonly description?: string; + + /** + * The parameters in this parameter group + */ + readonly parameters: { [name: string]: string }; +} + +/** + * A cluster parameter group + * + * @resource AWS::Redshift::ClusterParameterGroup + */ +export class ClusterParameterGroup extends ClusterParameterGroupBase { + /** + * Imports a parameter group + */ + public static fromClusterParameterGroupName(scope: Construct, id: string, clusterParameterGroupName: string): IClusterParameterGroup { + class Import extends Resource implements IClusterParameterGroup { + public readonly clusterParameterGroupName = clusterParameterGroupName; + } + return new Import(scope, id); + } + + /** + * The name of the parameter group + */ + public readonly clusterParameterGroupName: string; + + constructor(scope: Construct, id: string, props: ClusterParameterGroupProps) { + super(scope, id); + + const resource = new CfnClusterParameterGroup(this, 'Resource', { + description: props.description || 'Cluster parameter group for family redshift-1.0', + parameterGroupFamily: 'redshift-1.0', + parameters: Object.entries(props.parameters).map(([name, value]) => { + return {parameterName: name, parameterValue: value}; + }), + }); + + this.clusterParameterGroupName = resource.ref; + } +} diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 07283d9304b9f..3b645e15ba91e 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -66,20 +66,39 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", + "jest": "^25.5.3", "pkglint": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kms": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, + "awslint": { + "exclude": [ + "docs-public-apis:@aws-cdk/aws-redshift.ParameterGroupParameters.parameterName", + "docs-public-apis:@aws-cdk/aws-redshift.ParameterGroupParameters.parameterValue", + "props-physical-name:@aws-cdk/aws-redshift.ClusterParameterGroupProps", + "props-physical-name:@aws-cdk/aws-redshift.DatabaseSecretProps" + ] + }, "stability": "experimental", "maturity": "cfn-only", "awscdkio": { diff --git a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts new file mode 100644 index 0000000000000..385a2f53208b5 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts @@ -0,0 +1,329 @@ +import { expect as cdkExpect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; + +import { Cluster, ClusterParameterGroup, ClusterType, NodeType } from '../lib'; + +test('check that instantiation works', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + masterPassword: cdk.SecretValue.plainText('tooshort'), + }, + vpc, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + Properties: { + AllowVersionUpgrade: true, + MasterUsername: 'admin', + MasterUserPassword: 'tooshort', + ClusterType: 'multi-node', + AutomatedSnapshotRetentionPeriod: 1, + Encrypted: true, + NumberOfNodes: 2, + NodeType: 'dc2.large', + DBName: 'default_db', + PubliclyAccessible: false, + ClusterSubnetGroupName: { Ref: 'RedshiftSubnetsDFE70E0A' }, + VpcSecurityGroupIds: [{ 'Fn::GetAtt': ['RedshiftSecurityGroup796D74A7', 'GroupId'] }], + }, + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain', + }, ResourcePart.CompleteDefinition)); + + cdkExpect(stack).to(haveResource('AWS::Redshift::ClusterSubnetGroup', { + Properties: { + Description: 'Subnets for Redshift Redshift cluster', + SubnetIds: [ + { Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' }, + { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, + { Ref: 'VPCPrivateSubnet3Subnet3EDCD457' }, + ], + }, + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain', + }, ResourcePart.CompleteDefinition)); +}); + +test('can create a cluster with imported vpc and security group', () => { + // GIVEN + const stack = testStack(); + const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { + vpcId: 'VPC12345', + }); + const sg = ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'SecurityGroupId12345'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + masterPassword: cdk.SecretValue.plainText('tooshort'), + }, + vpc, + securityGroups: [sg], + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + ClusterSubnetGroupName: { Ref: 'RedshiftSubnetsDFE70E0A' }, + MasterUsername: 'admin', + MasterUserPassword: 'tooshort', + VpcSecurityGroupIds: ['SecurityGroupId12345'], + })); +}); + +test('creates a secret when master credentials are not specified', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + MasterUsername: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'RedshiftSecretA08D42D6', + }, + ':SecretString:username::}}', + ], + ], + }, + MasterUserPassword: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'RedshiftSecretA08D42D6', + }, + ':SecretString:password::}}', + ], + ], + }, + })); + + cdkExpect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeCharacters: '"@/\\\ \'', + GenerateStringKey: 'password', + PasswordLength: 30, + SecretStringTemplate: '{"username":"admin"}', + }, + })); +}); + +test('SIngle Node CLusters spawn only single node', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc, + nodeType: NodeType.DC1_8XLARGE, + clusterType: ClusterType.SINGLE_NODE, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + ClusterType: 'single-node', + NodeType: 'dc1.8xlarge', + NumberOfNodes: 1, + })); +}); + +test('create an encrypted cluster with custom KMS key', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + encryptionKey: new kms.Key(stack, 'Key'), + vpc, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + KmsKeyId: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn', + ], + }, + })); +}); + +test('cluster with parameter group', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const group = new ClusterParameterGroup(stack, 'Params', { + description: 'bye', + parameters: { + param: 'value', + }, + }); + + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc, + parameterGroup: group, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + ClusterParameterGroupName: { Ref: 'ParamsA8366201' }, + })); + +}); + +test('imported cluster with imported security group honors allowAllOutbound', () => { + // GIVEN + const stack = testStack(); + + const cluster = Cluster.fromClusterAttributes(stack, 'Database', { + clusterEndpointAddress: 'addr', + clusterName: 'identifier', + clusterEndpointPort: 3306, + securityGroups: [ + ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', { + allowAllOutbound: false, + }), + ], + }); + + // WHEN + cluster.connections.allowToAnyIpv4(ec2.Port.tcp(443)); + + // THEN + cdkExpect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: 'sg-123456789', + })); +}); + +test('can create a cluster with logging enabled', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const bucket = s3.Bucket.fromBucketName(stack, 'bucket', 'logging-bucket'); + + // WHEN + new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc, + loggingBucket: bucket, + loggingKeyPrefix: 'prefix', + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', { + LoggingProperties: { + BucketName: 'logging-bucket', + S3KeyPrefix: 'prefix', + }, + })); +}); + +test('throws when trying to add rotation to a cluster without secret', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const cluster = new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + masterPassword: cdk.SecretValue.plainText('tooshort'), + }, + vpc, + }); + + // THEN + expect(() => { + cluster.addRotationSingleUser(); + }).toThrowError(); + +}); + +test('throws validation error when trying to set encryptionKey without enabling encryption', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const key = new kms.Key(stack, 'kms-key'); + + // WHEN + const props = { + encrypted: false, + encryptionKey: key, + masterUser: { + masterUsername: 'admin', + }, + vpc, + }; + + // THEN + expect(() => { + new Cluster(stack, 'Redshift', props ); + }).toThrowError(); + +}); + +test('throws when trying to add single user rotation multiple times', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new Cluster(stack, 'Redshift', { + masterUser: { + masterUsername: 'admin', + }, + vpc, + }); + + // WHEN + cluster.addRotationSingleUser(); + + // THEN + expect(() => { + cluster.addRotationSingleUser(); + }).toThrowError(); +}); + +function testStack() { + const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); + stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); + return stack; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/test/parameter-group.test.ts b/packages/@aws-cdk/aws-redshift/test/parameter-group.test.ts new file mode 100644 index 0000000000000..ca5923ee36ba6 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/test/parameter-group.test.ts @@ -0,0 +1,29 @@ +import { expect as cdkExpect, haveResource } from '@aws-cdk/assert'; +import * as cdk from '@aws-cdk/core'; +import { ClusterParameterGroup } from '../lib'; + +test('create a cluster parameter group', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ClusterParameterGroup(stack, 'Params', { + description: 'desc', + parameters: { + param: 'value', + }, + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::Redshift::ClusterParameterGroup', { + Description: 'desc', + ParameterGroupFamily: 'redshift-1.0', + Parameters: [ + { + ParameterName: 'param', + ParameterValue: 'value', + }, + ], + })); + +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/test/redshift.test.ts b/packages/@aws-cdk/aws-redshift/test/redshift.test.ts deleted file mode 100644 index e394ef336bfb4..0000000000000 --- a/packages/@aws-cdk/aws-redshift/test/redshift.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assert/jest'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); From 9a54447f2a7d7e3a5d31a57bb3b2e2b0555430a1 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 27 May 2020 15:57:07 -0700 Subject: [PATCH 5/8] fix(app-delivery): could not use PipelineDeployStackAction more than once in a Stage (#8217) The PipelineDeployStackAction class hard-coded the names of the CloudFormation actions it created for preparing and executing the ChangeSet. Which meant this composite action could never be used more than once in the same stage (as that would result in errors saying the action names were duplicated). Allow overriding the names of the actions to make it possible to be used more than once in the same CodePipeline stage. Fixes #3984 Fixes #8183 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/pipeline-deploy-stack-action.ts | 18 ++++++++- .../test/test.pipeline-deploy-stack-action.ts | 39 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index 713b662808ad4..03685cfc9c413 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -32,6 +32,13 @@ export interface PipelineDeployStackActionProps { */ readonly createChangeSetRunOrder?: number; + /** + * The name of the CodePipeline action creating the ChangeSet. + * + * @default 'ChangeSet' + */ + readonly createChangeSetActionName?: string; + /** * The runOrder for the CodePipeline action executing the ChangeSet. * @@ -39,6 +46,13 @@ export interface PipelineDeployStackActionProps { */ readonly executeChangeSetRunOrder?: number; + /** + * The name of the CodePipeline action creating the ChangeSet. + * + * @default 'Execute' + */ + readonly executeChangeSetActionName?: string; + /** * IAM role to assume when deploying changes. * @@ -116,7 +130,7 @@ export class PipelineDeployStackAction implements codepipeline.IAction { const changeSetName = props.changeSetName || 'CDK-CodePipeline-ChangeSet'; const capabilities = cfnCapabilities(props.adminPermissions, props.capabilities); this.prepareChangeSetAction = new cpactions.CloudFormationCreateReplaceChangeSetAction({ - actionName: 'ChangeSet', + actionName: props.createChangeSetActionName ?? 'ChangeSet', changeSetName, runOrder: createChangeSetRunOrder, stackName: props.stack.stackName, @@ -126,7 +140,7 @@ export class PipelineDeployStackAction implements codepipeline.IAction { capabilities, }); this.executeChangeSetAction = new cpactions.CloudFormationExecuteChangeSetAction({ - actionName: 'Execute', + actionName: props.executeChangeSetActionName ?? 'Execute', changeSetName, runOrder: executeChangeSetRunOrder, stackName: this.stack.stackName, diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index d765eb887c14a..918279b480b30 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, isSuperObject } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, isSuperObject } from '@aws-cdk/assert'; import * as cfn from '@aws-cdk/aws-cloudformation'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; @@ -406,6 +406,43 @@ export = nodeunit.testCase({ ); test.done(); }, + + 'allows overriding the ChangeSet and Execute action names'(test: nodeunit.Test) { + const stack = getTestStack(); + const selfUpdatingPipeline = createSelfUpdatingStack(stack); + selfUpdatingPipeline.pipeline.addStage({ + stageName: 'Deploy', + actions: [ + new PipelineDeployStackAction({ + input: selfUpdatingPipeline.synthesizedApp, + adminPermissions: true, + stack, + createChangeSetActionName: 'Prepare', + executeChangeSetActionName: 'Deploy', + }), + ], + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + {}, + {}, + { + Name: 'Deploy', + Actions: [ + { + Name: 'Prepare', + }, + { + Name: 'Deploy', + }, + ], + }, + ], + })); + + test.done(); + }, }); class FakeAction implements codepipeline.IAction { From ac001b86bbff1801005cac1509e4480a30bf8f15 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 27 May 2020 18:15:33 -0700 Subject: [PATCH 6/8] fix(codepipeline): correctly handle CODEBUILD_CLONE_REF in BitBucket source (#7107) As it turns out, when using the `OutputArtifactFormat` equal to `CODEBUILD_CLONE_REF` in the BitBucket source action requires the subsequent CodeBuild project to have UseConnection permissions. Use CodePipeline's `Artifact` class to transfer that information between the source and build actions, by adding the capability to store arbitrary metadata inside the `Artifact` class. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/bitbucket/source-action.ts | 16 ++++ .../lib/codebuild/build-action.ts | 14 +++ .../bitbucket/test.bitbucket-source-action.ts | 93 +++++++++++++------ .../@aws-cdk/aws-codepipeline/lib/artifact.ts | 20 ++++ 4 files changed, 117 insertions(+), 26 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts index 9c005cc849edc..6fb8770796824 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts @@ -69,6 +69,14 @@ export interface BitBucketSourceActionProps extends codepipeline.CommonAwsAction * @experimental */ export class BitBucketSourceAction extends Action { + /** + * The name of the property that holds the ARN of the CodeStar Connection + * inside of the CodePipeline Artifact's metadata. + * + * @internal + */ + public static readonly _CONNECTION_ARN_PROPERTY = 'CodeStarConnectionArnProperty'; + private readonly props: BitBucketSourceActionProps; constructor(props: BitBucketSourceActionProps) { @@ -98,6 +106,14 @@ export class BitBucketSourceAction extends Action { // the action needs to write the output to the pipeline bucket options.bucket.grantReadWrite(options.role); + // if codeBuildCloneOutput is true, + // save the connectionArn in the Artifact instance + // to be read by the CodeBuildAction later + if (this.props.codeBuildCloneOutput === true) { + this.props.output.setMetadata(BitBucketSourceAction._CONNECTION_ARN_PROPERTY, + this.props.connectionArn); + } + return { configuration: { ConnectionArn: this.props.connectionArn, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts index 48bdfed738c31..53d789b665262 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts @@ -2,6 +2,7 @@ import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { BitBucketSourceAction } from '..'; import { Action } from '../action'; /** @@ -153,6 +154,19 @@ export class CodeBuildAction extends Action { }); } + // if any of the inputs come from the BitBucketSourceAction + // with codeBuildCloneOutput=true, + // grant the Project's Role to use the connection + for (const inputArtifact of this.actionProperties.inputs || []) { + const connectionArn = inputArtifact.getMetadata(BitBucketSourceAction._CONNECTION_ARN_PROPERTY); + if (connectionArn) { + this.props.project.addToRolePolicy(new iam.PolicyStatement({ + actions: ['codestar-connections:UseConnection'], + resources: [connectionArn], + })); + } + } + const configuration: any = { ProjectName: this.props.project.projectName, EnvironmentVariables: this.props.environmentVariables && diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts index 90ed1a4159134..f245a720a2fd9 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/test.bitbucket-source-action.ts @@ -12,32 +12,8 @@ export = { 'produces the correct configuration when added to a pipeline'(test: Test) { const stack = new Stack(); - const sourceOutput = new codepipeline.Artifact(); - new codepipeline.Pipeline(stack, 'Pipeline', { - stages: [ - { - stageName: 'Source', - actions: [ - new cpactions.BitBucketSourceAction({ - actionName: 'BitBucket', - owner: 'aws', - repo: 'aws-cdk', - output: sourceOutput, - connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh', - }), - ], - }, - { - stageName: 'Build', - actions: [ - new cpactions.CodeBuildAction({ - actionName: 'CodeBuild', - project: new codebuild.PipelineProject(stack, 'MyProject'), - input: sourceOutput, - }), - ], - }, - ], + createBitBucketAndCodeBuildPipeline(stack, { + codeBuildCloneOutput: false, }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -73,4 +49,69 @@ export = { test.done(); }, }, + + 'setting codeBuildCloneOutput=true adds permission to use the connection to the following CodeBuild Project'(test: Test) { + const stack = new Stack(); + + createBitBucketAndCodeBuildPipeline(stack, { + codeBuildCloneOutput: true, + }); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + }, + {}, + {}, + {}, + {}, + { + 'Action': 'codestar-connections:UseConnection', + 'Effect': 'Allow', + 'Resource': 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh', + }, + ], + }, + })); + + test.done(); + }, }; + +function createBitBucketAndCodeBuildPipeline(stack: Stack, props: { codeBuildCloneOutput: boolean }): void { + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.BitBucketSourceAction({ + actionName: 'BitBucket', + owner: 'aws', + repo: 'aws-cdk', + output: sourceOutput, + connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh', + codeBuildCloneOutput: props.codeBuildCloneOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'CodeBuild', + project: new codebuild.PipelineProject(stack, 'MyProject'), + input: sourceOutput, + outputs: [new codepipeline.Artifact()], + }), + ], + }, + ], + }); +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts index 79339691272b6..fab9b46edcfe6 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts @@ -17,6 +17,7 @@ export class Artifact { } private _artifactName?: string; + private readonly metadata: { [key: string]: any } = {}; constructor(artifactName?: string) { validation.validateArtifactName(artifactName); @@ -80,6 +81,25 @@ export class Artifact { }; } + /** + * Add arbitrary extra payload to the artifact under a given key. + * This can be used by CodePipeline actions to communicate data between themselves. + * If metadata was already present under the given key, + * it will be overwritten with the new value. + */ + public setMetadata(key: string, value: any): void { + this.metadata[key] = value; + } + + /** + * Retrieve the metadata stored in this artifact under the given key. + * If there is no metadata stored under the given key, + * null will be returned. + */ + public getMetadata(key: string): any { + return this.metadata[key]; + } + public toString() { return this.artifactName; } From 49c9f99c4dfd73bf53a461a844a1d9b0c02d3761 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 28 May 2020 10:12:57 +0100 Subject: [PATCH 7/8] feat(cognito): addDomain() on an imported user pool (#8123) In addition, reduce code duplication by introducing an abstract UserPoolBase class. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 61 +++++++++---------- .../aws-cognito/test/user-pool.test.ts | 29 +++++++++ 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 24fef0a42db70..a534eefbc509a 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -526,33 +526,52 @@ export interface IUserPool extends IResource { readonly userPoolArn: string; /** - * Create a user pool client. + * Add a new app client to this user pool. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html */ addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient; + + /** + * Associate a domain to this user pool. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html + */ + addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain; +} + +abstract class UserPoolBase extends Resource implements IUserPool { + public abstract readonly userPoolId: string; + public abstract readonly userPoolArn: string; + + public addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient { + return new UserPoolClient(this, id, { + userPool: this, + ...options, + }); + } + + public addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain { + return new UserPoolDomain(this, id, { + userPool: this, + ...options, + }); + } } /** * Define a Cognito User Pool */ -export class UserPool extends Resource implements IUserPool { +export class UserPool extends UserPoolBase { /** * Import an existing user pool based on its id. */ public static fromUserPoolId(scope: Construct, id: string, userPoolId: string): IUserPool { - class Import extends Resource implements IUserPool { + class Import extends UserPoolBase { public readonly userPoolId = userPoolId; public readonly userPoolArn = Stack.of(this).formatArn({ service: 'cognito-idp', resource: 'userpool', resourceName: userPoolId, }); - - public addClient(clientId: string, options?: UserPoolClientOptions): IUserPoolClient { - return new UserPoolClient(this, clientId, { - userPool: this, - ...options, - }); - } } return new Import(scope, id); } @@ -669,28 +688,6 @@ export class UserPool extends Resource implements IUserPool { (this.triggers as any)[operation.operationName] = fn.functionArn; } - /** - * Add a new app client to this user pool. - * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html - */ - public addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient { - return new UserPoolClient(this, id, { - userPool: this, - ...options, - }); - } - - /** - * Associate a domain to this user pool. - * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html - */ - public addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain { - return new UserPoolDomain(this, id, { - userPool: this, - ...options, - }); - } - private addLambdaPermission(fn: lambda.IFunction, name: string): void { const capitalize = name.charAt(0).toUpperCase() + name.slice(1); fn.addPermission(`${capitalize}Cognito`, { diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index e076d9e79bd2f..83d4863b751c3 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -818,6 +818,35 @@ test('addClient', () => { }); }); +test('addDomain', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const userpool = new UserPool(stack, 'Pool'); + userpool.addDomain('UserPoolDomain', { + cognitoDomain: { + domainPrefix: 'userpooldomain', + }, + }); + const imported = UserPool.fromUserPoolId(stack, 'imported', 'imported-userpool-id'); + imported.addDomain('UserPoolImportedDomain', { + cognitoDomain: { + domainPrefix: 'userpoolimporteddomain', + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolDomain', { + Domain: 'userpooldomain', + UserPoolId: stack.resolve(userpool.userPoolId), + }); + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolDomain', { + Domain: 'userpoolimporteddomain', + UserPoolId: stack.resolve(imported.userPoolId), + }); +}); + function fooFunction(scope: Construct, name: string): lambda.IFunction { return new lambda.Function(scope, name, { functionName: name, From 59ca0d1f38f1bed50f94f783ed298dccef720b4a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 28 May 2020 17:56:29 +0000 Subject: [PATCH 8/8] chore(deps-dev): bump @types/lodash from 4.14.152 to 4.14.153 (#8259) Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.152 to 4.14.153. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-codepipeline-actions/package.json | 2 +- packages/@aws-cdk/aws-lambda/package.json | 2 +- packages/@aws-cdk/core/package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index af13cef9e8ade..8f8cb92d237ef 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-cloudtrail": "0.0.0", - "@types/lodash": "^4.14.152", + "@types/lodash": "^4.14.153", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 292d921efb0fa..367e4dc8206d9 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/aws-lambda": "^8.10.39", - "@types/lodash": "^4.14.152", + "@types/lodash": "^4.14.153", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.3", "aws-sdk": "^2.681.0", diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 485821ebe8fb7..a0927130ac58f 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -151,7 +151,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/lodash": "^4.14.152", + "@types/lodash": "^4.14.153", "@types/node": "^10.17.21", "@types/nodeunit": "^0.0.31", "@types/minimatch": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index dfaf923f05271..657b0b9087378 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1507,10 +1507,10 @@ dependencies: jszip "*" -"@types/lodash@^4.14.152": - version "4.14.152" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.152.tgz#7e7679250adce14e749304cdb570969f77ec997c" - integrity sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg== +"@types/lodash@^4.14.153": + version "4.14.153" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.153.tgz#5cb7dded0649f1df97938ac5ffc4f134e9e9df98" + integrity sha512-lYniGRiRfZf2gGAR9cfRC3Pi5+Q1ziJCKqPmjZocigrSJUVPWf7st1BtSJ8JOeK0FLXVndQ1IjUjTco9CXGo/Q== "@types/md5@^2.2.0": version "2.2.0"