diff --git a/.github/workflows/auto-approve-v2-merge-forward.yml b/.github/workflows/auto-approve-v2-merge-forward.yml new file mode 100644 index 0000000000000..96dd3d0837e6e --- /dev/null +++ b/.github/workflows/auto-approve-v2-merge-forward.yml @@ -0,0 +1,26 @@ +# Automatically approve PRs that merge master forward to v2-main +# +# Only does approvals! mergify takes care of the actual merge. +name: Auto-approve forward merges onto v2-main +on: + pull_request: + types: + - labeled + - opened + - ready_for_review + - reopened + - synchronize + - unlabeled + - unlocked + +jobs: + approve: + runs-on: ubuntu-latest + steps: + - uses: hmarr/auto-approve-action@v2.0.0 + if: > + github.event.pull_request.user.login == 'aws-cdk-automation' + && github.event.pull_request.base.ref == 'v2-main' + && contains(github.event.pull_request.labels.*.name, 'pr/forward-merge') + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index 055b85911775d..28ed33d0064b2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,8 @@ yarn-error.log # Parcel default cache directory .parcel-cache +# VSCode history plugin +.vscode/.history/ + # Cloud9 .c9 diff --git a/.gitpod.yml b/.gitpod.yml index 61bf069e34517..2e63da1c1cb98 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,10 @@ +github: + prebuilds: + pullRequestsFromForks: true + addComment: true + image: jsii/superchain + tasks: - init: yarn build --skip-test --no-bail --skip-prereqs --skip-compat diff --git a/.mergify.yml b/.mergify.yml index ee41466232847..2675841005330 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -19,6 +19,7 @@ pull_request_rules: commit_message: title+body conditions: - base!=release + - -base~=^v2 - -title~=(WIP|wip) - -label~=(blocked|do-not-merge|no-squash|two-approvers) - -merged @@ -41,6 +42,7 @@ pull_request_rules: commit_message: title+body conditions: - base!=release + - -base~=^v2 - -title~=(WIP|wip) - label~=two-approvers - -label~=(blocked|do-not-merge|no-squash) @@ -64,6 +66,7 @@ pull_request_rules: strict_method: merge commit_message: title+body conditions: + - -base~=^v2 - -title~=(WIP|wip) - -label~=(blocked|do-not-merge) # Only if no-squash is set @@ -119,3 +122,21 @@ pull_request_rules: - "#changes-requested-reviews-by=0" - status-success~=AWS CodeBuild us-east-1 - status-success=validate-pr + - name: automatic merge of v2 forward merges + actions: + comment: + message: Forward merge successful! + merge: + method: merge + strict: smart+fasttrack + strict_method: merge + commit_message: title+body + conditions: + - base=v2-main + - label~=forward-merge + - -label~=(blocked|do-not-merge) + - -merged + - -closed + - author~=aws-cdk-automation + - "#approved-reviews-by>=1" + - status-success~=AWS CodeBuild us-east-1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8eeebf578f4..84e65d57fc71e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,116 @@ 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.69.0](https://github.com/aws/aws-cdk/compare/v1.68.0...v1.69.0) (2020-10-19) + + +### Features + +* **apigatewayv2:** configure description for HttpApi ([#10863](https://github.com/aws/aws-cdk/issues/10863)) ([895372f](https://github.com/aws/aws-cdk/commit/895372fc8b027bd12d64450c429c04d8efdd27f4)) +* **pipelines:** temporarily disable self-mutation ([#10466](https://github.com/aws/aws-cdk/issues/10466)) ([8ffabb4](https://github.com/aws/aws-cdk/commit/8ffabb4325d2853f8650f991706eccfe233b2c74)) + + +### Bug Fixes + +* **apigateway:** cannot configure stage for SpecRestApi ([#10749](https://github.com/aws/aws-cdk/issues/10749)) ([62a2286](https://github.com/aws/aws-cdk/commit/62a2286f6dc46059160daa3c7466e712dad9f136)), closes [#10300](https://github.com/aws/aws-cdk/issues/10300) +* **apigateway:** lambda integration does not recognize allowTestInvoke ([#10828](https://github.com/aws/aws-cdk/issues/10828)) ([650c23f](https://github.com/aws/aws-cdk/commit/650c23f1fe9e87a7b1eb521faf57c7ed341d0eb6)), closes [#7605](https://github.com/aws/aws-cdk/issues/7605) [#7604](https://github.com/aws/aws-cdk/issues/7604) +* **cli:** `cdk context --reset ` does not work ([#10753](https://github.com/aws/aws-cdk/issues/10753)) ([2f3a167](https://github.com/aws/aws-cdk/commit/2f3a167797e60fd2df6c83bc2f3906ddc8eb8966)), closes [#3033](https://github.com/aws/aws-cdk/issues/3033) [#10619](https://github.com/aws/aws-cdk/issues/10619) +* **cli:** failure if account cache is malformed ([#10887](https://github.com/aws/aws-cdk/issues/10887)) ([9b2438a](https://github.com/aws/aws-cdk/commit/9b2438a6e78fc7a9622e79b1435ea6f8b76d98f7)) +* **lambda-python:** asset hashes changed ([#10959](https://github.com/aws/aws-cdk/issues/10959)) ([b8de264](https://github.com/aws/aws-cdk/commit/b8de264af75aed11e14fb715f40d6d2e41d5233e)), closes [#10958](https://github.com/aws/aws-cdk/issues/10958) [#10957](https://github.com/aws/aws-cdk/issues/10957) + +## [1.68.0](https://github.com/aws/aws-cdk/compare/v1.67.0...v1.68.0) (2020-10-15) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **config:** `scopeToResource()`, `scopeToResources()` and `scopeToTag()` APIs have been removed. Use the `ruleScope` property to restrict the scope of a Config rule. `fromResource()`, `fromResources()` and `fromTag()` can be used from the `RuleScope` class. +* **cloudfront:** `Distribution` behaviors now enable compression by default +* **cloudfront:** Distribution `forwardQueryString` and `forwardQueryStringCacheKeys` have been removed in favor of `cachePolicy` and the new CachePolicy construct. +* **cloudfront:** Distributions now default to the "CachingOptimized" managed cache policy + +### Features + +* **apigateway:** autodetermine the private integration uri ([#10730](https://github.com/aws/aws-cdk/issues/10730)) ([46df4a7](https://github.com/aws/aws-cdk/commit/46df4a7c51843542bc79d2c1b3f211548ac39ab5)), closes [#10435](https://github.com/aws/aws-cdk/issues/10435) +* **apigateway:** grant methods to ApiKey ([#10633](https://github.com/aws/aws-cdk/issues/10633)) ([23f77fd](https://github.com/aws/aws-cdk/commit/23f77fd504891d8c0a3e21613ee426adfb128313)), closes [#8060](https://github.com/aws/aws-cdk/issues/8060) +* **apigateway:** metric methods for RestApi ([#10667](https://github.com/aws/aws-cdk/issues/10667)) ([45b1e36](https://github.com/aws/aws-cdk/commit/45b1e360cd5913806744880d5c4f427932da065e)), closes [#8321](https://github.com/aws/aws-cdk/issues/8321) +* **apigateway:** mTLS support ([#10521](https://github.com/aws/aws-cdk/issues/10521)) ([eb2c568](https://github.com/aws/aws-cdk/commit/eb2c568e6b87c66422137c2ef147011205195e0b)), closes [#10487](https://github.com/aws/aws-cdk/issues/10487) +* **apigatewayv2:** http api - metric methods for api and stage ([#10686](https://github.com/aws/aws-cdk/issues/10686)) ([aae5d1d](https://github.com/aws/aws-cdk/commit/aae5d1d79d3771b0ea097a8350daf3a5a7db2b04)), closes [#10325](https://github.com/aws/aws-cdk/issues/10325) [#10726](https://github.com/aws/aws-cdk/issues/10726) +* **appsync:** MappingTemplate.dynamoDbQuery - add ability to specify secondary index ([#10647](https://github.com/aws/aws-cdk/issues/10647)) ([346dbf4](https://github.com/aws/aws-cdk/commit/346dbf4fe859e29f2301b9ed17e50376035f44b1)) +* **aws-ec2:** vpc flow log s3 bucket prefix support ([#10779](https://github.com/aws/aws-cdk/issues/10779)) ([11ce726](https://github.com/aws/aws-cdk/commit/11ce726b760b8d72953f4ae2239d62aa89eaa894)), closes [#10778](https://github.com/aws/aws-cdk/issues/10778) +* **aws-ecs-builder:** add public ip support ([#10646](https://github.com/aws/aws-cdk/issues/10646)) ([cf26821](https://github.com/aws/aws-cdk/commit/cf268218163a6d25cc6bbe7375b9332f87062631)), closes [#10644](https://github.com/aws/aws-cdk/issues/10644) +* **cfnspec:** cloudformation spec v18.7.0 ([#10864](https://github.com/aws/aws-cdk/issues/10864)) ([0bb133e](https://github.com/aws/aws-cdk/commit/0bb133e8b500be3802bdfeb9c35ef61ad4687223)) +* **cloudfront:** Distribution is now in Developer Preview ([#10831](https://github.com/aws/aws-cdk/issues/10831)) ([fe8d5e6](https://github.com/aws/aws-cdk/commit/fe8d5e67f9e53c8f74a304e27cb668354699cc48)) +* **cloudfront:** support for cache policies ([#10656](https://github.com/aws/aws-cdk/issues/10656)) ([5a97d27](https://github.com/aws/aws-cdk/commit/5a97d2757d8f609b8f185b09f14e0eebf9c0dfa0)), closes [#9644](https://github.com/aws/aws-cdk/issues/9644) +* **cloudfront:** support for origin request policies ([#10765](https://github.com/aws/aws-cdk/issues/10765)) ([08efc96](https://github.com/aws/aws-cdk/commit/08efc9629337c9e3daf33306af563da95e29a910)), closes [#10656](https://github.com/aws/aws-cdk/issues/10656) [#10656](https://github.com/aws/aws-cdk/issues/10656) [#9647](https://github.com/aws/aws-cdk/issues/9647) +* **codedeploy:** Custom lambda deployment config ([#10462](https://github.com/aws/aws-cdk/issues/10462)) ([60ab50f](https://github.com/aws/aws-cdk/commit/60ab50f98c66154b8129e8309314d8d1aee2682c)) +* **cognito:** user pool identity support for Google ([#10649](https://github.com/aws/aws-cdk/issues/10649)) ([49ede22](https://github.com/aws/aws-cdk/commit/49ede22b35e54b0aa4541964df84aa4b4e76a985)) +* **config:** convenience class with static constants for referencing AWS managed rules ([#10834](https://github.com/aws/aws-cdk/issues/10834)) ([85738de](https://github.com/aws/aws-cdk/commit/85738de5be9b9e7ce2e5c2ce9db95f253a2d101f)) +* **config:** Scope class for scoping config rules to a specific resource, resource types, tags ([#10821](https://github.com/aws/aws-cdk/issues/10821)) ([25eb1c2](https://github.com/aws/aws-cdk/commit/25eb1c2af6c963f1a3c0a4f63dab210f14723b14)) +* **config:** the AWS Config Construct Library is now Generally Available (stable) ([#10875](https://github.com/aws/aws-cdk/issues/10875)) ([88e1cd9](https://github.com/aws/aws-cdk/commit/88e1cd96e92fe9d1b0b230eca1b7c3c7ae3d9501)) +* **core:** `BundlingDockerImage` now supports `run()` and `cp()` utilities ([#9728](https://github.com/aws/aws-cdk/issues/9728)) ([37fdc94](https://github.com/aws/aws-cdk/commit/37fdc94a9fdcaf37e02e66d6f1bf3c5501aee1a0)), closes [#9329](https://github.com/aws/aws-cdk/issues/9329) +* **ec2:** add c5a instance class ([240d4b5](https://github.com/aws/aws-cdk/commit/240d4b54bee4a0ac3bff0b92d5340493e5c55ed8)) +* **ec2:** t4g instances ([#10817](https://github.com/aws/aws-cdk/issues/10817)) ([5e0cd2b](https://github.com/aws/aws-cdk/commit/5e0cd2b01a3b30e471b94fd4ba0ea82c36a3bbcd)), closes [#10816](https://github.com/aws/aws-cdk/issues/10816) +* **eks:** Auto select AMI type for T4g instance type ([#10360](https://github.com/aws/aws-cdk/issues/10360)) ([a4bac34](https://github.com/aws/aws-cdk/commit/a4bac34d5b9c76dd7de3f7b4f3745121bef5927e)), closes [#10361](https://github.com/aws/aws-cdk/issues/10361) +* **eks:** Support KubernetesVersion 1.18 ([#10854](https://github.com/aws/aws-cdk/issues/10854)) ([25897d6](https://github.com/aws/aws-cdk/commit/25897d6ded4143f44c003a9dc0431bdbfa96912e)), closes [#10853](https://github.com/aws/aws-cdk/issues/10853) +* **events-targets:** allow passing a role to the CodeBuild target ([#10865](https://github.com/aws/aws-cdk/issues/10865)) ([f085a09](https://github.com/aws/aws-cdk/commit/f085a09790b1564c7e44a9a964866ce454c1d865)) +* **lambda-python:** bundle dependencies in a lambda layer ([#9582](https://github.com/aws/aws-cdk/issues/9582)) ([aebac92](https://github.com/aws/aws-cdk/commit/aebac9261dfaa37795a91ddced164bb6a0841f13)), closes [#9406](https://github.com/aws/aws-cdk/issues/9406) [#9944](https://github.com/aws/aws-cdk/issues/9944) +* **pipelines:** support SecurityGroups for ShellScriptAction ([#10770](https://github.com/aws/aws-cdk/issues/10770)) ([f9afbc5](https://github.com/aws/aws-cdk/commit/f9afbc561d59ebbef0bec3fb018e40626244a28f)), closes [#10621](https://github.com/aws/aws-cdk/issues/10621) +* **s3:** Support virtual-hosted style bucket URLs ([#10326](https://github.com/aws/aws-cdk/issues/10326)) ([227fb81](https://github.com/aws/aws-cdk/commit/227fb81b00ae60c2f784610a398c63a0a0d6f71a)), closes [#10319](https://github.com/aws/aws-cdk/issues/10319) +* **secretsmanager:** hosted rotation ([#10790](https://github.com/aws/aws-cdk/issues/10790)) ([2cb8e22](https://github.com/aws/aws-cdk/commit/2cb8e22221b266b90b3a0b6c198a0da6ff4e3b8a)) + + +### Bug Fixes + +* **cloudfront:** compression disabled by default for Distribution ([#10794](https://github.com/aws/aws-cdk/issues/10794)) ([3327b7f](https://github.com/aws/aws-cdk/commit/3327b7faafbed98b2a8da2caf769206f5c004d56)) +* **codebuild:** add BatchPutCodeCoverages permission to Project by default ([#10835](https://github.com/aws/aws-cdk/issues/10835)) ([dec8e07](https://github.com/aws/aws-cdk/commit/dec8e07e41564abf20b554809df210b3777bbfeb)) +* **core:** CfnCodeDeployBlueGreenHook outputs empty optional objects to the template ([#10809](https://github.com/aws/aws-cdk/issues/10809)) ([d88f034](https://github.com/aws/aws-cdk/commit/d88f0347a2a41845f4b27dacaee8e212822dfa85)), closes [#10803](https://github.com/aws/aws-cdk/issues/10803) +* **core:** partial wildcards don't work with selective bundling ([#10767](https://github.com/aws/aws-cdk/issues/10767)) ([f7ce079](https://github.com/aws/aws-cdk/commit/f7ce0796eae621e6974658959cdbf11822d39eb2)), closes [#10732](https://github.com/aws/aws-cdk/issues/10732) +* **dynamodb:** grantTableListStreams() permissions have incorrect Resource ([#10631](https://github.com/aws/aws-cdk/issues/10631)) ([b2f16b3](https://github.com/aws/aws-cdk/commit/b2f16b3c051406b58961327b7d4924d61ea09057)), closes [#9511](https://github.com/aws/aws-cdk/issues/9511) +* **ec2:** VPN preSharedKey cannot be a Token ([#10725](https://github.com/aws/aws-cdk/issues/10725)) ([55fa055](https://github.com/aws/aws-cdk/commit/55fa0555b78e69d99432018975adb5e03ab12bff)), closes [#10723](https://github.com/aws/aws-cdk/issues/10723) +* **events:** cannot use tokens as event bus name ([#10772](https://github.com/aws/aws-cdk/issues/10772)) ([8bee193](https://github.com/aws/aws-cdk/commit/8bee193fe66ed0dc3d7751181471a9eb9abae3cc)), closes [#10467](https://github.com/aws/aws-cdk/issues/10467) +* **lambda:** grantInvoke on imported function fails ([#10622](https://github.com/aws/aws-cdk/issues/10622)) ([99111f7](https://github.com/aws/aws-cdk/commit/99111f72adc210f48e269db50f2b8e8b78d21252)), closes [#8828](https://github.com/aws/aws-cdk/issues/8828) [#10607](https://github.com/aws/aws-cdk/issues/10607) +* **lambda-nodejs:** cache performance ([#10763](https://github.com/aws/aws-cdk/issues/10763)) ([d34773f](https://github.com/aws/aws-cdk/commit/d34773f19f803d4d5dbc20607b319762ace0a45a)) +* **s3:** correct write permission with key decrypt ([#10679](https://github.com/aws/aws-cdk/issues/10679)) ([c39cf95](https://github.com/aws/aws-cdk/commit/c39cf95b87d013a4ccc567e9900518b9638bf208)), closes [#8947](https://github.com/aws/aws-cdk/issues/8947) +* **s3-deployment:** User metadata was added with wrong prefix (x-amzn-meta-) ([#10678](https://github.com/aws/aws-cdk/issues/10678)) ([6b3687c](https://github.com/aws/aws-cdk/commit/6b3687c30ceb500d83da8f8a7b8fd8ea7f4ae7b6)), closes [#8459](https://github.com/aws/aws-cdk/issues/8459) + +## [1.67.0](https://github.com/aws/aws-cdk/compare/v1.66.0...v1.67.0) (2020-10-07) + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **monodk-experiment:** This package is now deprected in favor of `monocdk`. Note that `monocdk` is still experimental. + +### Features + +* **aws-ec2:** KINESIS_FIREHOSE vpc endpoint ([#10682](https://github.com/aws/aws-cdk/issues/10682)) ([08ae745](https://github.com/aws/aws-cdk/commit/08ae74569eb52fb73f202b770d652a941fd61ea4)), closes [#10611](https://github.com/aws/aws-cdk/issues/10611) +* **cfnspec:** cloudformation spec v18.6.0 ([#10762](https://github.com/aws/aws-cdk/issues/10762)) ([6078cab](https://github.com/aws/aws-cdk/commit/6078cab14dcc6d974d4f0c92eab05e661bbc44e6)) +* **cloudfront-origins:** customize origin access identity in s3origin ([#10491](https://github.com/aws/aws-cdk/issues/10491)) ([dbb7e34](https://github.com/aws/aws-cdk/commit/dbb7e34eeb9f396003522fbee52f63f985826f70)), closes [#9859](https://github.com/aws/aws-cdk/issues/9859) +* **core:** pass environment variables to CustomResourceProvider ([#10560](https://github.com/aws/aws-cdk/issues/10560)) ([320ec72](https://github.com/aws/aws-cdk/commit/320ec72a30f06920ab24fb43afff6975992db71f)), closes [#9668](https://github.com/aws/aws-cdk/issues/9668) +* **efs:** add support for backup policy ([#10524](https://github.com/aws/aws-cdk/issues/10524)) ([41f6de2](https://github.com/aws/aws-cdk/commit/41f6de2b3fe6cb20a49fce2d3db0bd25812fd5d9)), closes [#10414](https://github.com/aws/aws-cdk/issues/10414) +* **eks:** Support cdk8s charts ([#10562](https://github.com/aws/aws-cdk/issues/10562)) ([e51921d](https://github.com/aws/aws-cdk/commit/e51921d1a81ba9d89e21567291b5f7b215726ca7)) +* **rds:** add clusterArn property to IServerlessCluster ([#10741](https://github.com/aws/aws-cdk/issues/10741)) ([1559fe9](https://github.com/aws/aws-cdk/commit/1559fe9d352f5ec90e13196ab6b5a74e7e479753)), closes [#10736](https://github.com/aws/aws-cdk/issues/10736) +* **readme:** deprecate Gitter in favor of cdk.dev Slack ([#10700](https://github.com/aws/aws-cdk/issues/10700)) ([c60764e](https://github.com/aws/aws-cdk/commit/c60764e28ddc93e5a6f818fdc8e6ae3c0aa09f91)) + + +### Bug Fixes + +* **cli:** 'stack already contains Metadata resource' warning ([#10695](https://github.com/aws/aws-cdk/issues/10695)) ([e0b5508](https://github.com/aws/aws-cdk/commit/e0b55086cf9061e579c3c804b4d2e169ec9d7ae2)), closes [#10625](https://github.com/aws/aws-cdk/issues/10625) +* **cli:** deploying a transformed template without changes fails ([#10689](https://github.com/aws/aws-cdk/issues/10689)) ([d345919](https://github.com/aws/aws-cdk/commit/d345919800d02b80f40802da60e567d4a30b3521)), closes [#10650](https://github.com/aws/aws-cdk/issues/10650) +* **cloudfront-origins:** S3Origins with cross-stack buckets cause cyclic references ([#10696](https://github.com/aws/aws-cdk/issues/10696)) ([0ec4588](https://github.com/aws/aws-cdk/commit/0ec45881e66f598ec37bb772cd01c30be4da96f8)), closes [#10399](https://github.com/aws/aws-cdk/issues/10399) +* **codepipeline-actions:** correctly name the triggering Event in CodeCommitSourceAction ([#10706](https://github.com/aws/aws-cdk/issues/10706)) ([ff3a692](https://github.com/aws/aws-cdk/commit/ff3a69285f1f3ae1ff6c6b1495192ce54e46d2fc)), closes [#10665](https://github.com/aws/aws-cdk/issues/10665) +* **core:** cannot override properties with `.` in the name ([#10441](https://github.com/aws/aws-cdk/issues/10441)) ([063798b](https://github.com/aws/aws-cdk/commit/063798bdde241285df155999a0a5795eead87703)), closes [#10109](https://github.com/aws/aws-cdk/issues/10109) +* **core:** Stacks from 3rd-party libraries do not synthesize correctly ([#10690](https://github.com/aws/aws-cdk/issues/10690)) ([7bb5cf4](https://github.com/aws/aws-cdk/commit/7bb5cf43113db76d7e5e0fec5643e2e4cd64d289)), closes [#10671](https://github.com/aws/aws-cdk/issues/10671) +* **ec2:** `addExecuteFileCommand` arguments cannot be omitted ([#10692](https://github.com/aws/aws-cdk/issues/10692)) ([7178374](https://github.com/aws/aws-cdk/commit/7178374a3e545083724af70fbd777fbaabd33b1c)), closes [#10687](https://github.com/aws/aws-cdk/issues/10687) +* **ec2:** `InitCommand.shellCommand()` renders an argv command instead ([#10691](https://github.com/aws/aws-cdk/issues/10691)) ([de9d2f7](https://github.com/aws/aws-cdk/commit/de9d2f77779f16ead3ab871b8d4a51d12c700ea2)), closes [#10684](https://github.com/aws/aws-cdk/issues/10684) +* **ec2:** memory optimised graviton2 instance type ([#10615](https://github.com/aws/aws-cdk/issues/10615)) ([a72cfbd](https://github.com/aws/aws-cdk/commit/a72cfbd5d0f9af87aacf0657a6fc370ce2a23c55)) +* **elbv2:** metric(Un)HealthyHostCount don't use TargetGroup dimension ([#10697](https://github.com/aws/aws-cdk/issues/10697)) ([9444399](https://github.com/aws/aws-cdk/commit/9444399b08825b4d1c5dce58e3c396c9941724c4)), closes [#5046](https://github.com/aws/aws-cdk/issues/5046) +* **glue:** GetTableVersion permission not available for read ([#10628](https://github.com/aws/aws-cdk/issues/10628)) ([b0c5699](https://github.com/aws/aws-cdk/commit/b0c56999be6a1756b95eb9d976b36598a91c8316)), closes [#10577](https://github.com/aws/aws-cdk/issues/10577) +* **glue:** incorrect s3 prefix used for grant* in Table ([#10627](https://github.com/aws/aws-cdk/issues/10627)) ([4d20079](https://github.com/aws/aws-cdk/commit/4d20079c6c6e8343a3807beb5dbaca841d77c3d6)), closes [#10582](https://github.com/aws/aws-cdk/issues/10582) +* **pipelines:** cannot use constructs in build environment ([#10654](https://github.com/aws/aws-cdk/issues/10654)) ([bf2c629](https://github.com/aws/aws-cdk/commit/bf2c6298358a0eaa7db161798798dda37b1154aa)), closes [#10535](https://github.com/aws/aws-cdk/issues/10535) +* **pipelines:** pipeline doesn't restart if CLI version changes ([#10727](https://github.com/aws/aws-cdk/issues/10727)) ([0297f31](https://github.com/aws/aws-cdk/commit/0297f31ef4224b374a340ba09eeafe963d62e789)), closes [#10659](https://github.com/aws/aws-cdk/issues/10659) +* **rds:** secret for ServerlessCluster is not accessible programmatically ([#10657](https://github.com/aws/aws-cdk/issues/10657)) ([028495e](https://github.com/aws/aws-cdk/commit/028495e55fed02f727024a443fc29a17d4629fe3)) +* **redshift:** Allow redshift cluster securityGroupName to be generated ([#10742](https://github.com/aws/aws-cdk/issues/10742)) ([effed09](https://github.com/aws/aws-cdk/commit/effed09854b6614e75077fd39be8aced69c33582)), closes [#10740](https://github.com/aws/aws-cdk/issues/10740) +* **stepfunctions:** X-Ray policy does not match documentation ([#10721](https://github.com/aws/aws-cdk/issues/10721)) ([8006459](https://github.com/aws/aws-cdk/commit/8006459b9d20542cb9c4d8ca3f10ef5938c67e74)) + ## [1.66.0](https://github.com/aws/aws-cdk/compare/v1.65.0...v1.66.0) (2020-10-02) ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3333987762d80..0c5137c77a4a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,13 +77,12 @@ you need to have the following SDKs and tools locally: - [Node.js >= 10.13.0](https://nodejs.org/download/release/latest-v10.x/) - We recommend using a version in [Active LTS](https://nodejs.org/en/about/releases/) - ⚠️ versions `13.0.0` to `13.6.0` are not supported due to compatibility issues with our dependencies. -- [Yarn >= 1.19.1, < 1.3](https://yarnpkg.com/lang/en/docs/install) +- [Yarn >= 1.19.1, < 2](https://yarnpkg.com/lang/en/docs/install) - [Java >= OpenJDK 8, 11, 14](https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html) - [Apache Maven >= 3.6.0, < 4.0](http://maven.apache.org/install.html) - [.NET Core SDK 3.1.x](https://www.microsoft.com/net/download) - [Python >= 3.6.5, < 4.0](https://www.python.org/downloads/release/python-365/) -- [Ruby >= 2.5.1, < 3.0](https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/) -- [Docker 19.03](https://docs.docker.com/get-docker/) +- [Docker >= 19.03](https://docs.docker.com/get-docker/) The basic commands to get the repository cloned and built locally follow: diff --git a/build.sh b/build.sh index a17b80797c8ec..13d525731b16e 100755 --- a/build.sh +++ b/build.sh @@ -37,6 +37,11 @@ done export PATH=$(npm bin):$PATH export NODE_OPTIONS="--max-old-space-size=4096 ${NODE_OPTIONS:-}" +if ! [ -x "$(command -v yarn)" ]; then + echo "yarn is not installed. Install it from here- https://yarnpkg.com/en/docs/install." + exit 1 +fi + echo "=============================================================================================" echo "installing..." yarn install --frozen-lockfile --network-timeout 1000000 @@ -65,11 +70,6 @@ rm -rf $BUILD_INDICATOR export MERKLE_BUILD_CACHE=$(mktemp -d) trap "rm -rf $MERKLE_BUILD_CACHE" EXIT -if ! [ -x "$(command -v yarn)" ]; then - echo "yarn is not installed. Install it from here- https://yarnpkg.com/en/docs/install." - exit 1 -fi - echo "=============================================================================================" echo "building..." time lerna run $bail --stream $runtarget || fail diff --git a/buildspec.yaml b/buildspec.yaml index 8196752082e93..a3e28f521d25c 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -18,6 +18,7 @@ phases: - /bin/bash ./fetch-dotnet-snk.sh build: commands: + - 'if ${BUMP_CANDIDATE:-false}; then /bin/bash ./scripts/bump-candidate.sh; fi' - /bin/bash ./scripts/align-version.sh - /bin/bash ./build.sh post_build: diff --git a/lerna.json b/lerna.json index c1ba78ef181d7..b19998dd19695 100644 --- a/lerna.json +++ b/lerna.json @@ -11,5 +11,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.66.0" + "version": "1.69.0" } diff --git a/package.json b/package.json index e9cc508495fa6..0e9d0006b5941 100644 --- a/package.json +++ b/package.json @@ -82,18 +82,18 @@ "aws-cdk-lib/semver/**", "aws-cdk-lib/yaml", "aws-cdk-lib/yaml/**", - "monocdk-experiment/case", - "monocdk-experiment/case/**", - "monocdk-experiment/fs-extra", - "monocdk-experiment/fs-extra/**", - "monocdk-experiment/jsonschema", - "monocdk-experiment/jsonschema/**", - "monocdk-experiment/minimatch", - "monocdk-experiment/minimatch/**", - "monocdk-experiment/semver", - "monocdk-experiment/semver/**", - "monocdk-experiment/yaml", - "monocdk-experiment/yaml/**" + "monocdk/case", + "monocdk/case/**", + "monocdk/fs-extra", + "monocdk/fs-extra/**", + "monocdk/jsonschema", + "monocdk/jsonschema/**", + "monocdk/minimatch", + "monocdk/minimatch/**", + "monocdk/semver", + "monocdk/semver/**", + "monocdk/yaml", + "monocdk/yaml/**" ] } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore b/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore index 937533073b465..025c9003cf155 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore +++ b/packages/@aws-cdk-containers/ecs-service-extensions/.gitignore @@ -6,6 +6,9 @@ dist lib/generated/resources.ts .jsii +.coverage +__pycache__ + .LAST_BUILD .nyc_output coverage @@ -14,4 +17,4 @@ nyc.config.js *.snk !.eslintrc.js -junit.xml \ No newline at end of file +junit.xml diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore b/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore index 913c96cd9ae15..722d4070cdc1f 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore +++ b/packages/@aws-cdk-containers/ecs-service-extensions/.npmignore @@ -22,3 +22,5 @@ tsconfig.json **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index 7df503cf6a139..4de08fec00b23 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -114,6 +114,7 @@ nameDescription.add(new FireLensExtension()); nameDescription.add(new XRayExtension()); nameDescription.add(new CloudwatchAgentExtension()); nameDescription.add(new HttpLoadBalancerExtension()); +nameDescription.add(new AssignPublicIpExtension()); ``` ## Launching the `ServiceDescription` as a `Service` diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/assign-public-ip.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/assign-public-ip.ts new file mode 100644 index 0000000000000..747b06dc2d72a --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/assign-public-ip.ts @@ -0,0 +1,85 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; +import { Service } from '../../service'; +import { Container } from '../container'; +import { ServiceExtension, ServiceBuild, EnvironmentCapacityType } from '../extension-interfaces'; +import { TaskRecordManager } from './task-record-manager'; + +export interface AssignPublicIpExtensionOptions { + /** + * Enable publishing task public IPs to a recordset in a Route 53 hosted zone. + * + * Note: If you want to change the DNS zone or record name, you will need to + * remove this extension completely and then re-add it. + */ + dns?: AssignPublicIpDnsOptions; +} + +export interface AssignPublicIpDnsOptions { + /** + * A DNS Zone to expose task IPs in. + */ + zone: route53.IHostedZone; + + /** + * Name of the record to add to the zone and in which to add the task IP + * addresses to. + * + * @example 'myservice' + */ + recordName: string; +} + +/** + * Modifies the service to assign a public ip to each task and optionally + * exposes public IPs in a Route 53 record set. + * + * Note: If you want to change the DNS zone or record name, you will need to + * remove this extension completely and then re-add it. + */ +export class AssignPublicIpExtension extends ServiceExtension { + dns?: AssignPublicIpDnsOptions; + + constructor(options?: AssignPublicIpExtensionOptions) { + super('public-ip'); + + this.dns = options?.dns; + } + + private hasDns() { + return Boolean(this.dns); + } + + public prehook(service: Service, _scope: cdk.Construct) { + super.prehook(service, _scope); + + if (service.capacityType != EnvironmentCapacityType.FARGATE) { + throw new Error('AssignPublicIp only supports Fargate tasks'); + } + } + + public modifyServiceProps(props: ServiceBuild) { + return { + ...props, + assignPublicIp: true, + } as ServiceBuild; + } + + public useService(service: ecs.Ec2Service | ecs.FargateService) { + if (this.hasDns()) { + new TaskRecordManager(service, 'TaskRecordManager', { + service: service, + dnsZone: this.dns!.zone, + dnsRecordName: this.dns!.recordName, + }); + + const container = this.parentService.serviceDescription.get('service-container') as Container; + service.connections.allowFromAnyIpv4( + ec2.Port.tcp(container.trafficPort), + 'Accept inbound traffic on traffic port from anywhere', + ); + } + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/index.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/index.ts new file mode 100644 index 0000000000000..81a5e787e9ab9 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/index.ts @@ -0,0 +1 @@ +export * from './assign-public-ip'; diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/.style.yapf b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/.style.yapf new file mode 100644 index 0000000000000..72e1005523a92 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/.style.yapf @@ -0,0 +1,6 @@ +# Format using: yapf -ri . +# Since you're here, unit test using: python -m unittest discover +[style] +based_on_style = pep8 +column_limit = 120 +SPLIT_BEFORE_NAMED_ASSIGNS = False diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile new file mode 100644 index 0000000000000..27927581bc218 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile @@ -0,0 +1,14 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +yapf = "*" +boto3 = "*" +coverage = "*" + +[requires] +python_version = "3.8" diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock new file mode 100644 index 0000000000000..0c01b1a6d6409 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/Pipfile.lock @@ -0,0 +1,123 @@ +{ + "_meta": { + "hash": { + "sha256": "62f31cd5a0266aa03d564dff455c3c2fd49b3e086ede177a42d574a15789fbda" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "boto3": { + "hashes": [ + "sha256:5f3969dd167b787e5bc6742afbfe15e149051d8c6aa1edaa4858133384f64ec7", + "sha256:713da2b28e9e4cd77e922690c97935dd2316ab27635b6bab4745a2d42bd887ec" + ], + "index": "pypi", + "version": "==1.15.11" + }, + "botocore": { + "hashes": [ + "sha256:1531ee5d7f7d0f0d9a12ea829ef046ac52063a1948409ae19a452a3f47a07937", + "sha256:a0514ba531148af26fe36bf75c73089698a5ed8ae150695b96e7cbdf32dd232b" + ], + "version": "==1.18.11" + }, + "coverage": { + "hashes": [ + "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", + "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", + "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", + "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", + "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", + "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", + "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", + "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", + "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", + "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", + "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", + "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", + "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", + "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", + "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", + "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", + "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", + "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", + "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", + "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", + "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", + "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", + "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", + "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", + "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", + "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", + "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", + "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", + "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", + "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", + "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", + "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", + "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", + "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" + ], + "index": "pypi", + "version": "==5.3" + }, + "jmespath": { + "hashes": [ + "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", + "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.1" + }, + "s3transfer": { + "hashes": [ + "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13", + "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db" + ], + "version": "==0.3.3" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.15.0" + }, + "urllib3": { + "hashes": [ + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + ], + "markers": "python_version != '3.4'", + "version": "==1.25.10" + }, + "yapf": { + "hashes": [ + "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427", + "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9" + ], + "index": "pypi", + "version": "==0.30.0" + } + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/index.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/index.py new file mode 100644 index 0000000000000..790102022efed --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/index.py @@ -0,0 +1,34 @@ +import logging +import os + +import boto3 + +from lib.cleanup_resource_handler import CleanupResourceHandler +from lib.queue_handler import QueueHandler + +logging.getLogger().setLevel(logging.INFO) + + +def queue_handler(event, context): + """ + Handler for the event queue lambda trigger + """ + + ec2_client = boto3.client('ec2') + dynamodb_resource = boto3.resource('dynamodb') + route53_client = boto3.client('route53') + + handler = QueueHandler(ec2_client=ec2_client, dynamodb_resource=dynamodb_resource, route53_client=route53_client, + environ=os.environ) + + return handler.handle(event, context) + + +def cleanup_resource_handler(event, context): + """ + Event handler for the custom resource. + """ + + route53_client = boto3.client('route53') + handler = CleanupResourceHandler(route53_client=route53_client) + handler.handle_event(event, context) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/__init__.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/cleanup_resource_handler.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/cleanup_resource_handler.py new file mode 100644 index 0000000000000..1ffde51b261c0 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/cleanup_resource_handler.py @@ -0,0 +1,51 @@ +import time +from dataclasses import dataclass +import logging +from typing import Any + +from lib.route53 import Route53RecordSetAccessor, Route53RecordSetLocator + + +@dataclass +class CleanupResourceProperties: + HostedZoneId: str + RecordName: str + ServiceToken: str + + +class CleanupResourceHandler: + route53_client: Any + monitor_interval: int + + def __init__(self, route53_client, monitor_interval=5): + self.record_set_accessor = Route53RecordSetAccessor(route53_client=route53_client) + self.monitor_interval = monitor_interval + + def handle_event(self, event, context): + request_type = event['RequestType'] + resource_properties = event['ResourceProperties'] + logging.info(f'Handling a {request_type} event with properties {resource_properties}') + + # Decode resource properties right away so that mis-configured + # properties will always fail quickly. + resource_properties = CleanupResourceProperties(**resource_properties) + + if request_type == 'Delete': + return self.on_delete(resource_properties) + + def on_delete(self, resource_properties: CleanupResourceProperties): + locator = Route53RecordSetLocator(hosted_zone_id=resource_properties.HostedZoneId, + record_name=resource_properties.RecordName) + + deleted = self.record_set_accessor.delete(locator=locator) + + if deleted: + logging.info(f'Monitoring for the record deletion') + for interval_number in range(1, 10): + if not self.record_set_accessor.exists(locator): + logging.info(f'The record has been deleted') + return + else: + logging.info(f'The record still exists') + if self.monitor_interval > 0: + time.sleep(self.monitor_interval) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/events.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/events.py new file mode 100644 index 0000000000000..aa34343a00170 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/events.py @@ -0,0 +1,15 @@ +from lib.records import TaskInfo, EniInfo + + +def extract_event_task_info(task_description) -> TaskInfo: + arn = task_description['taskArn'] + + # Parse the eni info out of the the attachments + enis = [ + EniInfo(eni_id=detail['value']) for network_interface in task_description['attachments'] + if network_interface['type'] == 'eni' for detail in network_interface['details'] + if detail['name'] == 'networkInterfaceId' + ] + + # Create an object out of the extracted information + return TaskInfo(task_arn=arn, enis=enis) \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/queue_handler.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/queue_handler.py new file mode 100644 index 0000000000000..db56b4c898264 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/queue_handler.py @@ -0,0 +1,117 @@ +import json +import logging +from typing import Any + +from lib.events import extract_event_task_info +from lib.records import DdbRecordKey, DdbRecord +from lib.records_table import RecordsTableAccessor, RecordUpdate +from lib.route53 import Route53RecordSetLocator, Route53RecordSetAccessor +from lib.running_task_collector import RunningTaskCollector + + +class QueueHandler: + def __init__(self, ec2_client, route53_client, dynamodb_resource, environ): + self.ec2_client = ec2_client + self.route53_client = route53_client + + hosted_zone_id = environ['HOSTED_ZONE_ID'] + record_name = environ['RECORD_NAME'] + records_table = environ['RECORDS_TABLE'] + + cluster_arn = environ['CLUSTER_ARN'] + self.service_name = environ['SERVICE_NAME'] + + self.records_table_key = DdbRecordKey(cluster_arn=cluster_arn, service_name=self.service_name) + self.records_table_accessor = RecordsTableAccessor(table_client=dynamodb_resource.Table(records_table)) + + self.record_set_locator = Route53RecordSetLocator(hosted_zone_id=hosted_zone_id, record_name=record_name) + self.record_set_accessor = Route53RecordSetAccessor(route53_client=self.route53_client) + + def handle(self, event, context): + logging.info(f'event = {json.dumps(event)}') + + # Get a reference record from the records table to check for incoming + # event inconsistencies. + reference_record = self.records_table_accessor.get_record(self.records_table_key) + + # Collect running and stopped tasks from the status change events + running_tasks, stopped_tasks = self.collect_event_task_info(event, reference_record) + + # Build up a set of updates for the record + update = RecordUpdate(running_tasks=running_tasks, stopped_tasks=stopped_tasks) + + # Record the current record set locator + update.current_record_set(self.record_set_locator) + + # Clean any extra record sets in case the recordset has moved. + for record_set_locator in reference_record.record_sets: + if not record_set_locator.matches(self.record_set_locator): + update.extra_record_set(record_set_locator) + self.try_to_delete_record(record_set_locator) + + # Introduce some delay + # records_table.optimistic_simulation_delay = 5 + + # Update the record + ddb_record = self.records_table_accessor.put_update(key=self.records_table_key, update=update) + + # Update DNS + self.record_set_accessor.update(locator=self.record_set_locator, ipv4s=ddb_record.ipv4s) + + def collect_event_task_info(self, event, reference_record: DdbRecord): + running_task_collector = RunningTaskCollector(ec2_client=self.ec2_client, reference_record=reference_record) + stopped_tasks = [] + for message in decode_records(event): + if 'details' not in message: + logging.info(f'Received a non-task state message {message}') + continue + + task_description = message['details'] + + group = task_description['group'] + if group != f'service:{self.service_name}': + logging.info(f'Skipping irrelevant task description from group {group}') + continue + + task_info = extract_event_task_info(task_description) + logging.info(f'extracted task_info = {task_info}') + + last_status = task_description['lastStatus'] + if last_status == 'RUNNING': + logging.info(f'Collecting {task_info.task_arn} as running') + running_task_collector.collect(task_info) + + elif last_status == 'STOPPED': + logging.info(f'Collecting {task_info.task_arn} as stopped') + stopped_tasks.append(task_info) + + else: + logging.warning(f'{task_info.task_arn} had an unexpected status: {last_status}') + + # Query the ENIs store-back public IPs. + running_task_collector.fill_eni_info_from_eni_query() + + running_tasks = running_task_collector.tasks + + return running_tasks, stopped_tasks + + def try_to_delete_record(self, record_set_locator: Route53RecordSetLocator): + """ + Try to delete the given record set. This may not be possible if the + record is in a hosted zone we don't have access to. This may happen + when the user changes dns zones at the service extension level. + """ + + try: + self.record_set_accessor.delete(record_set_locator) + + except: + # We give up pretty easily if the record set accessor can't delete + # the extraneous record for any reason that the accessor can't + # handle. + logging.warning(f'Could not delete the extraneous record set {record_set_locator}') + + +def decode_records(sqs_event): + logging.info(f'sqs_event = {json.dumps(sqs_event)}') + return [json.loads(sqs_record['body']) for sqs_record in sqs_event['Records']] diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records.py new file mode 100644 index 0000000000000..a6a1f2a374ab0 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records.py @@ -0,0 +1,185 @@ +from dataclasses import dataclass, field +from datetime import datetime +from typing import Optional, List, Dict, Set + +from boto3.dynamodb.conditions import Key + +from lib.route53 import Route53RecordSetLocator + + +@dataclass +class EniInfo: + eni_id: str + public_ipv4: Optional[str] = None + + +@dataclass +class TaskInfo: + task_arn: str + enis: List[EniInfo] + stopped_datetime: Optional[datetime] = None + + # Tombstone information for the dynamodb record. + + def set_stopped_marker(self): + """ + Mark this task as stopped. + """ + self.stopped_datetime = datetime.utcnow() + + def is_stopped(self): + """ + Check if this task is stopped. + """ + return True if self.stopped_datetime is not None else False + + +@dataclass +class DdbRecordKey: + cluster_arn: str + service_name: str + + def to_composite(self): + return f'{self.cluster_arn}#{self.service_name}' + + @staticmethod + def from_composite(composite: str): + cluster_arn, service_name = composite.split('#') + return DdbRecordKey(cluster_arn=cluster_arn, service_name=service_name) + + +@dataclass +class DdbRecord: + key: DdbRecordKey + ipv4s: Set[str] = field(default_factory=set) + task_info: Dict[str, TaskInfo] = field(default_factory=dict) + record_sets: Set[Route53RecordSetLocator] = field(default_factory=set) + version: int = 0 + + def task_is_stopped(self, task_info: TaskInfo): + """ + Check if a task has already stopped. + """ + + return task_info.task_arn in self.task_info and self.task_info[task_info.task_arn].is_stopped() + + +class DdbRecordEncoding: + PK_NAME = 'cluster_service' + ATTR_VERSION = 'version' + ATTR_IPV4S = 'ipv4s' + ATTR_TASK_INFO = 'task_info' + ATTR_TASK_ARN = 'task_arn' + ATTR_TASK_ENIS = 'enis' + ATTR_TASK_STOPPED_DATETIME = 'stopped_datetime' + ATTR_ENI_ID = 'eni_id' + ATTR_ENI_PUBLIC_IPV4 = 'public_ipv4' + ATTR_RECORD_SETS = 'record_sets' + ATTR_RECORD_SET_ZONE = 'hosted_zone_id' + ATTR_RECORD_SET_NAME = 'record_name' + + def get_identity(self, key: DdbRecordKey): + return {self.PK_NAME: key.to_composite()} + + def get_identity_expression(self, key: DdbRecordKey): + return Key(self.PK_NAME).eq(key.to_composite()) + + def encode(self, record: DdbRecord) -> dict: + data = dict() + data[self.PK_NAME] = record.key.to_composite() + data[self.ATTR_VERSION] = record.version + + if len(record.ipv4s) > 0: + # Sorting only matters here for repeatability in tests, as set ordering + # isn't easily predictable. + data[self.ATTR_IPV4S] = [v for v in sorted(record.ipv4s)] + + if len(record.record_sets) > 0: + data[self.ATTR_RECORD_SETS] = [self.encode_record_set(v) for v in sorted(record.record_sets)] + + if len(record.task_info) > 0: + data[self.ATTR_TASK_INFO] = { + task_info.task_arn: self.encode_task_info(task_info) + for task_info in record.task_info.values() + } + + return data + + def encode_record_set(self, record_set: Route53RecordSetLocator): + return { + self.ATTR_RECORD_SET_ZONE: record_set.hosted_zone_id, + self.ATTR_RECORD_SET_NAME: record_set.record_name, + } + + def encode_task_info(self, task_info: TaskInfo) -> dict: + data = dict() + data[self.ATTR_TASK_ARN] = task_info.task_arn + + if task_info.stopped_datetime is not None: + data[self.ATTR_TASK_STOPPED_DATETIME] = task_info.stopped_datetime.isoformat() + + if len(task_info.enis) > 0: + data[self.ATTR_TASK_ENIS] = [self.encode_eni_info(eni_info) for eni_info in task_info.enis] + + return data + + def encode_eni_info(self, eni_info: EniInfo) -> dict: + data = dict() + data[self.ATTR_ENI_ID] = eni_info.eni_id + if eni_info.public_ipv4 is not None: + data[self.ATTR_ENI_PUBLIC_IPV4] = eni_info.public_ipv4 + + return data + + def decode(self, data: dict) -> DdbRecord: + key = DdbRecordKey.from_composite(data[self.PK_NAME]) + version = int(data[self.ATTR_VERSION]) + + ipv4s = set() + if self.ATTR_IPV4S in data: + ipv4s = {ip for ip in data[self.ATTR_IPV4S]} + + record_sets = set() + if self.ATTR_RECORD_SETS in data: + for record_set_data in data[self.ATTR_RECORD_SETS]: + record_set = self.decode_record_set(record_set_data) + record_sets.add(record_set) + + task_info = dict() + if self.ATTR_TASK_INFO in data: + task_info = { + k: self.decode_task_info(task_info_data) + for (k, task_info_data) in data[self.ATTR_TASK_INFO].items() + } + + record = DdbRecord(key=key, version=version, ipv4s=ipv4s, task_info=task_info, record_sets=record_sets) + + return record + + def decode_record_set(self, data) -> Route53RecordSetLocator: + hosted_zone_id = data[self.ATTR_RECORD_SET_ZONE] + record_name = data[self.ATTR_RECORD_SET_NAME] + + return Route53RecordSetLocator(hosted_zone_id=hosted_zone_id, record_name=record_name) + + def decode_task_info(self, data) -> TaskInfo: + task_arn = data[self.ATTR_TASK_ARN] + + stopped_datetime = None + if self.ATTR_TASK_STOPPED_DATETIME in data: + stopped_datetime = datetime.fromisoformat(data[self.ATTR_TASK_STOPPED_DATETIME]) + + enis = [] + if self.ATTR_TASK_ENIS in data: + enis = [self.decode_eni_info(eni_info_data) for eni_info_data in data[self.ATTR_TASK_ENIS]] + + return TaskInfo(task_arn=task_arn, stopped_datetime=stopped_datetime, enis=enis) + + def decode_eni_info(self, data) -> EniInfo: + eni_id = data[self.ATTR_ENI_ID] + + public_ipv4 = None + if self.ATTR_ENI_PUBLIC_IPV4 in data: + public_ipv4 = data[self.ATTR_ENI_PUBLIC_IPV4] + + return EniInfo(eni_id=eni_id, public_ipv4=public_ipv4) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records_table.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records_table.py new file mode 100644 index 0000000000000..390ef0066dde5 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/records_table.py @@ -0,0 +1,215 @@ +import logging +import time +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from typing import * + +from boto3.dynamodb.conditions import Attr +from botocore.exceptions import ClientError + +from lib.records import DdbRecord, DdbRecordKey, DdbRecordEncoding, TaskInfo +from lib.route53 import Route53RecordSetLocator + + +@dataclass +class RecordUpdate: + running_tasks: List[TaskInfo] = field(default_factory=list) + stopped_tasks: List[TaskInfo] = field(default_factory=list) + record_sets_added: List[Route53RecordSetLocator] = field(default_factory=list) + record_sets_removed: List[Route53RecordSetLocator] = field(default_factory=list) + + def current_record_set(self, record_set: Route53RecordSetLocator): + self.record_sets_added.append(record_set) + + def extra_record_set(self, record_set: Route53RecordSetLocator): + self.record_sets_removed.append(record_set) + + +class RecordsTableAccessor: + """ + Abstracts management of the task records to putting running and stopped tasks. + """ + + table_client: Any + ddb_record_encoding: DdbRecordEncoding + + # Max number of attempts at optimistic put_tasks + max_attempts = 50 + + # Amount of lag to add (if any) to simulate concurrent locking conflicts in + # lambda + optimistic_simulation_delay = 0 + + def __init__(self, table_client): + """ + Initializes a RecordsTable. Provide a boto3.resource + """ + self.table_client = table_client + self.ddb_record_encoding = DdbRecordEncoding() + + def delete(self, key: DdbRecordKey): + """ + Delete a record by record key. + """ + + logging.info(f'Deleting {key}') + self.table_client.delete_item(Key=self.ddb_record_encoding.get_identity(key)) + + def put_update(self, key: DdbRecordKey, update: RecordUpdate) -> DdbRecord: + """ + Retries putting tasks into the table record with optimistic locking. + """ + + for attempt in range(0, self.max_attempts): + try: + logging.info(f'Attempting to put the task optimistically (attempt {attempt+1})') + return self.put_update_optimistically(key=key, update=update) + except ClientError as e: + if e.response['Error']['Code'] == 'ConditionalCheckFailedException': + logging.info(f'Check condition was rejected') + continue + else: + raise + + # Ran out of retries!! + raise Exception('Exceeded maximum retries while optimistically putting changes') + + def get_record(self, key: DdbRecordKey) -> DdbRecord: + """ + Gets the record by key or provides a blank record. + """ + + # Search for the pertinent record + response = self.table_client.query(KeyConditionExpression=self.ddb_record_encoding.get_identity_expression(key)) + + if len(response['Items']) > 0: + # Decode a pre-existing record + logging.info(f'Found a pre-existing record') + return self.ddb_record_encoding.decode(response['Items'][0]) + else: + logging.info(f'Creating a new record') + # Create a new record + return DdbRecord(key=key) + + def put_update_optimistically(self, key: DdbRecordKey, update: RecordUpdate) -> DdbRecord: + """ + Optimistically record running and stopped tasks for this record. + """ + + ddb_record = self.get_record(key=key) + + # Add some lag (if any) to simulate concurrent locking conflicts in lambda + if self.optimistic_simulation_delay > 0: + time.sleep(self.optimistic_simulation_delay) + + # Update the record with the running and stopped task info + update_ddb_record(ddb_record=ddb_record, update=update) + + # Optimistic locking condition + optimistic_lock_condition = Attr(self.ddb_record_encoding.ATTR_VERSION).not_exists() \ + | Attr(self.ddb_record_encoding.ATTR_VERSION).eq(ddb_record.version) + + # Prepare the record for updating + ddb_record.version += 1 + item = self.ddb_record_encoding.encode(ddb_record) + + # Put it up + self.table_client.put_item(Item=item, ConditionExpression=optimistic_lock_condition) + + return ddb_record + + +def update_ddb_record(ddb_record: DdbRecord, update: RecordUpdate) -> DdbRecord: + """ + Updates a DynamoDB record with the list of running and stopped tasks. + """ + + # Add the record sets we want to add + for record_set in update.record_sets_added: + ddb_record.record_sets.add(record_set) + + # Remove the ones we want to remove + for record_set in update.record_sets_removed: + if record_set in ddb_record.record_sets: + ddb_record.record_sets.remove(record_set) + + # Add running task info to the record + for running_task in update.running_tasks: + # Don't add a task as running when it previously stopped (out-of-order receive) + if running_task.task_arn in ddb_record.task_info and ddb_record.task_info[running_task.task_arn].is_stopped(): + logging.info( + f'Received {running_task.task_arn} transition to RUNNING, but it was already stopped. Ignored.') + continue + + # Record info about the running task + ddb_record.task_info[running_task.task_arn] = running_task + + # Add all public ips to the public ip set + for eni in running_task.enis: + if eni.public_ipv4 is not None: + ddb_record.ipv4s.add(eni.public_ipv4) + + logging.info(f'Recorded {running_task.task_arn} as RUNNING.') + + # Remove stopped task ips from the record and set "stopped" markers on the + # stored task info. + for stopped_task in update.stopped_tasks: + # When the stopped task was previously represented in the task info list, + # then we fetch the old representation for its potential ip address info. + if stopped_task.task_arn in ddb_record.task_info: + task_arn = stopped_task.task_arn + stored_task = ddb_record.task_info[task_arn] + + # When the task is not yet marked as stopped, we need to mark it as such + # and remove its eni ips from the ip list. + if not stored_task.is_stopped(): + stored_task.set_stopped_marker() + + for eni in stored_task.enis: + if eni.public_ipv4 is not None and eni.public_ipv4 in ddb_record.ipv4s: + ddb_record.ipv4s.remove(eni.public_ipv4) + + logging.info(f'Recorded {task_arn} as STOPPED.') + + else: + # Stored task already marked as stopped, so the received task is a + # duplicate. Ignore it. + logging.info(f'Received {task_arn} which was already STOPPED. Ignoring.') + pass + + else: + # Stopped task isn't in the task list, so we've received an out-of-order + # STOPPED transition. We don't know this task, but we know that if we + # receive a running task in the future, that we don't want to accept it. + stopped_task.set_stopped_marker() + ddb_record.task_info[stopped_task.task_arn] = stopped_task + logging.info(f'Recorded {stopped_task.task_arn} as STOPPED even though we have never seen it.') + + # Expunge expired tasks. Use a copy of the dict items to avoid errors from the + # dictionary changing while iterating. + for (key, task) in list(ddb_record.task_info.items()): + if task_info_has_expired(task): + logging.info(f'Expunging {task.task_arn} as expired.') + del ddb_record.task_info[key] + + return ddb_record + + +# The the length of time that a task marked as stopped may continue to exist +# in the task info list before it is expunged. +STOPPED_MARKER_EXPIRATION = timedelta(minutes=30) + + +def task_info_has_expired(task_info: TaskInfo): + """ + Determine if this task info can be deleted from the DDB record. If the task + has stopped and the stopped marker expiration has elapsed, then we can + delete, otherwise the task info must be kept to filter out-of-order duplicate + RUNNING state transition events. + """ + + if not task_info.is_stopped(): + return False + + stopped_marker_age = datetime.utcnow() - task_info.stopped_datetime + return True if stopped_marker_age > STOPPED_MARKER_EXPIRATION else False diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/route53.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/route53.py new file mode 100644 index 0000000000000..df3b23c76a1fe --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/route53.py @@ -0,0 +1,167 @@ +from dataclasses import dataclass +import time +from typing import * +import logging + +from botocore.exceptions import ClientError + + +@dataclass +class Route53RecordSetLocator: + hosted_zone_id: str + record_name: str + + def __str__(self): + """String serialization for hashing and comparison""" + return f'{self.hosted_zone_id}#{self.record_name}' + + def __hash__(self): + """Unique hash for this object is based on its string serialization""" + return int.from_bytes(self.__str__().encode(), 'little') + + def __lt__(self, other): + """set() uses this""" + return self.__str__() < other.__str__() + + def get_dot_suffixed_name(self): + return self.record_name + '.' + + def matches_record_set(self, record_set): + return record_set['Name'] == self.get_dot_suffixed_name() + + def matches(self, record_set_locator): + return self.record_name == record_set_locator.record_name and self.hosted_zone_id == record_set_locator.hosted_zone_id + + +class Route53RecordSetAccessor: + route53_client: Any + ttl = 60 + + def __init__(self, route53_client: Any): + self.route53_client = route53_client + + def update(self, locator: Route53RecordSetLocator, ipv4s: Set[str] = None): + ipv4s = set() if ipv4s is None else ipv4s + + record_set, is_new = retry_with_backoff(lambda: self.get_record_set(locator)) + if is_new: + logging.info(f'Found a pre-existing record set: {record_set}') + else: + logging.info('Creating a new record set') + + if len(ipv4s) > 0: + record_set['ResourceRecords'] = map_ips_to_resource_records(ipv4s) + retry_with_backoff(lambda: self.request_upsert(locator, record_set)) + elif not is_new: + retry_with_backoff(lambda: self.request_delete(locator, record_set)) + else: + logging.info('Refusing to do anything with a new but empty recordset') + + def get_record_set(self, locator: Route53RecordSetLocator) -> Tuple[dict, bool]: + record_type = 'A' + result = self.route53_client.list_resource_record_sets(HostedZoneId=locator.hosted_zone_id, + StartRecordName=locator.record_name, + StartRecordType=record_type, MaxItems="1") + + logging.info(f'Query result: {result}') + existing_record_set = find_locator_record_set(locator, record_type, result['ResourceRecordSets']) + if existing_record_set: + return existing_record_set, False + else: + return { + 'Name': locator.get_dot_suffixed_name(), + 'Type': record_type, + 'ResourceRecords': [], + 'TTL': self.ttl + }, True + + def request_upsert(self, locator: Route53RecordSetLocator, record_set): + logging.info(f'Upserting record set {record_set}') + self.route53_client.change_resource_record_sets( + HostedZoneId=locator.hosted_zone_id, ChangeBatch={ + 'Comment': 'Automatic', + 'Changes': [{ + 'Action': 'UPSERT', + 'ResourceRecordSet': record_set + }] + }) + + def delete(self, locator: Route53RecordSetLocator): + """ + Delete the record. Returns true if it found and deleted the record. + Returns false if it didn't need to delete anything. + """ + + logging.info(f'Querying for {locator}') + record_set, is_new = retry_with_backoff(lambda: self.get_record_set(locator)) + + if not is_new: + logging.info(f'Found a record set') + retry_with_backoff(lambda: self.request_delete(locator, record_set)) + logging.info(f'Deleted record set {record_set}') + return True + + else: + logging.info(f'Did not find a record set, so no deletion needed') + return False + + def exists(self, locator: Route53RecordSetLocator): + """ + Returns true if the record exists. False otherwise. + """ + + _, is_new = retry_with_backoff(lambda: self.get_record_set(locator)) + + return not is_new + + def request_delete(self, locator: Route53RecordSetLocator, record_set): + logging.info(f'Deleting record set: {record_set}') + self.route53_client.change_resource_record_sets( + HostedZoneId=locator.hosted_zone_id, ChangeBatch={ + 'Comment': 'Automatic', + 'Changes': [{ + 'Action': 'DELETE', + 'ResourceRecordSet': record_set, + }] + }) + + +def exponential_backoff(attempt: int): + return 2**attempt + + +def retry_with_backoff(call: Callable, attempts=5, backoff=exponential_backoff): + for attempt in range(0, attempts): + try: + return call() + + except ClientError as e: + if e.response['Error']['Code'] == 'Throttling': + backoff_seconds = backoff(attempt) + logging.info(f'Attempt {attempt+1} throttled. Backing off for {backoff_seconds}.') + time.sleep(backoff_seconds) + continue + + if e.response['Error']['Code'] == 'PriorRequestNotComplete': + backoff_seconds = backoff(attempt) + logging.info( + f'Attempt {attempt+1} discovered the prior request is not yet complete. Backing off for {backoff_seconds}.' + ) + time.sleep(backoff_seconds) + continue + + raise + + +def map_ips_to_resource_records(ips: Set[str]): + # Take up to the first 400 ips after sorting as the max recordset record quota is 400 + ips_sorted_limited = sorted(ips)[0:400] + return [{'Value': ip} for ip in ips_sorted_limited] + + +def find_locator_record_set(locator: Route53RecordSetLocator, record_type: str, record_sets: list): + for record_set in record_sets: + if locator.matches_record_set(record_set) and record_set['Type'] == record_type: + return record_set + + return None diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/running_task_collector.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/running_task_collector.py new file mode 100644 index 0000000000000..a7034d81e5e53 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/lib/running_task_collector.py @@ -0,0 +1,57 @@ +import logging +from typing import * + +from lib.records import DdbRecord, EniInfo, TaskInfo + + +class RunningTaskCollector: + """ + Collects information about running tasks. After collecting all task info, + when `fill_eni_info_from_eni_query()` is called, the collector queries + for the ip addresses of the tasks and fills in the appropriate records. + """ + + ec2_client: Any + tasks: List[TaskInfo] + enis_by_id: Dict[str, EniInfo] + + def __init__(self, ec2_client, reference_record: DdbRecord): + self.ec2_client = ec2_client + self.tasks = list() + self.enis_by_id = dict() + self.reference_record = reference_record + + def collect(self, task_info): + # Check to see if the task we've received is already stopped. If so, + # we refuse to collect it on the basis that we'll just get an eni + # doesn't exist error anyway. + if self.reference_record.task_is_stopped(task_info): + logging.info(f'Refusing to collect {task_info.task_arn} as it has already been deleted') + return + + # Append the task info to the master list + self.tasks.append(task_info) + + # Collect enis indexed by their ids + for eni in task_info.enis: + self.enis_by_id[eni.eni_id] = eni + + def fill_eni_info_from_eni_query(self): + for eni_description in self.describe_enis(): + eni_id = eni_description['NetworkInterfaceId'] + + if 'Association' in eni_description: + public_ipv4 = eni_description['Association']['PublicIp'] + if public_ipv4 and eni_id in self.enis_by_id: + self.enis_by_id[eni_id].public_ipv4 = public_ipv4 + + def describe_enis(self): + paginator = self.ec2_client.get_paginator('describe_network_interfaces') + + eni_ids = list(self.enis_by_id.keys()) + for page in paginator.paginate(NetworkInterfaceIds=eni_ids): + for eni in page['NetworkInterfaces']: + yield eni + + def get_ips(self): + return [eni.public_ipv4 for eni in self.enis_by_id.values()] diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/run_test.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/run_test.py new file mode 100644 index 0000000000000..7efd5dc08534d --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/run_test.py @@ -0,0 +1,5 @@ +import unittest + +if __name__ == "__main__": + test_suite = unittest.defaultTestLoader.discover('.') + unittest.TextTestRunner().run(test_suite) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/__init__.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/__init__.py new file mode 100644 index 0000000000000..539bac0f9e2aa --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/__init__.py @@ -0,0 +1 @@ +# Keep this file so that python -m unittest discover can find these tests. diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/ddb-record.json b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/ddb-record.json new file mode 100644 index 0000000000000..f62cf391abf39 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/ddb-record.json @@ -0,0 +1,43 @@ +{ + "cluster_service": "CLUSTER_ARN#SERVICE", + "ipv4s": [ + "1.1.2.1", + "1.1.2.2" + ], + "task_info": { + "TASK1_ARN": { + "enis": [ + { + "eni_id": "TASK1_ENI1_ID", + "public_ipv4": "1.1.1.1" + } + ], + "stopped_datetime": "2020-10-04T23:47:36.322158", + "task_arn": "TASK1_ARN" + }, + "TASK2_ARN": { + "enis": [ + { + "eni_id": "TASK2_ENI1_ID", + "public_ipv4": "1.1.2.1" + }, + { + "eni_id": "TASK2_ENI2_ID", + "public_ipv4": "1.1.2.2" + } + ], + "task_arn": "TASK2_ARN" + } + }, + "record_sets": [ + { + "hosted_zone_id": "ABCD", + "record_name": "test-record.myexample.com" + }, + { + "hosted_zone_id": "ABCD", + "record_name": "test-record2.myexample.com" + } + ], + "version": 12 +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/eni_description.json b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/eni_description.json new file mode 100644 index 0000000000000..9e790e455fb99 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/eni_description.json @@ -0,0 +1,49 @@ +{ + "Association": { + "IpOwnerId": "amazon", + "PublicDnsName": "example.com", + "PublicIp": "1.2.3.4" + }, + "Attachment": { + "AttachTime": "2020-10-03T23:42:51+00:00", + "AttachmentId": "eni-attach-0704671692ecf366b", + "DeleteOnTermination": false, + "DeviceIndex": 1, + "InstanceOwnerId": "000000000", + "Status": "attached" + }, + "AvailabilityZone": "test-region-1a", + "Description": "arn:aws:ecs:test-region-1:0000000000:attachment/20d24cce-3d50-493d-b890-32d4f11859f4", + "Groups": [ + { + "GroupName": "aws-ecs-integ-nameserviceSecurityGroup33F4662C-16PM465FOR8L1", + "GroupId": "sg-0b83d6ad2edd8e940" + } + ], + "InterfaceType": "interface", + "Ipv6Addresses": [], + "MacAddress": "02:a4:cb:74:0f:a8", + "NetworkInterfaceId": "eni-abcd", + "OwnerId": "0000000000", + "PrivateDnsName": "ip-10-0-0-19.test-region-1.compute.internal", + "PrivateIpAddress": "10.0.0.19", + "PrivateIpAddresses": [ + { + "Association": { + "IpOwnerId": "amazon", + "PublicDnsName": "example.com", + "PublicIp": "1.2.3.4" + }, + "Primary": true, + "PrivateDnsName": "ip-10-0-0-19.test-region-1.compute.internal", + "PrivateIpAddress": "10.0.0.19" + } + ], + "RequesterId": "0000000000", + "RequesterManaged": true, + "SourceDestCheck": true, + "Status": "in-use", + "SubnetId": "subnet-036b0d1413bb6bd2c", + "TagSet": [], + "VpcId": "vpc-0e63014e689c4b14f" +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/task_description.json b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/task_description.json new file mode 100644 index 0000000000000..7bf19d91893a2 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/fixtures/task_description.json @@ -0,0 +1,77 @@ +{ + "attachments": [ + { + "id": "", + "type": "eni", + "status": "DELETED", + "details": [ + { + "name": "subnetId", + "value": "subnet-" + }, + { + "name": "networkInterfaceId", + "value": "eni-abcd" + }, + { + "name": "macAddress", + "value": "" + }, + { + "name": "privateIPv4Address", + "value": "10.0.0.52" + } + ] + } + ], + "availabilityZone": "test-region-1a", + "clusterArn": "arn:aws:ecs:test-region-1::cluster/aws-ecs-integ-productionenvironmentclusterC6599D2D-U7W8a2P2HPhC", + "containers": [ + { + "containerArn": "arn:aws:ecs:test-region-1::container/ff3b49f4-5eea-46cd-99c6-069584b3fb8e", + "exitCode": 1, + "lastStatus": "STOPPED", + "name": "app", + "image": "nathanpeck/name", + "runtimeId": "", + "taskArn": "arn:aws:ecs:test-region-1::task/12345678-1234-1234-1234-1234567890AB", + "networkInterfaces": [ + { + "attachmentId": "323eb03f-dedf-44b6-aa5f-d9d7f7b37714", + "privateIpv4Address": "10.0.0.52" + } + ], + "cpu": "256", + "memory": "512" + } + ], + "createdAt": "2020-10-03T22:31:35.117Z", + "launchType": "FARGATE", + "cpu": "256", + "memory": "512", + "desiredStatus": "STOPPED", + "group": "service:aws-ecs-integ-nameserviceService8015C8D6-I4TwUFv4xk2o", + "lastStatus": "STOPPED", + "overrides": { + "containerOverrides": [ + { + "name": "app" + } + ] + }, + "connectivity": "CONNECTED", + "connectivityAt": "2020-10-03T22:31:43.32Z", + "pullStartedAt": "2020-10-03T22:31:46.764Z", + "startedAt": "2020-10-03T22:31:54.764Z", + "startedBy": "ecs-svc/7073659324082574009", + "stoppingAt": "2020-10-03T22:43:06.753Z", + "stoppedAt": "2020-10-03T22:43:31.542Z", + "pullStoppedAt": "2020-10-03T22:31:53.764Z", + "executionStoppedAt": "2020-10-03T22:43:08Z", + "stoppedReason": "Scaling activity initiated by (deployment ecs-svc/7073659324082574009)", + "updatedAt": "2020-10-03T22:43:31.542Z", + "taskArn": "arn:aws:ecs:test-region-1::task/12345678-1234-1234-1234-1234567890AB", + "taskDefinitionArn": "arn:aws:ecs:test-region-1::task-definition/awsecsintegnametaskdefinition0EA6A1A0:3", + "version": 7, + "platformVersion": "1.3.0" +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_cleanup_resource_handler.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_cleanup_resource_handler.py new file mode 100644 index 0000000000000..c5ec1d0cf61ec --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_cleanup_resource_handler.py @@ -0,0 +1,46 @@ +import unittest +import unittest.mock as mock + +from lib.cleanup_resource_handler import CleanupResourceHandler +from lib.route53 import Route53RecordSetLocator + + +class TestCleanupResourceHandler(unittest.TestCase): + def test_handler_rejects_invalid_properties(self): + handler = CleanupResourceHandler(route53_client=mock.Mock()) + with self.assertRaises(Exception): + handler.handle_event({'RequestType': 'Delete', 'ResourceProperties': {'Invalid': 'Invalid'}}, {}) + + def test_handling_delete(self): + handler = CleanupResourceHandler(route53_client=mock.Mock(), monitor_interval=0) + record_set_accessor = mock.Mock() + record_set_accessor.delete = mock.Mock(return_value=True) # True = Deleted + + exists_count = 0 + + def exists_side_effect(*args): + nonlocal exists_count + exists_count += 1 + return True if exists_count < 3 else False + + record_set_accessor.exists = mock.Mock(side_effect=exists_side_effect) + + handler.record_set_accessor = record_set_accessor + + event = { + 'RequestType': 'Delete', + 'ResourceProperties': { + 'ServiceToken': 'Something', + 'HostedZoneId': 'ZONE', + 'RecordName': 'something.mydomain.com' + } + } + + # WHEN + handler.handle_event(event, {}) + + # THEN + expected_locator = Route53RecordSetLocator(hosted_zone_id='ZONE', record_name='something.mydomain.com') + record_set_accessor.delete.assert_called_with(locator=expected_locator) + record_set_accessor.exists.assert_called() + self.assertEqual(record_set_accessor.exists.call_count, 3) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_events.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_events.py new file mode 100644 index 0000000000000..a616bd7999309 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_events.py @@ -0,0 +1,21 @@ +import json +import os +import unittest + +from lib.events import extract_event_task_info + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(THIS_DIR, 'fixtures', 'task_description.json')) as f: + TASK_DESCRIPTION = json.loads(f.read()) + + +class TestEvents(unittest.TestCase): + def test_extract_event_task_info(self): + task_info = extract_event_task_info(TASK_DESCRIPTION) + + self.assertEqual(task_info.task_arn, 'arn:aws:ecs:test-region-1::task/12345678-1234-1234-1234-1234567890AB') + self.assertTrue(not task_info.is_stopped()) + + self.assertEqual(len(task_info.enis), 1) + self.assertEqual(task_info.enis[0].eni_id, 'eni-abcd') + self.assertEqual(task_info.enis[0].public_ipv4, None) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_queue_handler.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_queue_handler.py new file mode 100644 index 0000000000000..91477451cf72e --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_queue_handler.py @@ -0,0 +1,29 @@ +import unittest +import unittest.mock as mock + +from lib.queue_handler import QueueHandler + + +class TestQueueHandler(unittest.TestCase): + def test_queue_handler_sets_up(self): + environ = { + 'HOSTED_ZONE_ID': 'HOSTED_ZONE_ID', + 'RECORD_NAME': 'RECORD_NAME', + 'RECORDS_TABLE': 'RECORDS_TABLE', + 'CLUSTER_ARN': 'CLUSTER_ARN', + 'SERVICE_NAME': 'SERVICE_NAME', + } + + ec2_client = mock.Mock() + route53_client = mock.Mock() + dynamodb_resource = mock.Mock() + + # WHEN + + handler = QueueHandler(ec2_client=ec2_client, route53_client=route53_client, + dynamodb_resource=dynamodb_resource, environ=environ) + + # THEN + dynamodb_resource.Table.called_width('RECORDS_TABLE') + + pass diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records.py new file mode 100644 index 0000000000000..cb2e1e45f6ed4 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records.py @@ -0,0 +1,46 @@ +import json +import os +import unittest +from datetime import datetime + +from lib.records import DdbRecordEncoding, TaskInfo, EniInfo + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(THIS_DIR, 'fixtures', 'ddb-record.json')) as f: + DDB_RECORD_ENCODED = json.loads(f.read()) + + +class TestRecords(unittest.TestCase): + def test_task_info_stopped_marker(self): + task_info = TaskInfo(task_arn='a', enis=[]) + task_info.set_stopped_marker() + self.assertTrue(task_info.is_stopped()) + + def test_ddb_record_encoding(self): + # GIVEN + ddb_record_encoding = DdbRecordEncoding() + + # WHEN + ddb_record = ddb_record_encoding.decode(DDB_RECORD_ENCODED) + ddb_record_reencoded = ddb_record_encoding.encode(ddb_record) + + # THEN + self.assertEqual(ddb_record.key.cluster_arn, 'CLUSTER_ARN') + self.assertEqual(ddb_record.key.service_name, 'SERVICE') + self.assertEqual(sorted(ddb_record.ipv4s), ['1.1.2.1', '1.1.2.2']) + self.assertEqual( + ddb_record.task_info['TASK1_ARN'], + TaskInfo(task_arn='TASK1_ARN', stopped_datetime=datetime(2020, 10, 4, 23, 47, 36, 322158), enis=[ + EniInfo(eni_id='TASK1_ENI1_ID', public_ipv4='1.1.1.1'), + ])) + self.assertEqual( + ddb_record.task_info['TASK2_ARN'], + TaskInfo( + task_arn='TASK2_ARN', enis=[ + EniInfo(eni_id='TASK2_ENI1_ID', public_ipv4='1.1.2.1'), + EniInfo(eni_id='TASK2_ENI2_ID', public_ipv4='1.1.2.2'), + ])) + self.assertEqual(len(ddb_record.record_sets), 2) + + self.maxDiff = 9999999 + self.assertEqual(ddb_record_reencoded, DDB_RECORD_ENCODED) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records_table.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records_table.py new file mode 100644 index 0000000000000..3429c152a6a35 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_records_table.py @@ -0,0 +1,188 @@ +import json +import os +import unittest +import unittest.mock as mock + +from boto3.dynamodb.conditions import ConditionExpressionBuilder +from botocore.exceptions import ClientError + +from lib.records import DdbRecordKey, TaskInfo, EniInfo, DdbRecord +from lib.records_table import RecordsTableAccessor, update_ddb_record, RecordUpdate +from lib.route53 import Route53RecordSetLocator + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(THIS_DIR, 'fixtures', 'ddb-record.json')) as f: + DDB_RECORD_ENCODED = json.loads(f.read()) + + +class TestRecordsTable(unittest.TestCase): + def test_put_tasks_creates_with_optimistic_lock(self): + # GIVEN + table_client = mock.Mock() + table_client.query = mock.Mock(return_value={'Items': []}) + + key = DdbRecordKey(cluster_arn='a', service_name='b') + records_table = RecordsTableAccessor(table_client=table_client) + + running = [TaskInfo(task_arn='TASK1_ARN', enis=[ + EniInfo(eni_id='TASK1_ENI1_ID', public_ipv4='1.1.1.1'), + ])] + + # WHEN + records_table.put_update_optimistically(key=key, update=RecordUpdate(running_tasks=running)) + + # THEN + table_client.put_item.assert_called() + item = table_client.put_item.call_args.kwargs['Item'] + self.assertEqual(item['version'], 1) + + condition_expression = table_client.put_item.call_args.kwargs['ConditionExpression'] + expr, atts, vals = ConditionExpressionBuilder().build_expression(condition_expression) + self.assertEqual(expr, '(attribute_not_exists(#n0) OR #n1 = :v0)') + self.assertEqual(atts, {'#n0': 'version', '#n1': 'version'}) + self.assertEqual(vals, {':v0': 0}) + + def test_put_tasks_updates_with_optimistic_lock(self): + # GIVEN + table_client = mock.Mock() + table_client.query = mock.Mock(return_value={'Items': [dict(DDB_RECORD_ENCODED)]}) + + key = DdbRecordKey(cluster_arn='FOO', service_name='test.myexample.com') + records_table = RecordsTableAccessor(table_client=table_client) + + running = [TaskInfo(task_arn='TASK1_ARN', enis=[ + EniInfo(eni_id='TASK1_ENI1_ID', public_ipv4='1.1.1.1'), + ])] + + # WHEN + records_table.put_update_optimistically(key=key, update=RecordUpdate(running_tasks=running)) + + # THEN + condition_expression = table_client.put_item.call_args.kwargs['ConditionExpression'] + expr, atts, vals = ConditionExpressionBuilder().build_expression(condition_expression) + self.assertEqual(vals, {':v0': 12}) + + def test_put_tasks_retries_optimistically(self): + # GIVEN + table_client = mock.Mock() + table_client.query = mock.Mock(return_value={'Items': []}) + table_client.put_item = mock.Mock( + side_effect=ClientError({'Error': { + 'Code': 'ConditionalCheckFailedException' + }}, 'PutItem')) + + records_table = RecordsTableAccessor(table_client=table_client) + key = DdbRecordKey(cluster_arn='a', service_name='b') + + # WHEN + with self.assertRaisesRegex(Exception, r'Exceeded maximum retries'): + records_table.put_update(key=key, update=RecordUpdate()) + + # THEN + self.assertEqual(table_client.query.call_count, records_table.max_attempts) + self.assertEqual(table_client.put_item.call_count, records_table.max_attempts) + + def test_put_tasks_raises_other_errors(self): + # GIVEN + table_client = mock.Mock() + table_client.query = mock.Mock(return_value={'Items': []}) + table_client.put_item = mock.Mock(side_effect=ClientError({'Error': {'Code': 'SomethingElse'}}, 'PutItem')) + + records_table = RecordsTableAccessor(table_client=table_client) + key = DdbRecordKey(cluster_arn='a', service_name='b') + + # WHEN + with self.assertRaisesRegex(Exception, r'SomethingElse'): + records_table.put_update(key=key, update=RecordUpdate()) + + # THEN + self.assertEqual(table_client.query.call_count, 1) + self.assertEqual(table_client.put_item.call_count, 1) + + def test_delete(self): + # GIVEN + table_client = mock.Mock() + key = DdbRecordKey(cluster_arn='a', service_name='b') + records_table = RecordsTableAccessor(table_client=table_client) + + # WHEN + records_table.delete(key) + + # THEN + table_client.delete_item.called_with(Key='a#b') + + def test_update_ddb_record(self): + # GIVEN + ddb_record = DdbRecord(key=DdbRecordKey(cluster_arn='a', service_name='b')) + + # TASK1->RUNNING, TASK2->RUNNING + ord1_running = [ + TaskInfo(task_arn='TASK1_ARN', enis=[ + EniInfo(eni_id='TASK1_ENI1_ID', public_ipv4='1.1.1.1'), + ]), + TaskInfo(task_arn='TASK2_ARN', enis=[ + EniInfo(eni_id='TASK2_ENI1_ID', public_ipv4='1.1.2.1'), + ]), + ] + # TASK3->STOPPED (out of order) + ord1_stopped = [ + TaskInfo(task_arn='TASK3_ARN', enis=[ + EniInfo(eni_id='TASK3_ENI1_ID'), + ]), + ] + + # TASK1->STOPPED, TASK3->STOPPED (duplicate) + ord2_stopped = [ + # Expected TASK1 transition to STOPPED + TaskInfo(task_arn='TASK1_ARN', enis=[ + EniInfo(eni_id='TASK1_ENI1_ID'), + ]), + # Duplicate TASK3 transition to STOPPED + TaskInfo(task_arn='TASK3_ARN', enis=[ + EniInfo(eni_id='TASK3_ENI1_ID'), + ]), + ] + + # TASK1->RUNNING (out of order), TASK3->RUNNING (out of order) + ord3_running = [ + TaskInfo(task_arn='TASK1_ARN', enis=[ + EniInfo(eni_id='TASK1_ENI1_ID', public_ipv4='1.1.1.1'), + ]), + TaskInfo(task_arn='TASK3_ARN', enis=[ + EniInfo(eni_id='TASK3_ENI1_ID', public_ipv4='1.1.3.1'), + ]), + ] + + # WHEN + update_ddb_record(ddb_record, RecordUpdate(running_tasks=ord1_running, stopped_tasks=ord1_stopped)) + update_ddb_record(ddb_record, RecordUpdate(stopped_tasks=ord2_stopped)) + update_ddb_record(ddb_record, RecordUpdate(running_tasks=ord3_running)) + + # THEN + self.assertEqual(len(ddb_record.task_info), 3, msg='expected 3 task infos') + self.assertTrue(ddb_record.task_info['TASK1_ARN'].is_stopped()) + self.assertTrue(not ddb_record.task_info['TASK2_ARN'].is_stopped()) + self.assertTrue(ddb_record.task_info['TASK3_ARN'].is_stopped()) + + self.assertFalse('1.1.1.1' in ddb_record.ipv4s, + msg='ord3_running should have been ignored because the task previously stopped') + self.assertEqual(sorted(ddb_record.ipv4s), ['1.1.2.1']) + + def test_update_record_sets(self): + # GIVEN + ddb_record = DdbRecord(key=DdbRecordKey(cluster_arn='a', service_name='b')) + ord1 = [ + Route53RecordSetLocator('a', 'b'), + Route53RecordSetLocator('a', 'c'), + ] + ord2 = [ + Route53RecordSetLocator('a', 'b'), + ] + + # WHEN + update_ddb_record(ddb_record, RecordUpdate(record_sets_added=ord1)) + update_ddb_record(ddb_record, RecordUpdate(record_sets_removed=ord2)) + + # THEN + self.assertEqual(len(ddb_record.record_sets), 1) + self.assertTrue(Route53RecordSetLocator('a', 'c') in ddb_record.record_sets) \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_route53.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_route53.py new file mode 100644 index 0000000000000..a77093dcf484d --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_route53.py @@ -0,0 +1,281 @@ +import unittest +import unittest.mock as mock + +from botocore.exceptions import ClientError + +from lib.route53 import Route53RecordSetLocator, Route53RecordSetAccessor, exponential_backoff, retry_with_backoff, \ + map_ips_to_resource_records, find_locator_record_set + + +class TestRoute53(unittest.TestCase): + def get_route53_client_mock(self): + route53_client = mock.Mock() + record_set_value = None + + route53_client.list_resource_record_sets = mock.Mock( + side_effect=lambda **kwargs: + {'ResourceRecordSets': [record_set_value] if record_set_value is not None else []}) + + def change_resource_record_sets(HostedZoneId, ChangeBatch): + nonlocal record_set_value + change = ChangeBatch['Changes'][0] + change_action = change['Action'] + + if change_action == 'UPSERT': + record_set_value = change['ResourceRecordSet'] + elif change_action == 'DELETE': + record_set_value = None + + route53_client.change_resource_record_sets = mock.Mock(side_effect=change_resource_record_sets) + + return route53_client + + def test_creating_records(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + merger = Route53RecordSetAccessor(route53_client) + + # WHEN + merger.update(locator, ipv4s={'1.1.1.1'}) + + # THEN + route53_client.change_resource_record_sets.assert_called_with( + HostedZoneId='foo', ChangeBatch={ + 'Comment': + 'Automatic', + 'Changes': [{ + 'Action': 'UPSERT', + 'ResourceRecordSet': { + 'Name': 'foo.myexample.com.', + 'Type': 'A', + 'ResourceRecords': [ + { + 'Value': '1.1.1.1' + }, + ], + 'TTL': 60 + } + }] + }) + + def test_creating_empty_records(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + merger = Route53RecordSetAccessor(route53_client) + + # WHEN + merger.update(locator, ipv4s=set()) + + # THEN + route53_client.change_resource_record_sets.assert_not_called() + + def test_deleting_records(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + record_set = Route53RecordSetAccessor(route53_client) + + # Set up the mock with a record. + record_set.update(locator, ipv4s={'1.1.1.1'}) + + # WHEN + record_set.update(locator, ipv4s=set()) + + # THEN + route53_client.change_resource_record_sets.assert_called_with( + HostedZoneId='foo', ChangeBatch={ + 'Comment': + 'Automatic', + 'Changes': [{ + 'Action': 'DELETE', + 'ResourceRecordSet': { + 'Name': 'foo.myexample.com.', + 'Type': 'A', + 'ResourceRecords': [ + { + 'Value': '1.1.1.1' + }, + ], + 'TTL': 60 + } + }] + }) + + def test_deleting_records_with_frontend(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + record_set = Route53RecordSetAccessor(route53_client) + + # Set up the mock with a record. + record_set.update(locator, ipv4s={'1.1.1.1'}) + + # WHEN + record_set.delete(locator) + + # THEN + self.assertEqual(route53_client.list_resource_record_sets.call_count, 2) + route53_client.change_resource_record_sets.assert_called_with( + HostedZoneId='foo', ChangeBatch={ + 'Comment': + 'Automatic', + 'Changes': [{ + 'Action': 'DELETE', + 'ResourceRecordSet': { + 'Name': 'foo.myexample.com.', + 'Type': 'A', + 'ResourceRecords': [ + { + 'Value': '1.1.1.1' + }, + ], + 'TTL': 60 + } + }] + }) + + def test_deleting_no_records_with_frontend(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + record_set = Route53RecordSetAccessor(route53_client) + + # WHEN + record_set.delete(locator) + + # THEN + self.assertEqual(route53_client.list_resource_record_sets.call_count, 1) + route53_client.change_resource_record_sets.assert_not_called() + + def test_checks_not_exists(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + record_set = Route53RecordSetAccessor(route53_client) + + # WHEN + exists = record_set.exists(locator) + + # THEN + self.assertTrue(not exists) + + def test_checks_exists(self): + # GIVEN + route53_client = self.get_route53_client_mock() + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='foo.myexample.com') + record_set = Route53RecordSetAccessor(route53_client) + + # WHEN + record_set.update(locator, ipv4s={'1.1.1.1'}) + exists = record_set.exists(locator) + + # THEN + self.assertTrue(exists) + + def test_exponential_backoff(self): + self.assertEqual(exponential_backoff(0), 1) + self.assertEqual(exponential_backoff(1), 2) + self.assertEqual(exponential_backoff(2), 4) + + def test_retry_with_backoff_throttling(self): + # GIVEN + call = mock.Mock(side_effect=ClientError(error_response={'Error': { + 'Code': 'Throttling' + }}, operation_name='any')) + + # WHEN + retry_with_backoff(call, attempts=5, backoff=lambda x: 0) + + # THEN + self.assertEqual(call.call_count, 5) + + def test_retry_with_backoff_prior_request_not_complete(self): + # GIVEN + call = mock.Mock(side_effect=ClientError(error_response={'Error': { + 'Code': 'PriorRequestNotComplete' + }}, operation_name='any')) + + # WHEN + retry_with_backoff(call, attempts=5, backoff=lambda x: 0) + + # THEN + self.assertEqual(call.call_count, 5) + + def test_retry_with_backoff_other_client_errors(self): + # GIVEN + call = mock.Mock(side_effect=ClientError(error_response={'Error': { + 'Code': 'SomethingElse' + }}, operation_name='any')) + + # WHEN/THEN + with self.assertRaisesRegex(ClientError, r'SomethingElse'): + retry_with_backoff(call, attempts=5, backoff=lambda x: 0) + self.assertEqual(call.call_count, 1) + + def test_retry_with_backoff_other_errors(self): + # GIVEN + call = mock.Mock(side_effect=Exception('very good reason')) + + # WHEN/THEN + with self.assertRaisesRegex(Exception, r'very good reason'): + retry_with_backoff(call, attempts=5, backoff=lambda x: 0) + self.assertEqual(call.call_count, 1) + + def test_map_ips_to_resource_records(self): + # GIVEN + ips = {'1.1.1.1', '1.1.1.2'} + + # WHEN + output = map_ips_to_resource_records(ips) + + # THEN + self.assertEqual(output, [{'Value': '1.1.1.1'}, {'Value': '1.1.1.2'}]) + + def test_map_ips_to_resource_records_truncates_to_400(self): + # GIVEN + ips = {f'1.1.{a}.{b}' for a in range(1, 255) for b in range(1, 255)} + + # WHEN + output = map_ips_to_resource_records(ips) + + # THEN + self.assertEqual(len(output), 400) + + def test_find_locator_record_set_ignores_irrelevant_records(self): + # GIVEN + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='test-record.myexample.com') + record_sets = [{ + 'Name': 'u-record.myexample.com.', + 'Type': 'A', + 'TTL': 60, + 'ResourceRecords': [{ + 'Value': '1.1.1.1' + }] + }] + + # WHEN + result = find_locator_record_set(locator, 'A', record_sets) + + # THEN + self.assertIsNone(result) + + def test_find_locator_record_set_finds_it(self): + # GIVEN + locator = Route53RecordSetLocator(hosted_zone_id='foo', record_name='test-record.myexample.com') + matching_record = { + 'Name': 'test-record.myexample.com.', + 'Type': 'A', + 'TTL': 60, + 'ResourceRecords': [{ + 'Value': '1.1.1.1' + }] + } + record_sets = [matching_record] + + # WHEN + result = find_locator_record_set(locator, 'A', record_sets) + + # THEN + self.assertEqual(result, matching_record) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_tasks.py b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_tasks.py new file mode 100644 index 0000000000000..b4b491750bcce --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/lambda/test/test_tasks.py @@ -0,0 +1,55 @@ +import json +import os +import unittest +import unittest.mock as mock +from datetime import datetime + +from lib.events import extract_event_task_info +from lib.records import TaskInfo, DdbRecord, DdbRecordKey +from lib.running_task_collector import RunningTaskCollector + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(THIS_DIR, 'fixtures', 'task_description.json')) as f: + TASK_DESCRIPTION = json.loads(f.read()) +with open(os.path.join(THIS_DIR, 'fixtures', 'eni_description.json')) as f: + ENI_DESCRIPTION = json.loads(f.read()) + + +class TestRunningTasksCollector(unittest.TestCase): + def test_task_collector(self): + # GIVEN + ec2_client = mock.Mock() + paginator = mock.Mock() + paginator.paginate = mock.Mock(return_value=[{'NetworkInterfaces': [ENI_DESCRIPTION]}]) + ec2_client.get_paginator = mock.Mock(return_value=paginator) + + reference_record = DdbRecord(key=DdbRecordKey(cluster_arn="A", service_name="B")) + collector = RunningTaskCollector(ec2_client=ec2_client, reference_record=reference_record) + + # WHEN + task_info = extract_event_task_info(TASK_DESCRIPTION) + collector.collect(task_info) + collector.fill_eni_info_from_eni_query() + + # THEN + paginator.paginate.assert_called_with(NetworkInterfaceIds=['eni-abcd']) + self.assertTrue('1.2.3.4' in collector.get_ips()) + + def test_task_collector_doesnt_collect_stopped_tasks(self): + # GIVEN + ec2_client = mock.Mock() + paginator = mock.Mock() + paginator.paginate = mock.Mock(return_value=[{'NetworkInterfaces': [ENI_DESCRIPTION]}]) + ec2_client.get_paginator = mock.Mock(return_value=paginator) + + task_arn = TASK_DESCRIPTION['taskArn'] + task_info = {task_arn: TaskInfo(task_arn=task_arn, enis=[], stopped_datetime=datetime.utcnow())} + reference_record = DdbRecord(key=DdbRecordKey(cluster_arn="A", service_name="B"), task_info=task_info) + collector = RunningTaskCollector(ec2_client=ec2_client, reference_record=reference_record) + + # WHEN + task_info = extract_event_task_info(TASK_DESCRIPTION) + collector.collect(task_info) + + # THEN + self.assertEqual(len(collector.tasks), 0) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/task-record-manager.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/task-record-manager.ts new file mode 100644 index 0000000000000..3772f4432cc04 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/assign-public-ip/task-record-manager.ts @@ -0,0 +1,207 @@ +import * as path from 'path'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as events from '@aws-cdk/aws-events'; +import * as events_targets from '@aws-cdk/aws-events-targets'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as lambda_es from '@aws-cdk/aws-lambda-event-sources'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as cdk from '@aws-cdk/core'; +import * as customresources from '@aws-cdk/custom-resources'; + +export interface TaskRecordManagerProps { + service: ecs.Ec2Service | ecs.FargateService; + dnsZone: route53.IHostedZone; + dnsRecordName: string; +} + +/** + * An event-driven serverless app to maintain a list of public ips in a Route 53 + * hosted zone. + */ +export class TaskRecordManager extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, props: TaskRecordManagerProps) { + super(scope, id); + + // Poison pills go here. + const deadLetterQueue = new sqs.Queue(this, 'EventsDL', { + retentionPeriod: cdk.Duration.days(14), + }); + + // Time limit for processing queue items - we set the lambda time limit to + // this value as well. + const eventsQueueVisibilityTimeout = cdk.Duration.seconds(30); + + // This queue lets us batch together ecs task state events. This is useful + // for when when we would be otherwise bombarded by them. + const eventsQueue = new sqs.Queue(this, 'EventsQueue', { + deadLetterQueue: { + maxReceiveCount: 500, + queue: deadLetterQueue, + }, + visibilityTimeout: eventsQueueVisibilityTimeout, + }); + + // Storage for task and record set information. + const recordsTable = new dynamodb.Table(this, 'Records', { + partitionKey: { + name: 'cluster_service', + type: dynamodb.AttributeType.STRING, + }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Put the cluster's task state changes events into the queue. + const runningEventRule = new events.Rule(this, 'RuleRunning', { + eventPattern: { + source: ['aws.ecs'], + detailType: ['ECS Task State Change'], + detail: { + clusterArn: [props.service.cluster.clusterArn], + lastStatus: ['RUNNING'], + desiredStatus: ['RUNNING'], + }, + }, + targets: [ + new events_targets.SqsQueue(eventsQueue), + ], + }); + + const stoppedEventRule = new events.Rule(this, 'RuleStopped', { + eventPattern: { + source: ['aws.ecs'], + detailType: ['ECS Task State Change'], + detail: { + clusterArn: [props.service.cluster.clusterArn], + lastStatus: ['STOPPED'], + desiredStatus: ['STOPPED'], + }, + }, + targets: [ + new events_targets.SqsQueue(eventsQueue), + ], + }); + + // Shared codebase for the lambdas. + const code = lambda.Code.fromAsset(path.join(__dirname, 'lambda'), { + exclude: [ + '.coverage', + '*.pyc', + '.idea', + ], + }); + + // Fully qualified domain name of the record + const recordFqdn = cdk.Fn.join('.', [props.dnsRecordName, props.dnsZone.zoneName]); + + // Allow access to manage a zone's records. + const dnsPolicyStatement = new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 'route53:ChangeResourceRecordSets', + 'route53:ListResourceRecordSets', + ], + resources: [props.dnsZone.hostedZoneArn], + }); + + // This function consumes events from the event queue and does the work of + // querying task IP addresses and creating, updating record sets. When there + // are zero tasks, it deletes the record set. + const eventHandler = new lambda.Function(this, 'EventHandler', { + code: code, + handler: 'index.queue_handler', + runtime: lambda.Runtime.PYTHON_3_8, + timeout: eventsQueueVisibilityTimeout, + // Single-concurrency to prevent a race to set the RecordSet + reservedConcurrentExecutions: 1, + environment: { + HOSTED_ZONE_ID: props.dnsZone.hostedZoneId, + RECORD_NAME: recordFqdn, + RECORDS_TABLE: recordsTable.tableName, + CLUSTER_ARN: props.service.cluster.clusterArn, + SERVICE_NAME: props.service.serviceName, + }, + events: [ + new lambda_es.SqsEventSource(eventsQueue), + ], + initialPolicy: [ + // Look up task IPs + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ec2:DescribeNetworkInterfaces'], + resources: ['*'], + }), + dnsPolicyStatement, + ], + }); + recordsTable.grantReadWriteData(eventHandler); + + // The lambda for a custom resource provider that deletes dangling record + // sets when the stack is deleted. + const cleanupResourceProviderHandler = new lambda.Function(this, 'CleanupResourceProviderHandler', { + code: code, + handler: 'index.cleanup_resource_handler', + runtime: lambda.Runtime.PYTHON_3_8, + timeout: cdk.Duration.minutes(5), + initialPolicy: [ + dnsPolicyStatement, + ], + }); + + const cleanupResourceProvider = new customresources.Provider(this, 'CleanupResourceProvider', { + onEventHandler: cleanupResourceProviderHandler, + }); + + const cleanupResource = new cdk.CustomResource(this, 'Cleanup', { + serviceToken: cleanupResourceProvider.serviceToken, + properties: { + HostedZoneId: props.dnsZone.hostedZoneId, + RecordName: recordFqdn, + }, + }); + + // Prime the event queue with a message so that changes to dns config are + // quickly applied. + const primingSdkCall: customresources.AwsSdkCall = { + service: 'SQS', + action: 'sendMessage', + parameters: { + QueueUrl: eventsQueue.queueUrl, + DelaySeconds: 10, + MessageBody: '{ "prime": true }', + // Add the hosted zone id and record name so that priming occurs with + // dns config updates. + MessageAttributes: { + HostedZoneId: { DataType: 'String', StringValue: props.dnsZone.hostedZoneId }, + RecordName: { DataType: 'String', StringValue: props.dnsRecordName }, + }, + }, + physicalResourceId: customresources.PhysicalResourceId.fromResponse('MessageId'), + }; + + const primingCall = new customresources.AwsCustomResource(this, 'PrimingCall', { + onCreate: primingSdkCall, + onUpdate: primingSdkCall, + policy: customresources.AwsCustomResourcePolicy.fromStatements([ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sqs:SendMessage'], + resources: [eventsQueue.queueArn], + }), + ]), + }); + + // Send the priming call after the handler is created/updated. + primingCall.node.addDependency(eventHandler); + + // Ensure that the cleanup resource is deleted last (so it can clean up) + props.service.taskDefinition.node.addDependency(cleanupResource); + // Ensure that the event rules are created first so we can catch the first + // state transitions. + props.service.taskDefinition.node.addDependency(runningEventRule); + props.service.taskDefinition.node.addDependency(stoppedEventRule); + } +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts index cdd25613f0fef..1dac2d9257e73 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts @@ -37,6 +37,15 @@ export interface ServiceBuild { */ readonly taskDefinition: ecs.TaskDefinition, + /** + * Specifies whether the task's elastic network interface receives a public IP address. + * + * If true, each task will receive a public IP address. + * + * @default false + */ + readonly assignPublicIp?: boolean; + /** * Configuration for how to register the service in service discovery * @default - No Cloud Map configured diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts index e43005cf15bc6..4e464e0d0734e 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/index.ts @@ -4,4 +4,5 @@ export * from './appmesh'; export * from './http-load-balancer'; export * from './cloudwatch-agent'; export * from './scale-on-cpu-utilization'; -export * from './xray'; \ No newline at end of file +export * from './xray'; +export * from './assign-public-ip'; diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/package.json b/packages/@aws-cdk-containers/ecs-service-extensions/package.json index 31af1ded594ec..993be82075433 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/package.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/package.json @@ -40,7 +40,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^25.5.4", + "jest": "^26.6.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, @@ -56,12 +56,15 @@ "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-events-targets": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-event-sources": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", "@aws-cdk/aws-route53-targets": "0.0.0", "@aws-cdk/aws-servicediscovery": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.4" }, @@ -78,12 +81,15 @@ "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-events-targets": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-lambda-event-sources": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", "@aws-cdk/aws-route53-targets": "0.0.0", "@aws-cdk/aws-servicediscovery": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.4" }, @@ -95,4 +101,4 @@ }, "maturity": "experimental", "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json new file mode 100644 index 0000000000000..3abc980a7fae3 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.expected.json @@ -0,0 +1,1315 @@ +{ + "Resources": { + "vpcA2121C38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc" + } + ] + } + }, + "vpcpublicSubnet1SubnetA635257E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/24", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet1" + } + ] + } + }, + "vpcpublicSubnet1RouteTableA38152FE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet1" + } + ] + } + }, + "vpcpublicSubnet1RouteTableAssociationB46101B8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet1RouteTableA38152FE" + }, + "SubnetId": { + "Ref": "vpcpublicSubnet1SubnetA635257E" + } + } + }, + "vpcpublicSubnet1DefaultRouteF0973989": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet1RouteTableA38152FE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcpublicSubnet2Subnet027D165B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.1.0/24", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet2" + } + ] + } + }, + "vpcpublicSubnet2RouteTableA6135437": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet2" + } + ] + } + }, + "vpcpublicSubnet2RouteTableAssociation73F6478A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet2RouteTableA6135437" + }, + "SubnetId": { + "Ref": "vpcpublicSubnet2Subnet027D165B" + } + } + }, + "vpcpublicSubnet2DefaultRoute13685A07": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet2RouteTableA6135437" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcpublicSubnet3Subnet3B90E684": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.2.0/24", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet3" + } + ] + } + }, + "vpcpublicSubnet3RouteTable901FAA39": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc/publicSubnet3" + } + ] + } + }, + "vpcpublicSubnet3RouteTableAssociationF6210B68": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet3RouteTable901FAA39" + }, + "SubnetId": { + "Ref": "vpcpublicSubnet3Subnet3B90E684" + } + } + }, + "vpcpublicSubnet3DefaultRoute02D8E508": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcpublicSubnet3RouteTable901FAA39" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcIGWE57CBDCA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ/vpc" + } + ] + } + }, + "vpcVPCGW7984C166": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "InternetGatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + } + }, + "zoneEB40FF1E": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "myexample.com." + } + }, + "laterRecordD393EDE6": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "u-record.myexample.com.", + "Type": "CNAME", + "HostedZoneId": { + "Ref": "zoneEB40FF1E" + }, + "ResourceRecords": [ + "console.aws.amazon.com" + ], + "TTL": "1800" + } + }, + "productionenvironmentclusterC6599D2D": { + "Type": "AWS::ECS::Cluster" + }, + "nametaskdefinitionTaskRole50FE844E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + }, + "DependsOn": [ + "nameserviceTaskRecordManagerCleanupE19F1043", + "nameserviceTaskRecordManagerRuleRunningCD85F46F", + "nameserviceTaskRecordManagerRuleStopped66D08B70" + ] + }, + "nametaskdefinition690762BB": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 256, + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 512, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + } + ], + "Cpu": "256", + "Family": "awsecsintegnametaskdefinition0EA6A1A0", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "nametaskdefinitionTaskRole50FE844E", + "Arn" + ] + } + }, + "DependsOn": [ + "nameserviceTaskRecordManagerCleanupE19F1043", + "nameserviceTaskRecordManagerRuleRunningCD85F46F", + "nameserviceTaskRecordManagerRuleStopped66D08B70" + ] + }, + "nameserviceService8015C8D6": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "productionenvironmentclusterC6599D2D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "ENABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "nameserviceSecurityGroup33F4662C", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "vpcpublicSubnet1SubnetA635257E" + }, + { + "Ref": "vpcpublicSubnet2Subnet027D165B" + }, + { + "Ref": "vpcpublicSubnet3Subnet3B90E684" + } + ] + } + }, + "TaskDefinition": { + "Ref": "nametaskdefinition690762BB" + } + } + }, + "nameserviceSecurityGroup33F4662C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ/name-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Accept inbound traffic on traffic port from anywhere", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + } + }, + "nameserviceTaskRecordManagerEventsDLB8F13E1E": { + "Type": "AWS::SQS::Queue", + "Properties": { + "MessageRetentionPeriod": 1209600 + } + }, + "nameserviceTaskRecordManagerEventsQueueF805A6C1": { + "Type": "AWS::SQS::Queue", + "Properties": { + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsDLB8F13E1E", + "Arn" + ] + }, + "maxReceiveCount": 500 + }, + "VisibilityTimeout": 30 + } + }, + "nameserviceTaskRecordManagerEventsQueuePolicy65CC6F9E": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerRuleRunningCD85F46F", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + } + }, + { + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerRuleStopped66D08B70", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "nameserviceTaskRecordManagerEventsQueueF805A6C1" + } + ] + } + }, + "nameserviceTaskRecordManagerRecordsA4648C6E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "cluster_service", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "cluster_service", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "nameserviceTaskRecordManagerRuleRunningCD85F46F": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.ecs" + ], + "detail-type": [ + "ECS Task State Change" + ], + "detail": { + "clusterArn": [ + { + "Fn::GetAtt": [ + "productionenvironmentclusterC6599D2D", + "Arn" + ] + } + ], + "lastStatus": [ + "RUNNING" + ], + "desiredStatus": [ + "RUNNING" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + }, + "Id": "Target0" + } + ] + } + }, + "nameserviceTaskRecordManagerRuleStopped66D08B70": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.ecs" + ], + "detail-type": [ + "ECS Task State Change" + ], + "detail": { + "clusterArn": [ + { + "Fn::GetAtt": [ + "productionenvironmentclusterC6599D2D", + "Arn" + ] + } + ], + "lastStatus": [ + "STOPPED" + ], + "desiredStatus": [ + "STOPPED" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + }, + "Id": "Target0" + } + ] + } + }, + "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "nameserviceTaskRecordManagerEventHandlerServiceRoleDefaultPolicy96DD8140": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ec2:DescribeNetworkInterfaces", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":route53:::hostedzone/", + { + "Ref": "zoneEB40FF1E" + } + ] + ] + } + }, + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + } + }, + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerRecordsA4648C6E", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameserviceTaskRecordManagerEventHandlerServiceRoleDefaultPolicy96DD8140", + "Roles": [ + { + "Ref": "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A" + } + ] + } + }, + "nameserviceTaskRecordManagerEventHandler4B8C6905": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3Bucket52DB41A7" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3VersionKey0F583551" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3VersionKey0F583551" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.queue_handler", + "Role": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A", + "Arn" + ] + }, + "Runtime": "python3.8", + "Environment": { + "Variables": { + "HOSTED_ZONE_ID": { + "Ref": "zoneEB40FF1E" + }, + "RECORD_NAME": "test-record.myexample.com", + "RECORDS_TABLE": { + "Ref": "nameserviceTaskRecordManagerRecordsA4648C6E" + }, + "CLUSTER_ARN": { + "Fn::GetAtt": [ + "productionenvironmentclusterC6599D2D", + "Arn" + ] + }, + "SERVICE_NAME": { + "Fn::GetAtt": [ + "nameserviceService8015C8D6", + "Name" + ] + } + } + }, + "ReservedConcurrentExecutions": 1, + "Timeout": 30 + }, + "DependsOn": [ + "nameserviceTaskRecordManagerEventHandlerServiceRoleDefaultPolicy96DD8140", + "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A" + ] + }, + "nameserviceTaskRecordManagerEventHandlerSqsEventSourceawsecsintegnameserviceTaskRecordManagerEventsQueueC5EE9A869F1EB155": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + }, + "FunctionName": { + "Ref": "nameserviceTaskRecordManagerEventHandler4B8C6905" + } + } + }, + "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleCCA462F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleDefaultPolicy7D095576": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":route53:::hostedzone/", + { + "Ref": "zoneEB40FF1E" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleDefaultPolicy7D095576", + "Roles": [ + { + "Ref": "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleCCA462F0" + } + ] + } + }, + "nameserviceTaskRecordManagerCleanupResourceProviderHandler08068F99": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3Bucket52DB41A7" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3VersionKey0F583551" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3VersionKey0F583551" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.cleanup_resource_handler", + "Role": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleCCA462F0", + "Arn" + ] + }, + "Runtime": "python3.8", + "Timeout": 300 + }, + "DependsOn": [ + "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleDefaultPolicy7D095576", + "nameserviceTaskRecordManagerCleanupResourceProviderHandlerServiceRoleCCA462F0" + ] + }, + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleF0570BD0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleDefaultPolicy350D6FAC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerCleanupResourceProviderHandler08068F99", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleDefaultPolicy350D6FAC", + "Roles": [ + { + "Ref": "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleF0570BD0" + } + ] + } + }, + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEvent9B27C899": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleF0570BD0", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - onEvent (aws-ecs-integ/name-service/TaskRecordManager/CleanupResourceProvider)", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerCleanupResourceProviderHandler08068F99", + "Arn" + ] + } + } + }, + "Timeout": 900 + }, + "DependsOn": [ + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleDefaultPolicy350D6FAC", + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEventServiceRoleF0570BD0" + ] + }, + "nameserviceTaskRecordManagerCleanupE19F1043": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerCleanupResourceProviderframeworkonEvent9B27C899", + "Arn" + ] + }, + "HostedZoneId": { + "Ref": "zoneEB40FF1E" + }, + "RecordName": "test-record.myexample.com" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "nameserviceTaskRecordManagerPrimingCallCustomResourcePolicy376F02F0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "nameserviceTaskRecordManagerEventsQueueF805A6C1", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "nameserviceTaskRecordManagerPrimingCallCustomResourcePolicy376F02F0", + "Roles": [ + { + "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + } + ] + }, + "DependsOn": [ + "nameserviceTaskRecordManagerEventHandler4B8C6905", + "nameserviceTaskRecordManagerEventHandlerServiceRoleDefaultPolicy96DD8140", + "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A", + "nameserviceTaskRecordManagerEventHandlerSqsEventSourceawsecsintegnameserviceTaskRecordManagerEventsQueueC5EE9A869F1EB155" + ] + }, + "nameserviceTaskRecordManagerPrimingCallE6113369": { + "Type": "Custom::AWS", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn" + ] + }, + "Create": { + "service": "SQS", + "action": "sendMessage", + "parameters": { + "QueueUrl": { + "Ref": "nameserviceTaskRecordManagerEventsQueueF805A6C1" + }, + "DelaySeconds": 10, + "MessageBody": "{ \"prime\": true }", + "MessageAttributes": { + "HostedZoneId": { + "DataType": "String", + "StringValue": { + "Ref": "zoneEB40FF1E" + } + }, + "RecordName": { + "DataType": "String", + "StringValue": "test-record" + } + } + }, + "physicalResourceId": { + "responsePath": "MessageId" + } + }, + "Update": { + "service": "SQS", + "action": "sendMessage", + "parameters": { + "QueueUrl": { + "Ref": "nameserviceTaskRecordManagerEventsQueueF805A6C1" + }, + "DelaySeconds": 10, + "MessageBody": "{ \"prime\": true }", + "MessageAttributes": { + "HostedZoneId": { + "DataType": "String", + "StringValue": { + "Ref": "zoneEB40FF1E" + } + }, + "RecordName": { + "DataType": "String", + "StringValue": "test-record" + } + } + }, + "physicalResourceId": { + "responsePath": "MessageId" + } + }, + "InstallLatestAwsSdk": true + }, + "DependsOn": [ + "nameserviceTaskRecordManagerEventHandler4B8C6905", + "nameserviceTaskRecordManagerEventHandlerServiceRoleDefaultPolicy96DD8140", + "nameserviceTaskRecordManagerEventHandlerServiceRoleE66EE52A", + "nameserviceTaskRecordManagerEventHandlerSqsEventSourceawsecsintegnameserviceTaskRecordManagerEventsQueueC5EE9A869F1EB155", + "nameserviceTaskRecordManagerPrimingCallCustomResourcePolicy376F02F0" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94S3Bucket38F1BB8E" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94S3VersionKeyCCDC67C0" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94S3VersionKeyCCDC67C0" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 120 + }, + "DependsOn": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + ] + } + }, + "Parameters": { + "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3Bucket52DB41A7": { + "Type": "String", + "Description": "S3 bucket for asset \"c02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4\"" + }, + "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4S3VersionKey0F583551": { + "Type": "String", + "Description": "S3 key for asset version \"c02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4\"" + }, + "AssetParametersc02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4ArtifactHash85B8E84D": { + "Type": "String", + "Description": "Artifact hash for asset \"c02a0f4f3094701fe7fc414690fc416909b3a1f04dcba086e366807a15f2b0b4\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { + "Type": "String", + "Description": "S3 bucket for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F": { + "Type": "String", + "Description": "S3 key for asset version \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1ArtifactHashA521A16F": { + "Type": "String", + "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94S3Bucket38F1BB8E": { + "Type": "String", + "Description": "S3 bucket for asset \"b64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94\"" + }, + "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94S3VersionKeyCCDC67C0": { + "Type": "String", + "Description": "S3 key for asset version \"b64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94\"" + }, + "AssetParametersb64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94ArtifactHash782948FC": { + "Type": "String", + "Description": "Artifact hash for asset \"b64b129569a5ac7a9abf88a18ac0b504d1fb1208872460476ed3fd435830eb94\"" + } + }, + "Outputs": { + "DnsName": { + "Value": "test-record.myexample.com" + }, + "DnsServer": { + "Value": { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "zoneEB40FF1E", + "NameServers" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.ts new file mode 100644 index 0000000000000..c7464cd28b671 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.assign-public-ip.ts @@ -0,0 +1,94 @@ +import { SubnetType, Vpc } from '@aws-cdk/aws-ec2'; +import { ContainerImage } from '@aws-cdk/aws-ecs'; +import { CnameRecord, PublicHostedZone } from '@aws-cdk/aws-route53'; +import { App, CfnOutput, Fn, Stack } from '@aws-cdk/core'; +import { AssignPublicIpExtension, Container, Environment, Service, ServiceDescription } from '../lib'; + +// Record name. You can change this and redeploy this integration test to see +// what happens when the record name changes. +const RECORD_NAME = 'test-record'; + +const app = new App(); +const stack = new Stack(app, 'aws-ecs-integ'); + +const vpc = new Vpc(stack, 'vpc', { + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: SubnetType.PUBLIC, + }, + ], +}); + +const dnsZone = new PublicHostedZone(stack, 'zone', { + zoneName: 'myexample.com', +}); + +// A record in the zone that is lexicographically later than 'test-record' +// to try to trip up the record set locator. +new CnameRecord(stack, 'laterRecord', { + recordName: 'u-record', + zone: dnsZone, + domainName: 'console.aws.amazon.com', +}); + +const environment = new Environment(stack, 'production', { vpc }); + +const nameDescription = new ServiceDescription(); + +nameDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, +})); + +nameDescription.add(new AssignPublicIpExtension({ + dns: { + zone: dnsZone, + recordName: RECORD_NAME, + }, +})); + +new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, +}); + +new CfnOutput(stack, 'DnsName', { + value: Fn.join('.', [RECORD_NAME, dnsZone.zoneName]), +}); + +new CfnOutput(stack, 'DnsServer', { + value: Fn.select(0, dnsZone.hostedZoneNameServers!), +}); + +/** + * Expect this stack to deploy. The stack outputs include a DNS name and a + * nameserver. A short time after the services have settled, you may query the + * nameserver for the record. If an IP address is shown, then this test has + * succeeded. + * + * Example: + * + * ``` + * $ cdk --app 'node ./integ.assign-public-ip.js' deploy + * ... + * Outputs: + * aws-ecs-integ.DnsName = test-record.myexample.com + * aws-ecs-integ.DnsServer = ns-1836.awsdns-37.co.uk + * ... + * + * $ host test-record.myexample.com ns-1836.awsdns-37.co.uk + * Using domain server: + * Name: ns-1836.awsdns-37.co.uk + * Address: 2600:9000:5307:2c00::1#53 + * Aliases: + * + * test-record.myexample.com has address 52.60.53.62 + * ``` + */ diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.assign-public-ip.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.assign-public-ip.ts new file mode 100644 index 0000000000000..92a36dd1d788b --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.assign-public-ip.ts @@ -0,0 +1,199 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { AssignPublicIpExtension, Container, Environment, EnvironmentCapacityType, Service, ServiceDescription } from '../lib'; +import { TaskRecordManager } from '../lib/extensions/assign-public-ip/task-record-manager'; + +export = { + 'should assign a public ip to fargate tasks'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new AssignPublicIpExtension()); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::Service', { + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'ENABLED', + }, + }, + })); + + test.done(); + }, + + 'errors when adding a public ip to ec2-backed service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + const environment = new Environment(stack, 'production', { + vpc, + cluster, + capacityType: EnvironmentCapacityType.EC2, + }); + + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new AssignPublicIpExtension()); + + // WHEN / THEN + test.throws(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + }, /Fargate/i); + + test.done(); + }, + + 'should not add a task record manager by default'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + // WHEN + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new AssignPublicIpExtension()); + + const service = new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + test.strictEqual(service.ecsService.node.tryFindChild('TaskRecordManager'), undefined, 'task record manager should not be present'); + + test.done(); + }, + + 'should add a task record manager when dns is requested'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const dnsZone = new route53.PublicHostedZone(stack, 'zone', { + zoneName: 'myexample.com', + }); + + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + // WHEN + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new AssignPublicIpExtension({ + dns: { + zone: dnsZone, + recordName: 'test-record', + }, + })); + + const service = new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // THEN + test.notEqual(service.ecsService.node.tryFindChild('TaskRecordManager'), undefined, 'task record manager should be present'); + + test.done(); + }, + + 'task record manager listens for ecs events'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const dnsZone = new route53.PublicHostedZone(stack, 'zone', { + zoneName: 'myexample.com', + }); + + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + serviceDescription.add(new AssignPublicIpExtension()); + + const service = new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + + // WHEN + new TaskRecordManager(stack, 'manager', { + dnsRecordName: 'test-record', + dnsZone: dnsZone, + service: service.ecsService, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + EventPattern: { + 'source': ['aws.ecs'], + 'detail-type': [ + 'ECS Task State Change', + ], + 'detail': { + lastStatus: ['RUNNING'], + desiredStatus: ['RUNNING'], + }, + }, + })); + + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + EventPattern: { + 'source': ['aws.ecs'], + 'detail-type': [ + 'ECS Task State Change', + ], + 'detail': { + lastStatus: ['STOPPED'], + desiredStatus: ['STOPPED'], + }, + }, + })); + + test.done(); + }, +} diff --git a/packages/@aws-cdk/alexa-ask/.npmignore b/packages/@aws-cdk/alexa-ask/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/alexa-ask/.npmignore +++ b/packages/@aws-cdk/alexa-ask/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/alexa-ask/package.json b/packages/@aws-cdk/alexa-ask/package.json index 8435ce066a29c..26aaaf75630b6 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "Alexa::ASK", diff --git a/packages/@aws-cdk/app-delivery/.npmignore b/packages/@aws-cdk/app-delivery/.npmignore index 82e007c2c1c49..d04129afefc48 100644 --- a/packages/@aws-cdk/app-delivery/.npmignore +++ b/packages/@aws-cdk/app-delivery/.npmignore @@ -20,4 +20,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md index 85e378002e5d1..949cf90b6c714 100644 --- a/packages/@aws-cdk/app-delivery/README.md +++ b/packages/@aws-cdk/app-delivery/README.md @@ -17,7 +17,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw # Replacement recommended This library has been deprecated. We recommend you use the -[@aws-cdk/pipelines](https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines.html) module instead. +[@aws-cdk/pipelines](https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines-readme.html) module instead. ### Limitations diff --git a/packages/@aws-cdk/assert/.npmignore b/packages/@aws-cdk/assert/.npmignore index 582a6ea324723..6f149ce45fddd 100644 --- a/packages/@aws-cdk/assert/.npmignore +++ b/packages/@aws-cdk/assert/.npmignore @@ -18,4 +18,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index e2476231c0af3..047998b2e3ef9 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/jest": "^26.0.14", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "ts-jest": "^26.4.1" }, @@ -37,7 +37,7 @@ "peerDependencies": { "@aws-cdk/core": "0.0.0", "constructs": "^3.0.4", - "jest": "^26.4.2" + "jest": "^26.6.0" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/assets/.npmignore b/packages/@aws-cdk/assets/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/assets/.npmignore +++ b/packages/@aws-cdk/assets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-accessanalyzer/.npmignore b/packages/@aws-cdk/aws-accessanalyzer/.npmignore index a7c5b49852b3b..c2827f80c26db 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/.npmignore +++ b/packages/@aws-cdk/aws-accessanalyzer/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-accessanalyzer/package.json b/packages/@aws-cdk/aws-accessanalyzer/package.json index cba163f1e1914..530a3eddf7014 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/package.json +++ b/packages/@aws-cdk/aws-accessanalyzer/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AccessAnalyzer", diff --git a/packages/@aws-cdk/aws-acmpca/.npmignore b/packages/@aws-cdk/aws-acmpca/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-acmpca/.npmignore +++ b/packages/@aws-cdk/aws-acmpca/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-acmpca/package.json b/packages/@aws-cdk/aws-acmpca/package.json index 7941394fee2af..c1b3fb93577e8 100644 --- a/packages/@aws-cdk/aws-acmpca/package.json +++ b/packages/@aws-cdk/aws-acmpca/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ACMPCA", diff --git a/packages/@aws-cdk/aws-amazonmq/.npmignore b/packages/@aws-cdk/aws-amazonmq/.npmignore index 3dffd1ce79a72..2892fc6e99416 100644 --- a/packages/@aws-cdk/aws-amazonmq/.npmignore +++ b/packages/@aws-cdk/aws-amazonmq/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index 70b7db473e7fc..6bca2f8c0d800 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AmazonMQ", diff --git a/packages/@aws-cdk/aws-amplify/.npmignore b/packages/@aws-cdk/aws-amplify/.npmignore index 917201c845418..f3eaded585e2d 100644 --- a/packages/@aws-cdk/aws-amplify/.npmignore +++ b/packages/@aws-cdk/aws-amplify/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index 9d2fd24c60e62..4e220c8fc1faa 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Amplify", diff --git a/packages/@aws-cdk/aws-apigateway/.npmignore b/packages/@aws-cdk/aws-apigateway/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-apigateway/.npmignore +++ b/packages/@aws-cdk/aws-apigateway/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 852801b79aa5d..2a71331596f5d 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -22,6 +22,7 @@ running on AWS Lambda, or any web application. - [Breaking up Methods and Resources across Stacks](#breaking-up-methods-and-resources-across-stacks) - [AWS Lambda-backed APIs](#aws-lambda-backed-apis) - [Integration Targets](#integration-targets) +- [API Keys](#api-keys) - [Working with models](#working-with-models) - [Default Integration and Method Options](#default-integration-and-method-options) - [Proxy Routes](#proxy-routes) @@ -29,6 +30,7 @@ running on AWS Lambda, or any web application. - [IAM-based authorizer](#iam-based-authorizer) - [Lambda-based token authorizer](#lambda-based-token-authorizer) - [Lambda-based request authorizer](#lambda-based-request-authorizer) +- [Mutual TLS](#mutal-tls-mtls) - [Deployments](#deployments) - [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments) - [Custom Domains](#custom-domains) @@ -39,6 +41,7 @@ running on AWS Lambda, or any web application. - [Gateway Response](#gateway-response) - [OpenAPI Definition](#openapi-definition) - [Endpoint configuration](#endpoint-configuration) +- [Metrics](#metrics) - [APIGateway v2](#apigateway-v2) ## Defining APIs @@ -151,6 +154,8 @@ book.addMethod('GET', getBookIntegration, { }); ``` +## API Keys + The following example shows how to use an API Key with a usage plan: ```ts @@ -207,6 +212,13 @@ Existing API keys can also be imported into a CDK app using its id. const importedKey = ApiKey.fromApiKeyId(this, 'imported-key', ''); ``` +The "grant" methods can be used to give prepackaged sets of permissions to other resources. The +following code provides read permission to an API key. + +```ts +importedKey.grantRead(lambda); +``` + In scenarios where you need to create a single api key and configure rate limiting for it, you can use `RateLimitedApiKey`. This construct lets you specify rate limiting properties which should be applied only to the api key being created. The API key created has the specified rate limits, such as quota and throttles, applied. @@ -473,7 +485,7 @@ iamUser.attachInlinePolicy(new iam.Policy(this, 'AllowBooks', { new iam.PolicyStatement({ actions: [ 'execute-api:Invoke' ], effect: iam.Effect.Allow, - resources: [ getBooks.methodArn() ] + resources: [ getBooks.methodArn ] }) ] })) @@ -562,6 +574,24 @@ Authorizers can also be passed via the `defaultMethodOptions` property within th explicitly overridden, the specified defaults will be applied across all `Method`s across the `RestApi` or across all `Resource`s, depending on where the defaults were specified. +## Mutual TLS (mTLS) + +Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. + +```ts +new apigw.DomainName(this, 'domain-name', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(this, 'cert' 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + mtls: { + bucket: new Bucket(this, 'bucket')), + key: 'truststore.pem', + version: 'version', + }, +}); +``` + +Instructions for configuring your trust store can be found [here](https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/). + ## Deployments By default, the `RestApi` construct will automatically create an API Gateway @@ -930,6 +960,11 @@ const integration = new apigw.Integration({ }); ``` +The uri for the private integration, in the case of a VpcLink, will be set to the DNS name of +the VPC Link's NLB. If the VPC Link has multiple NLBs or the VPC Link is imported or the DNS +name cannot be determined for any other reason, the user is expected to specify the `uri` +property. + Any existing `VpcLink` resource can be imported into the CDK app via the `VpcLink.fromVpcLinkId()`. ```ts @@ -1016,6 +1051,18 @@ const api = new apigateway.SpecRestApi(this, 'ExampleRestApi', { [`x-amazon-apigateway-endpoint-configuration`](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html) in your openApi file. +## Metrics + +The API Gateway service sends metrics around the performance of Rest APIs to Amazon CloudWatch. +These metrics can be referred to using the metric APIs available on the `RestApi` construct. +The APIs with the `metric` prefix can be used to get reference to specific metrics for this API. For example, +the method below refers to the client side errors metric for this API. + +```ts +const api = new apigw.RestApi(stack, 'my-api'); +const clientErrorMetric = api.metricClientError(); +``` + ## APIGateway v2 APIGateway v2 APIs are now moved to its own package named `aws-apigatewayv2`. For backwards compatibility, existing diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index 30501ff513130..84722c8811864 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,8 +1,10 @@ -import { IResource as IResourceBase, Resource } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import { IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from './resource'; import { RestApi } from './restapi'; +import { QuotaSettings, ThrottleSettings, UsagePlan, UsagePlanPerApiStage } from './usage-plan'; /** * API keys are alphanumeric string values that you distribute to @@ -14,6 +16,11 @@ export interface IApiKey extends IResourceBase { * @attribute */ readonly keyId: string; + + /** + * The API key ARN. + */ + readonly keyArn: string; } /** @@ -75,26 +82,81 @@ export interface ApiKeyProps extends ApiKeyOptions { readonly generateDistinctId?: boolean; } +/** + * Base implementation that is common to the various implementations of IApiKey + */ +abstract class ApiKeyBase extends Resource implements IApiKey { + public abstract readonly keyId: string; + public abstract readonly keyArn: string; + + /** + * Permits the IAM principal all read operations through this key + * + * @param grantee The principal to grant access to + */ + public grantRead(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: readPermissions, + resourceArns: [this.keyArn], + }); + } + + /** + * Permits the IAM principal all write operations through this key + * + * @param grantee The principal to grant access to + */ + public grantWrite(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: writePermissions, + resourceArns: [this.keyArn], + }); + } + + /** + * Permits the IAM principal all read and write operations through this key + * + * @param grantee The principal to grant access to + */ + public grantReadWrite(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: [...readPermissions, ...writePermissions], + resourceArns: [this.keyArn], + }); + } +} + /** * An API Gateway ApiKey. * * An ApiKey can be distributed to API clients that are executing requests * for Method resources that require an Api Key. */ -export class ApiKey extends Resource implements IApiKey { +export class ApiKey extends ApiKeyBase { /** * Import an ApiKey by its Id */ public static fromApiKeyId(scope: Construct, id: string, apiKeyId: string): IApiKey { - class Import extends Resource implements IApiKey { + class Import extends ApiKeyBase { public keyId = apiKeyId; + public keyArn = Stack.of(this).formatArn({ + service: 'apigateway', + account: '', + resource: '/apikeys', + sep: '/', + resourceName: apiKeyId, + }); } return new Import(scope, id); } public readonly keyId: string; + public readonly keyArn: string; constructor(scope: Construct, id: string, props: ApiKeyProps = { }) { super(scope, id, { @@ -112,6 +174,13 @@ export class ApiKey extends Resource implements IApiKey { }); this.keyId = resource.ref; + this.keyArn = Stack.of(this).formatArn({ + service: 'apigateway', + account: '', + resource: '/apikeys', + sep: '/', + resourceName: this.keyId, + }); } private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined { @@ -127,3 +196,67 @@ export class ApiKey extends Resource implements IApiKey { }); } } + +/** + * RateLimitedApiKey properties. + */ +export interface RateLimitedApiKeyProps extends ApiKeyProps { + /** + * API Stages to be associated with the RateLimitedApiKey. + * @default none + */ + readonly apiStages?: UsagePlanPerApiStage[]; + + /** + * Number of requests clients can make in a given time period. + * @default none + */ + readonly quota?: QuotaSettings; + + /** + * Overall throttle settings for the API. + * @default none + */ + readonly throttle?: ThrottleSettings; +} + +/** + * An API Gateway ApiKey, for which a rate limiting configuration can be specified. + * + * @resource AWS::ApiGateway::ApiKey + */ +export class RateLimitedApiKey extends ApiKeyBase { + public readonly keyId: string; + public readonly keyArn: string; + + constructor(scope: Construct, id: string, props: RateLimitedApiKeyProps = { }) { + super(scope, id, { + physicalName: props.apiKeyName, + }); + + const resource = new ApiKey(this, 'Resource', props); + + if (props.apiStages || props.quota || props.throttle) { + new UsagePlan(this, 'UsagePlanResource', { + apiKey: resource, + apiStages: props.apiStages, + quota: props.quota, + throttle: props.throttle, + }); + } + + this.keyId = resource.keyId; + this.keyArn = resource.keyArn; + } +} + +const readPermissions = [ + 'apigateway:GET', +]; + +const writePermissions = [ + 'apigateway:POST', + 'apigateway:PUT', + 'apigateway:PATCH', + 'apigateway:DELETE', +]; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts index 6026fe3eaecd3..17fa52ee7005c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts @@ -2,7 +2,7 @@ import { Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBasePathMapping } from './apigateway.generated'; import { IDomainName } from './domain-name'; -import { IRestApi, RestApi } from './restapi'; +import { IRestApi, RestApiBase } from './restapi'; import { Stage } from './stage'; export interface BasePathMappingOptions { @@ -55,7 +55,7 @@ export class BasePathMapping extends Resource { // if restApi is an owned API and it has a deployment stage, map all requests // to that stage. otherwise, the stage will have to be specified in the URL. - const stage = props.stage ?? (props.restApi instanceof RestApi + const stage = props.stage ?? (props.restApi instanceof RestApiBase ? props.restApi.deploymentStage : undefined); diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index f3b5074fb65a5..3252bb1691307 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -1,4 +1,5 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; +import { IBucket } from '@aws-cdk/aws-s3'; import { IResource, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDomainName } from './apigateway.generated'; @@ -40,6 +41,12 @@ export interface DomainNameOptions { * @default SecurityPolicy.TLS_1_0 */ readonly securityPolicy?: SecurityPolicy + + /** + * The mutual TLS authentication configuration for a custom domain name. + * @default - mTLS is not configured. + */ + readonly mtls?: MTLSConfig } export interface DomainNameProps extends DomainNameOptions { @@ -76,6 +83,7 @@ export interface IDomainName extends IResource { * @attribute DistributionHostedZoneId,RegionalHostedZoneId */ readonly domainNameAliasHostedZoneId: string; + } export class DomainName extends Resource implements IDomainName { @@ -107,12 +115,13 @@ export class DomainName extends Resource implements IDomainName { throw new Error('domainName does not support uppercase letters. ' + `got: '${props.domainName}'`); } - + const mtlsConfig = this.configureMTLS(props.mtls); const resource = new CfnDomainName(this, 'Resource', { domainName: props.domainName, certificateArn: edge ? props.certificate.certificateArn : undefined, regionalCertificateArn: edge ? undefined : props.certificate.certificateArn, endpointConfiguration: { types: [endpointType] }, + mutualTlsAuthentication: mtlsConfig, securityPolicy: props.securityPolicy, }); @@ -145,6 +154,14 @@ export class DomainName extends Resource implements IDomainName { ...options, }); } + + private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined { + if (!mtlsConfig) return undefined; + return { + truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key), + truststoreVersion: mtlsConfig.version, + }; + } } export interface DomainNameAttributes { @@ -162,4 +179,26 @@ export interface DomainNameAttributes { * Thje Route53 hosted zone ID to use in order to connect a record set to this domain through an alias. */ readonly domainNameAliasHostedZoneId: string; + +} + +/** + * The mTLS authentication configuration for a custom domain name. + */ +export interface MTLSConfig { + /** + * The bucket that the trust store is hosted in. + */ + readonly bucket: IBucket; + /** + * The key in S3 to look at for the trust store + */ + readonly key: string; + + /** + * The version of the S3 object that contains your truststore. + * To specify a version, you must have versioning enabled for the S3 bucket. + * @default - latest version + */ + readonly version?: string; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/index.ts b/packages/@aws-cdk/aws-apigateway/lib/index.ts index cdb63b19d2e07..4c288b27f4160 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/index.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/index.ts @@ -7,7 +7,6 @@ export * from './stage'; export * from './integrations'; export * from './lambda-api'; export * from './api-key'; -export * from './rate-limited-api-key'; export * from './usage-plan'; export * from './vpc-link'; export * from './methodresponse'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index 5b6a3040cb946..9d08a02b57bc2 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; +import { Lazy } from '@aws-cdk/core'; import { Method } from './method'; -import { IVpcLink } from './vpc-link'; +import { IVpcLink, VpcLink } from './vpc-link'; export interface IntegrationOptions { /** @@ -90,7 +91,7 @@ export interface IntegrationOptions { /** * The type of network connection to the integration endpoint. - * @default ConnectionType.Internet + * @default - ConnectionType.VPC_LINK if `vpcLink` property is configured; ConnectionType.Internet otherwise. */ readonly connectionType?: ConnectionType; @@ -199,10 +200,34 @@ export class Integration { * being integrated, access the REST API object, method ARNs, etc. */ public bind(_method: Method): IntegrationConfig { + let uri = this.props.uri; + const options = this.props.options; + + if (options?.connectionType === ConnectionType.VPC_LINK && uri === undefined) { + uri = Lazy.stringValue({ + // needs to be a lazy since the targets can be added to the VpcLink construct after initialization. + produce: () => { + const vpcLink = options.vpcLink; + if (vpcLink instanceof VpcLink) { + const targets = vpcLink._targetDnsNames; + if (targets.length > 1) { + throw new Error("'uri' is required when there are more than one NLBs in the VPC Link"); + } else { + return `http://${targets[0]}`; + } + } else { + throw new Error("'uri' is required when the 'connectionType' is VPC_LINK"); + } + }, + }); + } return { - options: this.props.options, + options: { + ...options, + connectionType: options?.vpcLink ? ConnectionType.VPC_LINK : options?.connectionType, + }, type: this.props.type, - uri: this.props.uri, + uri, integrationHttpMethod: this.props.integrationHttpMethod, }; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index c6a8ec04863c7..91c1c9da97d64 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -49,7 +49,7 @@ export class LambdaIntegration extends AwsIntegration { }); this.handler = handler; - this.enableTest = options.allowTestInvoke === undefined ? true : false; + this.enableTest = options.allowTestInvoke === undefined ? true : options.allowTestInvoke; } public bind(method: Method): IntegrationConfig { diff --git a/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts deleted file mode 100644 index a82892b112ef7..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/lib/rate-limited-api-key.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Resource } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import { ApiKey, ApiKeyProps, IApiKey } from './api-key'; -import { QuotaSettings, ThrottleSettings, UsagePlan, UsagePlanPerApiStage } from './usage-plan'; - -/** - * RateLimitedApiKey properties. - */ -export interface RateLimitedApiKeyProps extends ApiKeyProps { - /** - * API Stages to be associated with the RateLimitedApiKey. - * @default none - */ - readonly apiStages?: UsagePlanPerApiStage[]; - - /** - * Number of requests clients can make in a given time period. - * @default none - */ - readonly quota?: QuotaSettings; - - /** - * Overall throttle settings for the API. - * @default none - */ - readonly throttle?: ThrottleSettings; -} - -/** - * An API Gateway ApiKey, for which a rate limiting configuration can be specified. - * - * @resource AWS::ApiGateway::ApiKey - */ -export class RateLimitedApiKey extends Resource implements IApiKey { - public readonly keyId: string; - - constructor(scope: Construct, id: string, props: RateLimitedApiKeyProps = { }) { - super(scope, id, { - physicalName: props.apiKeyName, - }); - - const resource = new ApiKey(this, 'Resource', props); - - if (props.apiStages || props.quota || props.throttle) { - new UsagePlan(this, 'UsagePlanResource', { - apiKey: resource, - apiStages: props.apiStages, - quota: props.quota, - throttle: props.throttle, - }); - } - - this.keyId = resource.keyId; - } -} diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 0ab0b458d0b39..a4b8f6e2ee389 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; @@ -299,13 +300,17 @@ export abstract class RestApiBase extends Resource implements IRestApi { */ public deploymentStage!: Stage; + /** + * A human friendly name for this Rest API. Note that this is different from `restApiId`. + */ + public readonly restApiName: string; + private _latestDeployment?: Deployment; private _domainName?: DomainName; constructor(scope: Construct, id: string, props: RestApiBaseProps = { }) { - super(scope, id, { - physicalName: props.restApiName || id, - }); + super(scope, id); + this.restApiName = props.restApiName ?? id; Object.defineProperty(this, RESTAPI_SYMBOL, { value: true }); } @@ -373,6 +378,85 @@ export abstract class RestApiBase extends Resource implements IRestApi { }); } + /** + * Returns the given named metric for this API + */ + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/ApiGateway', + metricName, + dimensions: { ApiName: this.restApiName }, + ...props, + }); + } + + /** + * Metric for the number of client-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + public metricClientError(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('4XXError', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the number of server-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + public metricServerError(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('5XXError', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the number of requests served from the API cache in a given period. + * + * @default - sum over 5 minutes + */ + public metricCacheHitCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('CacheHitCount', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the number of requests served from the backend in a given period, + * when API caching is enabled. + * + * @default - sum over 5 minutes + */ + public metricCacheMissCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('CacheMissCount', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the total number API requests in a given period. + * + * @default - SampleCount over 5 minutes + */ + public metricCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('Count', { statistic: 'SampleCount', ...props }); + } + + /** + * Metric for the time between when API Gateway relays a request to the backend + * and when it receives a response from the backend. + * + * @default - no statistic + */ + public metricIntegrationLatency(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('IntegrationLatency', props); + } + + /** + * The time between when API Gateway receives a request from a client + * and when it returns a response to the client. + * The latency includes the integration latency and other API Gateway overhead. + * + * @default - no statistic + */ + public metricLatency(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metric('Latency', props); + } + /** * Internal API used by `Method` to keep an inventory of methods at the API * level for validation purposes. @@ -485,7 +569,7 @@ export class SpecRestApi extends RestApiBase { super(scope, id, props); const apiDefConfig = props.apiDefinition.bind(this); const resource = new CfnRestApi(this, 'Resource', { - name: this.physicalName, + name: this.restApiName, policy: props.policy, failOnWarnings: props.failOnWarnings, body: apiDefConfig.inlineDefinition ? apiDefConfig.inlineDefinition : undefined, @@ -587,7 +671,7 @@ export class RestApi extends RestApiBase { super(scope, id, props); const resource = new CfnRestApi(this, 'Resource', { - name: this.physicalName, + name: this.restApiName, description: props.description, policy: props.policy, failOnWarnings: props.failOnWarnings, diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 85bdf63d0022c..700d22c137cc1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -61,7 +61,7 @@ export class VpcLink extends Resource implements IVpcLink { */ public readonly vpcLinkId: string; - private readonly targets = new Array(); + private readonly _targets = new Array(); constructor(scope: Construct, id: string, props: VpcLinkProps = {}) { super(scope, id, { @@ -83,17 +83,25 @@ export class VpcLink extends Resource implements IVpcLink { } public addTargets(...targets: elbv2.INetworkLoadBalancer[]) { - this.targets.push(...targets); + this._targets.push(...targets); + } + + /** + * Return the list of DNS names from the target NLBs. + * @internal + * */ + public get _targetDnsNames(): string[] { + return this._targets.map(t => t.loadBalancerDnsName); } protected validate(): string[] { - if (this.targets.length === 0) { + if (this._targets.length === 0) { return ['No targets added to vpc link']; } return []; } private renderTargets() { - return this.targets.map(nlb => nlb.loadBalancerArn); + return this._targets.map(nlb => nlb.loadBalancerArn); } } diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index ccd888c50c0c9..5125418beaf8d 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApiGateway", @@ -81,6 +82,7 @@ "dependencies": { "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -96,6 +98,7 @@ "peerDependencies": { "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -314,6 +317,9 @@ "attribute-tag:@aws-cdk/aws-apigateway.LambdaAuthorizer.authorizerArn", "attribute-tag:@aws-cdk/aws-apigateway.RequestAuthorizer.authorizerArn", "attribute-tag:@aws-cdk/aws-apigateway.TokenAuthorizer.authorizerArn", + "attribute-tag:@aws-cdk/aws-apigateway.RestApi.restApiName", + "attribute-tag:@aws-cdk/aws-apigateway.SpecRestApi.restApiName", + "attribute-tag:@aws-cdk/aws-apigateway.LambdaRestApi.restApiName", "from-method:@aws-cdk/aws-apigateway.Stage" ] }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts index e7db715036bc6..f33751ee9cbc7 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigateway from '../lib'; @@ -66,4 +67,262 @@ export = { })); test.done(); }, + + 'grantRead'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + const apiKey = new apigateway.ApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api], + }); + apiKey.grantRead(user); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'apigateway:GET', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + '::/apikeys/', + { + Ref: 'testapikeyE093E501', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + + 'grantWrite'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + const apiKey = new apigateway.ApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api], + }); + apiKey.grantWrite(user); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'apigateway:POST', + 'apigateway:PUT', + 'apigateway:PATCH', + 'apigateway:DELETE', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + '::/apikeys/', + { + Ref: 'testapikeyE093E501', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + + 'grantReadWrite'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'User'); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + const apiKey = new apigateway.ApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api], + }); + apiKey.grantReadWrite(user); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'apigateway:GET', + 'apigateway:POST', + 'apigateway:PUT', + 'apigateway:PATCH', + 'apigateway:DELETE', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + '::/apikeys/', + { + Ref: 'testapikeyE093E501', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + + 'rate limited': { + 'default setup'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: false }); + api.root.addMethod('GET'); // Need at least one method on the api + + // WHEN + new apigateway.RateLimitedApiKey(stack, 'my-api-key'); + + // THEN + // should have an api key with no props defined. + expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', undefined, ResourcePart.CompleteDefinition)); + // should not have a usage plan. + expect(stack).notTo(haveResource('AWS::ApiGateway::UsagePlan')); + // should not have a usage plan key. + expect(stack).notTo(haveResource('AWS::ApiGateway::UsagePlanKey')); + + test.done(); + }, + + 'only api key is created when rate limiting properties are not provided'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + new apigateway.RateLimitedApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api], + }); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', { + CustomerId: 'test-customer', + StageKeys: [ + { + RestApiId: { Ref: 'testapiD6451F70' }, + StageName: { Ref: 'testapiDeploymentStagetest5869DF71' }, + }, + ], + })); + // should not have a usage plan. + expect(stack).notTo(haveResource('AWS::ApiGateway::UsagePlan')); + // should not have a usage plan key. + expect(stack).notTo(haveResource('AWS::ApiGateway::UsagePlanKey')); + + test.done(); + }, + + 'api key and usage plan are created and linked when rate limiting properties are provided'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + new apigateway.RateLimitedApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api], + quota: { + limit: 10000, + period: apigateway.Period.MONTH, + }, + }); + + // THEN + // should have an api key + expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', { + CustomerId: 'test-customer', + StageKeys: [ + { + RestApiId: { Ref: 'testapiD6451F70' }, + StageName: { Ref: 'testapiDeploymentStagetest5869DF71' }, + }, + ], + })); + // should have a usage plan with specified quota. + expect(stack).to(haveResource('AWS::ApiGateway::UsagePlan', { + Quota: { + Limit: 10000, + Period: 'MONTH', + }, + }, ResourcePart.Properties)); + // should have a usage plan key linking the api key and usage plan + expect(stack).to(haveResource('AWS::ApiGateway::UsagePlanKey', { + KeyId: { + Ref: 'testapikey998028B6', + }, + KeyType: 'API_KEY', + UsagePlanId: { + Ref: 'testapikeyUsagePlanResource66DB63D6', + }, + }, ResourcePart.Properties)); + + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts index b4e9b66f15310..1e611c4601cd4 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.domains.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.domains.ts @@ -1,5 +1,6 @@ import { ABSENT, expect, haveResource } from '@aws-cdk/assert'; import * as acm from '@aws-cdk/aws-certificatemanager'; +import { Bucket } from '@aws-cdk/aws-s3'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; @@ -399,4 +400,106 @@ export = { })); test.done(); }, + + 'accepts a mutual TLS configuration'(test: Test) { + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + new apigw.DomainName(stack, 'another-domain', { + domainName: 'example.com', + mtls: { + bucket, + key: 'someca.pem', + }, + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + }); + + expect(stack).to(haveResource('AWS::ApiGateway::DomainName', { + 'DomainName': 'example.com', + 'EndpointConfiguration': { 'Types': ['REGIONAL'] }, + 'RegionalCertificateArn': 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d', + 'MutualTlsAuthentication': { 'TruststoreUri': 's3://exampleBucket/someca.pem' }, + })); + test.done(); + }, + + 'mTLS should allow versions to be set on the s3 bucket'(test: Test) { + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + new apigw.DomainName(stack, 'another-domain', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert2', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + mtls: { + bucket, + key: 'someca.pem', + version: 'version', + }, + }); + expect(stack).to(haveResource('AWS::ApiGateway::DomainName', { + 'DomainName': 'example.com', + 'EndpointConfiguration': { 'Types': ['REGIONAL'] }, + 'RegionalCertificateArn': 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d', + 'MutualTlsAuthentication': { 'TruststoreUri': 's3://exampleBucket/someca.pem', 'TruststoreVersion': 'version' }, + })); + test.done(); + }, + + 'base path mapping configures stage for RestApi creation'(test: Test) { + // GIVEN + const stack = new Stack(); + new apigw.RestApi(stack, 'restApiWithStage', { + domainName: { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }, + }).root.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::BasePathMapping', { + 'DomainName': { + 'Ref': 'restApiWithStageCustomDomainC4749625', + }, + 'RestApiId': { + 'Ref': 'restApiWithStageD4F931D0', + }, + 'Stage': { + 'Ref': 'restApiWithStageDeploymentStageprodC82A6648', + }, + })); + + test.done(); + }, + + 'base path mapping configures stage for SpecRestApi creation'(test: Test) { + // GIVEN + const stack = new Stack(); + + const definition = { + key1: 'val1', + }; + + new apigw.SpecRestApi(stack, 'specRestApiWithStage', { + apiDefinition: apigw.ApiDefinition.fromInline(definition), + domainName: { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }, + }).root.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::BasePathMapping', { + 'DomainName': { + 'Ref': 'specRestApiWithStageCustomDomain8A36A5C9', + }, + 'RestApiId': { + 'Ref': 'specRestApiWithStageC1492575', + }, + 'Stage': { + 'Ref': 'specRestApiWithStageDeploymentStageprod2D3037ED', + }, + })); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.integration.ts b/packages/@aws-cdk/aws-apigateway/test/test.integration.ts index 40047b53df745..e946ded961b29 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.integration.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.integration.ts @@ -1,3 +1,4 @@ +import { ABSENT, expect, haveResourceLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as iam from '@aws-cdk/aws-iam'; @@ -33,6 +34,89 @@ export = { test.done(); }, + 'uri is self determined from the NLB'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc }); + const link = new apigw.VpcLink(stack, 'link', { + targets: [nlb], + }); + const api = new apigw.RestApi(stack, 'restapi'); + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + connectionType: apigw.ConnectionType.VPC_LINK, + vpcLink: link, + }, + }); + api.root.addMethod('GET', integration); + + expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', { + Integration: { + Uri: { + 'Fn::Join': [ + '', + [ + 'http://', + { + 'Fn::GetAtt': [ + 'NLB55158F82', + 'DNSName', + ], + }, + ], + ], + }, + }, + })); + + test.done(); + }, + + 'uri must be set for VpcLink with multiple NLBs'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const vpc = new ec2.Vpc(stack, 'VPC'); + const nlb1 = new elbv2.NetworkLoadBalancer(stack, 'NLB1', { vpc }); + const nlb2 = new elbv2.NetworkLoadBalancer(stack, 'NLB2', { vpc }); + const link = new apigw.VpcLink(stack, 'link', { + targets: [nlb1, nlb2], + }); + const api = new apigw.RestApi(stack, 'restapi'); + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + connectionType: apigw.ConnectionType.VPC_LINK, + vpcLink: link, + }, + }); + api.root.addMethod('GET', integration); + test.throws(() => app.synth(), /'uri' is required when there are more than one NLBs in the VPC Link/); + + test.done(); + }, + + 'uri must be set when using an imported VpcLink'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app); + const link = apigw.VpcLink.fromVpcLinkId(stack, 'link', 'vpclinkid'); + const api = new apigw.RestApi(stack, 'restapi'); + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + connectionType: apigw.ConnectionType.VPC_LINK, + vpcLink: link, + }, + }); + api.root.addMethod('GET', integration); + test.throws(() => app.synth(), /'uri' is required when the 'connectionType' is VPC_LINK/); + + test.done(); + }, + 'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -55,4 +139,60 @@ export = { }), /cannot set 'vpcLink' where 'connectionType' is INTERNET/); test.done(); }, + + 'connectionType is ABSENT when vpcLink is not specified'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'restapi'); + + // WHEN + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + }); + api.root.addMethod('ANY', integration); + + // THEN + expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', { + HttpMethod: 'ANY', + Integration: { + ConnectionType: ABSENT, + }, + })); + + test.done(); + }, + + 'connectionType defaults to VPC_LINK if vpcLink is configured'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { + vpc, + }); + const link = new apigw.VpcLink(stack, 'link', { + targets: [nlb], + }); + const api = new apigw.RestApi(stack, 'restapi'); + + // WHEN + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + vpcLink: link, + }, + }); + api.root.addMethod('ANY', integration); + + // THEN + expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', { + HttpMethod: 'ANY', + Integration: { + ConnectionType: 'VPC_LINK', + }, + })); + + test.done(); + }, }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts index abfe3e44b66f4..0519f62ee5a36 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts @@ -102,6 +102,44 @@ export = { test.done(); }, + '"allowTestInvoke" set to true allows calling the API from the test UI'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Handler', { + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + }); + + const api = new apigateway.RestApi(stack, 'api'); + + // WHEN + const integ = new apigateway.LambdaIntegration(fn, { allowTestInvoke: true }); + api.root.addMethod('GET', integ); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Permission', { + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':execute-api:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'apiC8550315' }, + '/test-invoke-stage/GET/', + ], + ], + }, + })); + + test.done(); + }, + '"proxy" can be used to disable proxy mode'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.rate-limited-api-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.rate-limited-api-key.ts deleted file mode 100644 index ce69374064da1..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/test/test.rate-limited-api-key.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as apigateway from '../lib'; - -const API_KEY_RESOURCE_TYPE = 'AWS::ApiGateway::ApiKey'; -const USAGE_PLAN_RESOURCE_TYPE = 'AWS::ApiGateway::UsagePlan'; -const USAGE_PLAN_KEY_RESOURCE_TYPE = 'AWS::ApiGateway::UsagePlanKey'; - -export = { - 'default setup'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: false }); - api.root.addMethod('GET'); // Need at least one method on the api - - // WHEN - new apigateway.RateLimitedApiKey(stack, 'my-api-key'); - - // THEN - // should have an api key with no props defined. - expect(stack).to(haveResource(API_KEY_RESOURCE_TYPE, undefined, ResourcePart.CompleteDefinition)); - // should not have a usage plan. - expect(stack).notTo(haveResource(USAGE_PLAN_RESOURCE_TYPE)); - // should not have a usage plan key. - expect(stack).notTo(haveResource(USAGE_PLAN_KEY_RESOURCE_TYPE)); - - test.done(); - }, - - 'only api key is created when rate limiting properties are not provided'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); - api.root.addMethod('GET'); // api must have atleast one method. - - // WHEN - new apigateway.RateLimitedApiKey(stack, 'test-api-key', { - customerId: 'test-customer', - resources: [api], - }); - - // THEN - expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', { - CustomerId: 'test-customer', - StageKeys: [ - { - RestApiId: { Ref: 'testapiD6451F70' }, - StageName: { Ref: 'testapiDeploymentStagetest5869DF71' }, - }, - ], - })); - // should not have a usage plan. - expect(stack).notTo(haveResource(USAGE_PLAN_RESOURCE_TYPE)); - // should not have a usage plan key. - expect(stack).notTo(haveResource(USAGE_PLAN_KEY_RESOURCE_TYPE)); - - test.done(); - }, - - 'api key and usage plan are created and linked when rate limiting properties are provided'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); - api.root.addMethod('GET'); // api must have atleast one method. - - // WHEN - new apigateway.RateLimitedApiKey(stack, 'test-api-key', { - customerId: 'test-customer', - resources: [api], - quota: { - limit: 10000, - period: apigateway.Period.MONTH, - }, - }); - - // THEN - // should have an api key - expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', { - CustomerId: 'test-customer', - StageKeys: [ - { - RestApiId: { Ref: 'testapiD6451F70' }, - StageName: { Ref: 'testapiDeploymentStagetest5869DF71' }, - }, - ], - })); - // should have a usage plan with specified quota. - expect(stack).to(haveResource(USAGE_PLAN_RESOURCE_TYPE, { - Quota: { - Limit: 10000, - Period: 'MONTH', - }, - }, ResourcePart.Properties)); - // should have a usage plan key linking the api key and usage plan - expect(stack).to(haveResource(USAGE_PLAN_KEY_RESOURCE_TYPE, { - KeyId: { - Ref: 'testapikey998028B6', - }, - KeyType: 'API_KEY', - UsagePlanId: { - Ref: 'testapikeyUsagePlanResource66DB63D6', - }, - }, ResourcePart.Properties)); - - test.done(); - }, -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 9196f505bf7f3..dfbe61d180e28 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -100,6 +100,22 @@ export = { test.done(); }, + 'restApiName is set correctly'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const myapi = new apigw.RestApi(stack, 'myapi'); + const yourapi = new apigw.RestApi(stack, 'yourapi', { + restApiName: 'namedapi', + }); + + // THEN + test.deepEqual(myapi.restApiName, 'myapi'); + test.deepEqual(yourapi.restApiName, 'namedapi'); + test.done(); + }, + 'defaultChild is set correctly'(test: Test) { const stack = new Stack(); const api = new apigw.RestApi(stack, 'my-api'); @@ -1051,4 +1067,142 @@ export = { test.done(); }, }, + + Metrics: { + 'metric'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const metricName = '4XXError'; + const statistic = 'Sum'; + + // WHEN + const countMetric = api.metric(metricName, { statistic }); + + // THEN + test.equal(countMetric.namespace, 'AWS/ApiGateway'); + test.equal(countMetric.metricName, metricName); + test.deepEqual(countMetric.dimensions, { ApiName: 'my-api' }); + test.equal(countMetric.statistic, statistic); + + test.done(); + }, + + 'metricClientError'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricClientError({ color }); + + // THEN + test.equal(countMetric.metricName, '4XXError'); + test.equal(countMetric.statistic, 'Sum'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricServerError'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricServerError({ color }); + + // THEN + test.equal(countMetric.metricName, '5XXError'); + test.equal(countMetric.statistic, 'Sum'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricCacheHitCount'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricCacheHitCount({ color }); + + // THEN + test.equal(countMetric.metricName, 'CacheHitCount'); + test.equal(countMetric.statistic, 'Sum'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricCacheMissCount'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricCacheMissCount({ color }); + + // THEN + test.equal(countMetric.metricName, 'CacheMissCount'); + test.equal(countMetric.statistic, 'Sum'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricCount'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricCount({ color }); + + // THEN + test.equal(countMetric.metricName, 'Count'); + test.equal(countMetric.statistic, 'SampleCount'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricIntegrationLatency'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricIntegrationLatency({ color }); + + // THEN + test.equal(countMetric.metricName, 'IntegrationLatency'); + test.equal(countMetric.color, color); + + test.done(); + }, + + 'metricLatency'(test: Test) { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'my-api'); + const color = '#00ff00'; + + // WHEN + const countMetric = api.metricLatency({ color }); + + // THEN + test.equal(countMetric.metricName, 'Latency'); + test.equal(countMetric.color, color); + + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-apigatewayv2/.npmignore b/packages/@aws-cdk/aws-apigatewayv2/.npmignore index e9418d013ad1f..093c734b1bd2f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/.npmignore +++ b/packages/@aws-cdk/aws-apigatewayv2/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 14ce142487587..e5cf86886aa3d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -23,6 +23,7 @@ - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) + - [Metrics](#metrics) ## Introduction @@ -198,3 +199,27 @@ with 3 API mapping resources across different APIs and Stages. | api | $default | `https://${domainName}/foo` | | api | beta | `https://${domainName}/bar` | | apiDemo | $default | `https://${domainName}/demo` | + +## Metrics + +The API Gateway v2 service sends metrics around the performance of HTTP APIs to Amazon CloudWatch. +These metrics can be referred to using the metric APIs available on the `HttpApi` construct. +The APIs with the `metric` prefix can be used to get reference to specific metrics for this API. For example, +the method below refers to the client side errors metric for this API. + +``` +const api = new apigw.HttpApi(stack, 'my-api'); +const clientErrorMetric = api.metricClientError(); + +``` + +Please note that this will return a metric for all the stages defined in the api. It is also possible to refer to metrics for a specific Stage using +the `metric` methods from the `Stage` construct. + +``` +const api = new apigw.HttpApi(stack, 'my-api'); +const stage = new HttpStage(stack, 'Stage', { + httpApi: api, +}); +const clientErrorMetric = stage.metricClientError(); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 7678a7bbe99e4..ff75808b5a8d6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,3 +1,4 @@ +import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; import { Duration, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; @@ -20,6 +21,58 @@ export interface IHttpApi extends IResource { * The default stage */ readonly defaultStage?: HttpStage; + + /** + * Return the given named metric for this HTTP Api Gateway + * + * @default - average over 5 minutes + */ + metric(metricName: string, props?: MetricOptions): Metric; + + /** + * Metric for the number of client-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + metricClientError(props?: MetricOptions): Metric; + + /** + * Metric for the number of server-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + metricServerError(props?: MetricOptions): Metric; + + /** + * Metric for the amount of data processed in bytes. + * + * @default - sum over 5 minutes + */ + metricDataProcessed(props?: MetricOptions): Metric; + + /** + * Metric for the total number API requests in a given period. + * + * @default - SampleCount over 5 minutes + */ + metricCount(props?: MetricOptions): Metric; + + /** + * Metric for the time between when API Gateway relays a request to the backend + * and when it receives a response from the backend. + * + * @default - no statistic + */ + metricIntegrationLatency(props?: MetricOptions): Metric; + + /** + * The time between when API Gateway receives a request from a client + * and when it returns a response to the client. + * The latency includes the integration latency and other API Gateway overhead. + * + * @default - no statistic + */ + metricLatency(props?: MetricOptions): Metric; } /** @@ -32,6 +85,12 @@ export interface HttpApiProps { */ readonly apiName?: string; + /** + * The description of the API. + * @default - none + */ + readonly description?: string; + /** * An integration that will be configured on the catch-all route ($default). * @default - none @@ -116,22 +175,66 @@ export interface AddRoutesOptions extends BatchHttpRouteOptions { readonly methods?: HttpMethod[]; } +abstract class HttpApiBase extends Resource implements IHttpApi { // note that this is not exported + + public abstract readonly httpApiId: string; + + public metric(metricName: string, props?: MetricOptions): Metric { + return new Metric({ + namespace: 'AWS/ApiGateway', + metricName, + dimensions: { ApiId: this.httpApiId }, + ...props, + }).attachTo(this); + } + + public metricClientError(props?: MetricOptions): Metric { + return this.metric('4XXError', { statistic: 'Sum', ...props }); + } + + public metricServerError(props?: MetricOptions): Metric { + return this.metric('5XXError', { statistic: 'Sum', ...props }); + } + + public metricDataProcessed(props?: MetricOptions): Metric { + return this.metric('DataProcessed', { statistic: 'Sum', ...props }); + } + + public metricCount(props?: MetricOptions): Metric { + return this.metric('Count', { statistic: 'SampleCount', ...props }); + } + + public metricIntegrationLatency(props?: MetricOptions): Metric { + return this.metric('IntegrationLatency', props); + } + + public metricLatency(props?: MetricOptions): Metric { + return this.metric('Latency', props); + } +} + /** * Create a new API Gateway HTTP API endpoint. * @resource AWS::ApiGatewayV2::Api */ -export class HttpApi extends Resource implements IHttpApi { +export class HttpApi extends HttpApiBase { /** * Import an existing HTTP API into this CDK app. */ public static fromApiId(scope: Construct, id: string, httpApiId: string): IHttpApi { - class Import extends Resource implements IHttpApi { + class Import extends HttpApiBase { public readonly httpApiId = httpApiId; } return new Import(scope, id); } + /** + * A human friendly name for this HTTP API. Note that this is different from `httpApiId`. + */ + public readonly httpApiName?: string; + public readonly httpApiId: string; + /** * default stage of the api resource */ @@ -140,7 +243,7 @@ export class HttpApi extends Resource implements IHttpApi { constructor(scope: Construct, id: string, props?: HttpApiProps) { super(scope, id); - const apiName = props?.apiName ?? id; + this.httpApiName = props?.apiName ?? id; let corsConfiguration: CfnApi.CorsProperty | undefined; if (props?.corsPreflight) { @@ -167,9 +270,10 @@ export class HttpApi extends Resource implements IHttpApi { } const apiProps: CfnApiProps = { - name: apiName, + name: this.httpApiName, protocolType: 'HTTP', corsConfiguration, + description: props?.description, }; const resource = new CfnApi(this, 'Resource', apiProps); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index 986649f5301e5..fbe54345e25e3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -1,3 +1,4 @@ +import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; import { Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnStage } from '../apigatewayv2.generated'; @@ -5,6 +6,7 @@ import { CommonStageOptions, IDomainName, IStage } from '../common'; import { IHttpApi } from './api'; import { HttpApiMapping } from './api-mapping'; + const DEFAULT_STAGE_NAME = '$default'; /** @@ -117,4 +119,73 @@ export class HttpStage extends Resource implements IStage { const urlPath = this.stageName === DEFAULT_STAGE_NAME ? '' : this.stageName; return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } + + /** + * Return the given named metric for this HTTP Api Gateway Stage + * + * @default - average over 5 minutes + */ + public metric(metricName: string, props?: MetricOptions): Metric { + var api = this.httpApi; + return api.metric(metricName, props).with({ + dimensions: { ApiId: this.httpApi.httpApiId, Stage: this.stageName }, + }).attachTo(this); + } + + /** + * Metric for the number of client-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + public metricClientError(props?: MetricOptions): Metric { + return this.metric('4XXError', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the number of server-side errors captured in a given period. + * + * @default - sum over 5 minutes + */ + public metricServerError(props?: MetricOptions): Metric { + return this.metric('5XXError', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the amount of data processed in bytes. + * + * @default - sum over 5 minutes + */ + public metricDataProcessed(props?: MetricOptions): Metric { + return this.metric('DataProcessed', { statistic: 'Sum', ...props }); + } + + /** + * Metric for the total number API requests in a given period. + * + * @default - SampleCount over 5 minutes + */ + public metricCount(props?: MetricOptions): Metric { + return this.metric('Count', { statistic: 'SampleCount', ...props }); + } + + /** + * Metric for the time between when API Gateway relays a request to the backend + * and when it receives a response from the backend. + * + * @default - no statistic + */ + public metricIntegrationLatency(props?: MetricOptions): Metric { + return this.metric('IntegrationLatency', props); + } + + /** + * The time between when API Gateway receives a request from a client + * and when it returns a response to the client. + * The latency includes the integration latency and other API Gateway overhead. + * + * @default - no statistic + */ + public metricLatency(props?: MetricOptions): Metric { + return this.metric('Latency', props); + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 9d1edcb5efbba..7c079b71baab1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApiGatewayV2", @@ -82,6 +83,7 @@ "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.4" }, @@ -89,6 +91,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.4" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index d01b137796306..13ee4e120945d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert/jest'; import { ABSENT } from '@aws-cdk/assert'; +import { Metric } from '@aws-cdk/aws-cloudwatch'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { Duration, Stack } from '@aws-cdk/core'; import { HttpApi, HttpMethod, LambdaProxyIntegration } from '../../lib'; @@ -155,5 +156,81 @@ describe('HttpApi', () => { }, })).toThrowError(/allowCredentials is not supported/); }); + + test('get metric', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'test-api', { + createDefaultStage: false, + }); + const metricName = '4xxError'; + const statistic = 'Sum'; + const apiId = api.httpApiId; + + // WHEN + const countMetric = api.metric(metricName, { statistic }); + + // THEN + expect(countMetric.namespace).toEqual('AWS/ApiGateway'); + expect(countMetric.metricName).toEqual(metricName); + expect(countMetric.dimensions).toEqual({ ApiId: apiId }); + expect(countMetric.statistic).toEqual(statistic); + }); + + test('Exercise metrics', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'test-api', { + createDefaultStage: false, + }); + const color = '#00ff00'; + const apiId = api.httpApiId; + + // WHEN + const metrics = new Array(); + metrics.push(api.metricClientError({ color })); + metrics.push(api.metricServerError({ color })); + metrics.push(api.metricDataProcessed({ color })); + metrics.push(api.metricLatency({ color })); + metrics.push(api.metricIntegrationLatency({ color })); + metrics.push(api.metricCount({ color })); + // THEN + for (const metric of metrics) { + expect(metric.namespace).toEqual('AWS/ApiGateway'); + expect(metric.dimensions).toEqual({ ApiId: apiId }); + expect(metric.color).toEqual(color); + } + }); + + test('Metrics from imported resource', () => { + // GIVEN + const stack = new Stack(); + const apiId = 'importedId'; + const api = HttpApi.fromApiId(stack, 'test-api', apiId); + const metricName = '4xxError'; + const statistic = 'Sum'; + + // WHEN + const countMetric = api.metric(metricName, { statistic }); + + // THEN + expect(countMetric.namespace).toEqual('AWS/ApiGateway'); + expect(countMetric.metricName).toEqual(metricName); + expect(countMetric.dimensions).toEqual({ ApiId: apiId }); + expect(countMetric.statistic).toEqual(statistic); + }); + }); + + test('description is set', () => { + const stack = new Stack(); + new HttpApi(stack, 'api', { + description: 'My Api', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Api', { + Name: 'api', + ProtocolType: 'HTTP', + Description: 'My Api', + }); }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts index 06e7a9efbc4ce..6c4359b5439c9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts @@ -1,7 +1,9 @@ import '@aws-cdk/assert/jest'; +import { Metric } from '@aws-cdk/aws-cloudwatch'; import { Stack } from '@aws-cdk/core'; import { HttpApi, HttpStage } from '../../lib'; + describe('HttpStage', () => { test('default', () => { const stack = new Stack(); @@ -52,4 +54,61 @@ describe('HttpStage', () => { expect(defaultStage.url.endsWith('/')).toBe(true); expect(betaStage.url.endsWith('/')).toBe(false); }); + + test('get metric', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'test-api', { + createDefaultStage: false, + }); + const stage = new HttpStage(stack, 'Stage', { + httpApi: api, + }); + const metricName = '4xxError'; + const statistic = 'Sum'; + const apiId = api.httpApiId; + + // WHEN + const countMetric = stage.metric(metricName, { statistic }); + + // THEN + expect(countMetric.namespace).toEqual('AWS/ApiGateway'); + expect(countMetric.metricName).toEqual(metricName); + expect(countMetric.dimensions).toEqual({ + ApiId: apiId, + Stage: '$default', + }); + expect(countMetric.statistic).toEqual(statistic); + }); + + test('Exercise metrics', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'test-api', { + createDefaultStage: false, + }); + const stage = new HttpStage(stack, 'Stage', { + httpApi: api, + }); + const color = '#00ff00'; + const apiId = api.httpApiId; + + // WHEN + const metrics = new Array(); + metrics.push(stage.metricClientError({ color })); + metrics.push(stage.metricServerError({ color })); + metrics.push(stage.metricDataProcessed({ color })); + metrics.push(stage.metricLatency({ color })); + metrics.push(stage.metricIntegrationLatency({ color })); + metrics.push(stage.metricCount({ color })); + // THEN + for (const metric of metrics) { + expect(metric.namespace).toEqual('AWS/ApiGateway'); + expect(metric.dimensions).toEqual({ + ApiId: apiId, + Stage: '$default', + }); + expect(metric.color).toEqual(color); + } + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appconfig/.npmignore b/packages/@aws-cdk/aws-appconfig/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-appconfig/.npmignore +++ b/packages/@aws-cdk/aws-appconfig/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appconfig/package.json b/packages/@aws-cdk/aws-appconfig/package.json index 138ac518ef269..d88d38fe78249 100644 --- a/packages/@aws-cdk/aws-appconfig/package.json +++ b/packages/@aws-cdk/aws-appconfig/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppConfig", diff --git a/packages/@aws-cdk/aws-appflow/.npmignore b/packages/@aws-cdk/aws-appflow/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-appflow/.npmignore +++ b/packages/@aws-cdk/aws-appflow/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appflow/package.json b/packages/@aws-cdk/aws-appflow/package.json index d589158afe5c4..446d4c0787e03 100644 --- a/packages/@aws-cdk/aws-appflow/package.json +++ b/packages/@aws-cdk/aws-appflow/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppFlow", diff --git a/packages/@aws-cdk/aws-applicationautoscaling/.npmignore b/packages/@aws-cdk/aws-applicationautoscaling/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/.npmignore +++ b/packages/@aws-cdk/aws-applicationautoscaling/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index d0cfa2e654773..19c9121c033f0 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApplicationAutoScaling", diff --git a/packages/@aws-cdk/aws-applicationinsights/.npmignore b/packages/@aws-cdk/aws-applicationinsights/.npmignore index 7633957caec65..9c086d15db4aa 100644 --- a/packages/@aws-cdk/aws-applicationinsights/.npmignore +++ b/packages/@aws-cdk/aws-applicationinsights/.npmignore @@ -25,3 +25,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationinsights/package.json b/packages/@aws-cdk/aws-applicationinsights/package.json index 0d9cb18068cee..8b59f2cd18ea9 100644 --- a/packages/@aws-cdk/aws-applicationinsights/package.json +++ b/packages/@aws-cdk/aws-applicationinsights/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ApplicationInsights", diff --git a/packages/@aws-cdk/aws-appmesh/.npmignore b/packages/@aws-cdk/aws-appmesh/.npmignore index f937500da09a6..305e5b0db6ec1 100644 --- a/packages/@aws-cdk/aws-appmesh/.npmignore +++ b/packages/@aws-cdk/aws-appmesh/.npmignore @@ -25,4 +25,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 94a4d5d7fe0b5..89ba1dd23a157 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppMesh", diff --git a/packages/@aws-cdk/aws-appstream/.npmignore b/packages/@aws-cdk/aws-appstream/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-appstream/.npmignore +++ b/packages/@aws-cdk/aws-appstream/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index d26317e17b8ad..da76fb0128a25 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppStream", diff --git a/packages/@aws-cdk/aws-appsync/.npmignore b/packages/@aws-cdk/aws-appsync/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-appsync/.npmignore +++ b/packages/@aws-cdk/aws-appsync/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 0c2896a6cd2e1..6095e3657ef9d 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -760,7 +760,7 @@ To learn more about top level operations, check out the docs [here](https://docs #### Mutation Every schema **can** have a top level Mutation type. By default, the schema will look -for the `Object Type` named `Mutation`. The top level `Mutation` Type is the only exposed +for the `ObjectType` named `Mutation`. The top level `Mutation` Type is the only exposed type that users can access to perform `mutable` operations on your Api. To add fields for these mutations, we can simply run the `addMutation` function to add diff --git a/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts b/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts index 22a7c1cf65ba3..91e9162e7900c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts +++ b/packages/@aws-cdk/aws-appsync/lib/mapping-template.ts @@ -45,8 +45,8 @@ export abstract class MappingTemplate { * * @param cond the key condition for the query */ - public static dynamoDbQuery(cond: KeyCondition): MappingTemplate { - return this.fromString(`{"version" : "2017-02-28", "operation" : "Query", ${cond.renderTemplate()}}`); + public static dynamoDbQuery(cond: KeyCondition, indexName?: string): MappingTemplate { + return this.fromString(`{"version" : "2017-02-28", "operation" : "Query", ${indexName ? `"index" : "${indexName}", ` : ''}${cond.renderTemplate()}}`); } /** diff --git a/packages/@aws-cdk/aws-appsync/lib/resolver.ts b/packages/@aws-cdk/aws-appsync/lib/resolver.ts index ccb3bb0e600d3..91abfea5f3cb4 100644 --- a/packages/@aws-cdk/aws-appsync/lib/resolver.ts +++ b/packages/@aws-cdk/aws-appsync/lib/resolver.ts @@ -17,7 +17,7 @@ export interface BaseResolverProps { */ readonly typeName: string; /** - * name of the GraphQL fiel din the given type this resolver is attached to + * name of the GraphQL field in the given type this resolver is attached to */ readonly fieldName: string; /** @@ -89,4 +89,4 @@ export class Resolver extends CoreConstruct { } this.arn = this.resolver.attrResolverArn; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 617831552c6ce..1bbdcb00298aa 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AppSync", @@ -75,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { 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 e5eed82bd5992..503471f25c9a6 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -60,7 +60,7 @@ "ApiId" ] }, - "Definition": "type ServiceVersion @aws_api_key {\n version: String!\n}\n\ntype Customer @aws_api_key {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order @aws_api_key {\n customer: String!\n order: String!\n}\n\ntype Payment @aws_api_key {\n id: String!\n amount: String!\n}\n\ninput PaymentInput {\n amount: String!\n}\n\ntype Query @aws_api_key {\n getServiceVersion: ServiceVersion\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 getPayment(id: String): Payment\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation @aws_api_key {\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 savePayment(payment: PaymentInput!): Payment\n doPostOnAws: String!\n}\n" + "Definition": "type ServiceVersion @aws_api_key {\n version: String!\n}\n\ntype Customer @aws_api_key {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order @aws_api_key {\n customer: String!\n order: String!\n}\n\ntype Payment @aws_api_key {\n id: String!\n amount: String!\n}\n\ninput PaymentInput {\n amount: String!\n}\n\ntype Query @aws_api_key {\n getServiceVersion: ServiceVersion\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 getOrderCustomersEq(order: String): [Customer]\n getOrderCustomersLt(order: String): [Customer]\n getOrderCustomersLe(order: String): [Customer]\n getOrderCustomersGt(order: String): [Customer]\n getOrderCustomersGe(order: String): [Customer]\n getOrderCustomersFilter(order: String, customer: String): [Customer]\n getOrderCustomersBetween(order: String, customer1: String, customer2: String): [Customer]\n getPayment(id: String): Payment\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation @aws_api_key {\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 savePayment(payment: PaymentInput!): Payment\n doPostOnAws: String!\n}\n" } }, "ApiDefaultApiKeyF991C37B": { @@ -367,7 +367,18 @@ ] }, { - "Ref": "AWS::NoValue" + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "OrderTable416EB896", + "Arn" + ] + }, + "/index/*" + ] + ] } ] } @@ -430,6 +441,27 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersEqResolverE58570FF": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersEq", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApiorderDsQuerygetCustomerOrdersLtResolver909F3D8F": { "Type": "AWS::AppSync::Resolver", "Properties": { @@ -451,6 +483,27 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersLtResolver77468800": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersLt", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order < :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApiorderDsQuerygetCustomerOrdersLeResolverF230A8BE": { "Type": "AWS::AppSync::Resolver", "Properties": { @@ -472,6 +525,27 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersLeResolver836A0389": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersLe", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order <= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApiorderDsQuerygetCustomerOrdersGtResolverF01F806B": { "Type": "AWS::AppSync::Resolver", "Properties": { @@ -493,6 +567,27 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersGtResolver3197CCFE": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersGt", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order > :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApiorderDsQuerygetCustomerOrdersGeResolver63CAD303": { "Type": "AWS::AppSync::Resolver", "Properties": { @@ -514,6 +609,27 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersGeResolver0B78B0B4": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersGe", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order >= :order\",\n \"expressionNames\" : {\n \"#order\" : \"order\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApiorderDsQuerygetCustomerOrdersFilterResolverCD2B8747": { "Type": "AWS::AppSync::Resolver", "Properties": { @@ -556,6 +672,48 @@ "ApiSchema510EECD7" ] }, + "ApiorderDsQuerygetOrderCustomersFilterResolver628CC68D": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersFilter", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"query\" : {\n \"expression\" : \"#order = :order AND begins_with(#customer, :customer)\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, + "ApiorderDsQuerygetOrderCustomersBetweenResolver2048F3CB": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getOrderCustomersBetween", + "TypeName": "Query", + "DataSourceName": "Order", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Query\", \"index\" : \"orderIndex\", \"query\" : {\n \"expression\" : \"#order = :order AND #customer BETWEEN :customer1 AND :customer2\",\n \"expressionNames\" : {\n \"#order\" : \"order\", \"#customer\" : \"customer\"\n },\n \"expressionValues\" : {\n \":order\" : $util.dynamodb.toDynamoDBJson($ctx.args.order), \":customer1\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer1), \":customer2\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer2)\n }\n }}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiorderDsB50C8AAD", + "ApiSchema510EECD7" + ] + }, "ApipaymentDsServiceRole0DAC58D6": { "Type": "AWS::IAM::Role", "Properties": { @@ -799,7 +957,25 @@ "AttributeType": "S" } ], - "BillingMode": "PAY_PER_REQUEST" + "BillingMode": "PAY_PER_REQUEST", + "GlobalSecondaryIndexes": [ + { + "IndexName": "orderIndex", + "KeySchema": [ + { + "AttributeName": "order", + "KeyType": "HASH" + }, + { + "AttributeName": "customer", + "KeyType": "RANGE" + } + ], + "Projection": { + "ProjectionType": "ALL" + } + } + ] }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.graphql b/packages/@aws-cdk/aws-appsync/test/integ.graphql.graphql index 67576c3ed816c..345979e12f169 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.graphql +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.graphql @@ -36,6 +36,13 @@ type Query @aws_api_key { getCustomerOrdersGe(customer: String): Order getCustomerOrdersFilter(customer: String, order: String): Order getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order + getOrderCustomersEq(order: String): [Customer] + getOrderCustomersLt(order: String): [Customer] + getOrderCustomersLe(order: String): [Customer] + getOrderCustomersGt(order: String): [Customer] + getOrderCustomersGe(order: String): [Customer] + getOrderCustomersFilter(order: String, customer: String): [Customer] + getOrderCustomersBetween(order: String, customer1: String, customer2: String): [Customer] getPayment(id: String): Payment } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 1de80995c90a0..b014043b05607 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -84,6 +84,17 @@ const orderTable = new Table(stack, 'OrderTable', { }, removalPolicy: RemovalPolicy.DESTROY, }); +orderTable.addGlobalSecondaryIndex({ + indexName: 'orderIndex', + partitionKey: { + name: 'order', + type: AttributeType.STRING, + }, + sortKey: { + name: 'customer', + type: AttributeType.STRING, + }, +}); new Table(stack, 'PaymentTable', { billingMode: BillingMode.PAY_PER_REQUEST, @@ -157,6 +168,12 @@ for (const { suffix, op } of ops) { requestMappingTemplate: MappingTemplate.dynamoDbQuery(op('customer', 'customer')), responseMappingTemplate: MappingTemplate.dynamoDbResultList(), }); + orderDS.createResolver({ + typeName: 'Query', + fieldName: 'getOrderCustomers' + suffix, + requestMappingTemplate: MappingTemplate.dynamoDbQuery(op('order', 'order'), 'orderIndex'), + responseMappingTemplate: MappingTemplate.dynamoDbResultList(), + }); } orderDS.createResolver({ typeName: 'Query', @@ -172,6 +189,20 @@ orderDS.createResolver({ KeyCondition.eq('customer', 'customer').and(KeyCondition.between('order', 'order1', 'order2'))), responseMappingTemplate: MappingTemplate.dynamoDbResultList(), }); +orderDS.createResolver({ + typeName: 'Query', + fieldName: 'getOrderCustomersFilter', + requestMappingTemplate: MappingTemplate.dynamoDbQuery( + KeyCondition.eq('order', 'order').and(KeyCondition.beginsWith('customer', 'customer'))), + responseMappingTemplate: MappingTemplate.dynamoDbResultList(), +}); +orderDS.createResolver({ + typeName: 'Query', + fieldName: 'getOrderCustomersBetween', + requestMappingTemplate: MappingTemplate.dynamoDbQuery( + KeyCondition.eq('order', 'order').and(KeyCondition.between('customer', 'customer1', 'customer2')), 'orderIndex'), + responseMappingTemplate: MappingTemplate.dynamoDbResultList(), +}); paymentDS.createResolver({ typeName: 'Query', diff --git a/packages/@aws-cdk/aws-athena/.npmignore b/packages/@aws-cdk/aws-athena/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-athena/.npmignore +++ b/packages/@aws-cdk/aws-athena/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index a33bffbb14686..d28bc676d18af 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -56,7 +56,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-autoscaling-common/.npmignore b/packages/@aws-cdk/aws-autoscaling-common/.npmignore index b0d6aa6c90bf1..500c9f6884d44 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling-common/.npmignore @@ -25,4 +25,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore b/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index b5bf3df6b0b48..3a046c9503859 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-autoscaling/.npmignore b/packages/@aws-cdk/aws-autoscaling/.npmignore index 5d8f93d8a9c7e..6b3aee5fcab7c 100644 --- a/packages/@aws-cdk/aws-autoscaling/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling/.npmignore @@ -23,4 +23,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out jest.config.js -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index b84adf0699b09..29a782f24b356 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AutoScaling", diff --git a/packages/@aws-cdk/aws-autoscalingplans/.npmignore b/packages/@aws-cdk/aws-autoscalingplans/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/.npmignore +++ b/packages/@aws-cdk/aws-autoscalingplans/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index be56095ec1992..cbdfb6ba3cec3 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::AutoScalingPlans", diff --git a/packages/@aws-cdk/aws-backup/.npmignore b/packages/@aws-cdk/aws-backup/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-backup/.npmignore +++ b/packages/@aws-cdk/aws-backup/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-backup/package.json b/packages/@aws-cdk/aws-backup/package.json index 2fcc9c8b1582c..a264887259ecc 100644 --- a/packages/@aws-cdk/aws-backup/package.json +++ b/packages/@aws-cdk/aws-backup/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Backup", diff --git a/packages/@aws-cdk/aws-batch/.npmignore b/packages/@aws-cdk/aws-batch/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-batch/.npmignore +++ b/packages/@aws-cdk/aws-batch/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index 970872060bbbd..5caaf31e20bd7 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Batch", @@ -75,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-budgets/.npmignore b/packages/@aws-cdk/aws-budgets/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-budgets/.npmignore +++ b/packages/@aws-cdk/aws-budgets/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index 0d685503f4828..f59821a305b52 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Budgets", diff --git a/packages/@aws-cdk/aws-cassandra/.npmignore b/packages/@aws-cdk/aws-cassandra/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-cassandra/.npmignore +++ b/packages/@aws-cdk/aws-cassandra/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cassandra/package.json b/packages/@aws-cdk/aws-cassandra/package.json index 46adae57ddd00..c047c535fad5a 100644 --- a/packages/@aws-cdk/aws-cassandra/package.json +++ b/packages/@aws-cdk/aws-cassandra/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Cassandra", diff --git a/packages/@aws-cdk/aws-ce/.npmignore b/packages/@aws-cdk/aws-ce/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-ce/.npmignore +++ b/packages/@aws-cdk/aws-ce/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ce/package.json b/packages/@aws-cdk/aws-ce/package.json index dff5201588b1f..b34e30b82ca4b 100644 --- a/packages/@aws-cdk/aws-ce/package.json +++ b/packages/@aws-cdk/aws-ce/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CE", diff --git a/packages/@aws-cdk/aws-certificatemanager/.npmignore b/packages/@aws-cdk/aws-certificatemanager/.npmignore index 0a336369a6bfb..8ace99e984f5a 100644 --- a/packages/@aws-cdk/aws-certificatemanager/.npmignore +++ b/packages/@aws-cdk/aws-certificatemanager/.npmignore @@ -27,3 +27,5 @@ tsconfig.json **/cdk.out junit.xml jest.config.js + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 9985cf3e1d912..2c8dbf640c433 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CertificateManager", diff --git a/packages/@aws-cdk/aws-chatbot/.npmignore b/packages/@aws-cdk/aws-chatbot/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-chatbot/.npmignore +++ b/packages/@aws-cdk/aws-chatbot/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-chatbot/README.md b/packages/@aws-cdk/aws-chatbot/README.md index b45609ebc066f..27954ad252d9d 100644 --- a/packages/@aws-cdk/aws-chatbot/README.md +++ b/packages/@aws-cdk/aws-chatbot/README.md @@ -6,6 +6,10 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index 35b468f95a27b..c699af51544f5 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Chatbot", @@ -99,7 +100,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-cloud9/.npmignore b/packages/@aws-cdk/aws-cloud9/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-cloud9/.npmignore +++ b/packages/@aws-cdk/aws-cloud9/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index de9042b93d715..636198593e417 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Cloud9", diff --git a/packages/@aws-cdk/aws-cloudformation/.npmignore b/packages/@aws-cdk/aws-cloudformation/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-cloudformation/.npmignore +++ b/packages/@aws-cdk/aws-cloudformation/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 9c8edb74fb9c7..a7c731d4675c6 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudFormation" diff --git a/packages/@aws-cdk/aws-cloudfront-origins/.npmignore b/packages/@aws-cdk/aws-cloudfront-origins/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/.npmignore +++ b/packages/@aws-cdk/aws-cloudfront-origins/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/README.md b/packages/@aws-cdk/aws-cloudfront-origins/README.md index 614a089247459..8047fd612b814 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/README.md +++ b/packages/@aws-cdk/aws-cloudfront-origins/README.md @@ -3,9 +3,9 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.svg?style=for-the-badge) -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> The APIs of higher level constructs in this module are in **developer preview** before they become stable. We will only make breaking changes to address unforeseen API issues. Therefore, these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes will be announced in release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. --- @@ -32,7 +32,7 @@ The above will treat the bucket differently based on if `IBucket.isWebsite` is s treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and CloudFront's redirect and error handling will be used. In the latter case, the Origin wil create an origin access identity and grant it access to the underlying bucket. This can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront -URLs and not S3 URLs directly. +URLs and not S3 URLs directly. Alternatively, a custom origin access identity can be passed to the S3 origin in the properties. ## ELBv2 Load Balancer diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts index 9159bc1f545ed..c6e0f83d0c15a 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts @@ -16,6 +16,12 @@ export interface S3OriginProps { * @default '/' */ readonly originPath?: string; + /** + * An optional Origin Access Identity of the origin identity cloudfront will use when calling your s3 bucket. + * + * @default - An Origin Access Identity will be created. + */ + readonly originAccessIdentity?: cloudfront.IOriginAccessIdentity; } /** @@ -50,10 +56,13 @@ export class S3Origin implements cloudfront.IOrigin { * Contains additional logic around bucket permissions and origin access identities. */ class S3BucketOrigin extends cloudfront.OriginBase { - private originAccessIdentity!: cloudfront.OriginAccessIdentity; + private originAccessIdentity!: cloudfront.IOriginAccessIdentity; - constructor(private readonly bucket: s3.IBucket, props: S3OriginProps) { + constructor(private readonly bucket: s3.IBucket, { originAccessIdentity, ...props }: S3OriginProps) { super(bucket.bucketRegionalDomainName, props); + if (originAccessIdentity) { + this.originAccessIdentity = originAccessIdentity; + } } public bind(scope: cdk.Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index e8810f8903394..419fb01f53c78 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -96,7 +96,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "experimental", + "maturity": "developer-preview", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json index 7658a9ac15be2..bf1cb5354e5d3 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json @@ -5,9 +5,8 @@ "Properties": { "DistributionConfig": { "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "cloudfronthttporiginDistributionOrigin162B02709", "ViewerProtocolPolicy": "allow-all" }, diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json index a62e474390482..3e2d0d1a33572 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json @@ -411,9 +411,8 @@ "Properties": { "DistributionConfig": { "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "cloudfrontloadbalanceroriginDistributionOrigin1BCC75186", "ViewerProtocolPolicy": "allow-all" }, diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json index 6efc530e7f5f6..c65231c502f19 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json @@ -70,18 +70,16 @@ "DistributionConfig": { "CacheBehaviors": [ { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "PathPattern": "/api", "TargetOriginId": "cloudfrontorigingroupDistributionOriginGroup10B57F1D1", "ViewerProtocolPolicy": "allow-all" } ], "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "cloudfrontorigingroupDistributionOriginGroup10B57F1D1", "ViewerProtocolPolicy": "allow-all" }, @@ -153,4 +151,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.expected.json new file mode 100644 index 0000000000000..3d17b0944873e --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.expected.json @@ -0,0 +1,57 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "OriginAccessIdentityDF1E3CAC": { + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + "Properties": { + "CloudFrontOriginAccessIdentityConfig": { + "Comment": "Identity for bucket provided by test" + } + } + }, + "Distribution830FAC52": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "TargetOriginId": "cloudfronts3originoaiDistributionOrigin1516C5A91", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "Id": "cloudfronts3originoaiDistributionOrigin1516C5A91", + "S3OriginConfig": { + "OriginAccessIdentity": { + "Fn::Join": [ + "", + [ + "origin-access-identity/cloudfront/", + { + "Ref": "OriginAccessIdentityDF1E3CAC" + } + ] + ] + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.ts new file mode 100644 index 0000000000000..d7331a160abcd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin-oai.ts @@ -0,0 +1,18 @@ +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as origins from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'cloudfront-s3-origin-oai'); + +const bucket = new s3.Bucket(stack, 'Bucket'); +const originAccessIdentity = new cloudfront.OriginAccessIdentity(stack, 'OriginAccessIdentity', { + comment: 'Identity for bucket provided by test', +}); +new cloudfront.Distribution(stack, 'Distribution', { + defaultBehavior: { origin: new origins.S3Origin(bucket, { originAccessIdentity }) }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json index cf4a342fc6583..5eb310b5b92d7 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json @@ -69,9 +69,8 @@ "Properties": { "DistributionConfig": { "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "cloudfronts3originDistributionOrigin1741C4E95", "ViewerProtocolPolicy": "allow-all" }, @@ -106,4 +105,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts index 7545135e4fb0e..75770ce250ce1 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts @@ -34,7 +34,7 @@ describe('With bucket', () => { }); }); - test('can customize properties', () => { + test('can customize originPath property', () => { const bucket = new s3.Bucket(stack, 'Bucket'); const origin = new S3Origin(bucket, { originPath: '/assets' }); @@ -56,6 +56,23 @@ describe('With bucket', () => { }); }); + test('can customize OriginAccessIdentity property', () => { + const bucket = new s3.Bucket(stack, 'Bucket'); + + const originAccessIdentity = new cloudfront.OriginAccessIdentity(stack, 'OriginAccessIdentity', { + comment: 'Identity for bucket provided by test', + }); + + const origin = new S3Origin(bucket, { originAccessIdentity }); + new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin } }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::CloudFrontOriginAccessIdentity', { + CloudFrontOriginAccessIdentityConfig: { + Comment: 'Identity for bucket provided by test', + }, + }); + }); + test('creates an OriginAccessIdentity and grants read permissions on the bucket', () => { const bucket = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-cloudfront/.npmignore b/packages/@aws-cdk/aws-cloudfront/.npmignore index cccec9064cc31..fccf7d5aa28df 100644 --- a/packages/@aws-cdk/aws-cloudfront/.npmignore +++ b/packages/@aws-cdk/aws-cloudfront/.npmignore @@ -25,4 +25,5 @@ tsconfig.json **/cdk.out jest.config.js -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index bb6324c93f1e9..cbf6baf3d888b 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -6,12 +6,12 @@ | Features | Stability | | --- | --- | | CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | -| Higher level constructs for Distribution | ![Experimental](https://img.shields.io/badge/experimental-important.svg?style=for-the-badge) | +| Higher level constructs for Distribution | ![Developer Preview](https://img.shields.io/badge/developer--preview-informational.svg?style=for-the-badge) | | Higher level constructs for CloudFrontWebDistribution | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | > **CFN Resources:** All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. -> **Experimental:** Higher level constructs in this module that are marked as experimental are under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> **Developer Preview:** Higher level constructs in this module that are marked as developer preview have completed their phase of active development and are looking for adoption and feedback. While the same caveats around non-backward compatible as Experimental constructs apply, they will undergo fewer breaking changes. Just as with Experimental constructs, these are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. > **Stable:** Higher level constructs in this module that are marked stable will not undergo any breaking changes. They will strictly follow the [Semantic Versioning](https://semver.org/) model. @@ -35,9 +35,12 @@ for more complex use cases. ### Creating a distribution CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your -content. Origins can be created from S3 buckets or a custom origin (HTTP server). Each distribution has a default behavior which applies to all -requests to that distribution, and routes requests to a primary origin. Constructs to define origins are in the `@aws-cdk/aws-cloudfront-origins` -module. +content. Origins can be created from S3 buckets or a custom origin (HTTP server). Constructs to define origins are in the `@aws-cdk/aws-cloudfront-origins` module. + +Each distribution has a default behavior which applies to all requests to that distribution, and routes requests to a primary origin. +Additional behaviors may be specified for an origin with a given URL path pattern. Behaviors allow routing with multiple origins, +controlling which HTTP methods to support, whether to require users to use HTTPS, and what query strings or cookies to forward to your origin, +among other settings. #### From an S3 Bucket @@ -163,6 +166,78 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` +### Customizing Cache Keys and TTLs with Cache Policies + +You can use a cache policy to improve your cache hit ratio by controlling the values (URL query strings, HTTP headers, and cookies) +that are included in the cache key, and/or adjusting how long items remain in the cache via the time-to-live (TTL) settings. +CloudFront provides some predefined cache policies, known as managed policies, for common use cases. You can use these managed policies, +or you can create your own cache policy that’s specific to your needs. +See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html for more details. + +```ts +// Using an existing cache policy +new cloudfront.Distribution(this, 'myDistManagedPolicy', { + defaultBehavior: { + origin: bucketOrigin, + cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, + }, +}); + +// Creating a custom cache policy -- all parameters optional +const myCachePolicy = new cloudfront.CachePolicy(this, 'myCachePolicy', { + cachePolicyName: 'MyPolicy', + comment: 'A default policy', + defaultTtl: Duration.days(2), + minTtl: Duration.minutes(1), + maxTtl: Duration.days(10), + cookieBehavior: cloudfront.CacheCookieBehavior.all(), + headerBehavior: cloudfront.CacheHeaderBehavior.allowList('X-CustomHeader'), + queryStringBehavior: cloudfront.CacheQueryStringBehavior.denyList('username'), + enableAcceptEncodingGzip: true, + enableAcceptEncodingBrotli: true, +}); +new cloudfront.Distribution(this, 'myDistCustomPolicy', { + defaultBehavior: { + origin: bucketOrigin, + cachePolicy: myCachePolicy, + }, +}); +``` + +### Customizing Origin Requests with Origin Request Policies + +When CloudFront makes a request to an origin, the URL path, request body (if present), and a few standard headers are included. +Other information from the viewer request, such as URL query strings, HTTP headers, and cookies, is not included in the origin request by default. +You can use an origin request policy to control the information that’s included in an origin request. +CloudFront provides some predefined origin request policies, known as managed policies, for common use cases. You can use these managed policies, +or you can create your own origin request policy that’s specific to your needs. +See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-origin-requests.html for more details. + +```ts +// Using an existing origin request policy +new cloudfront.Distribution(this, 'myDistManagedPolicy', { + defaultBehavior: { + origin: bucketOrigin, + originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN, + }, +}); +// Creating a custom origin request policy -- all parameters optional +const myOriginRequestPolicy = new cloudfront.OriginRequestPolicy(stack, 'OriginRequestPolicy', { + originRequestPolicyName: 'MyPolicy', + comment: 'A default policy', + cookieBehavior: cloudfront.OriginRequestCookieBehavior.none(), + headerBehavior: cloudfront.OriginRequestHeaderBehavior.all('CloudFront-Is-Android-Viewer'), + queryStringBehavior: cloudfront.OriginRequestQueryStringBehavior.allowList('username'), +}); +new cloudfront.Distribution(this, 'myDistCustomPolicy', { + defaultBehavior: { + origin: bucketOrigin, + cachePolicy: myCachePolicy, + originRequestPolicy: myOriginRequestPolicy, + }, +}); +``` + ### Lambda@Edge Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts new file mode 100644 index 0000000000000..ac3075d5f9aa4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts @@ -0,0 +1,300 @@ +import { Duration, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnCachePolicy } from './cloudfront.generated'; + +/** + * Represents a Cache Policy + * @experimental + */ +export interface ICachePolicy { + /** + * The ID of the cache policy + * @attribute + */ + readonly cachePolicyId: string; +} + +/** + * Properties for creating a Cache Policy + * @experimental + */ +export interface CachePolicyProps { + /** + * A unique name to identify the cache policy. + * The name must only include '-', '_', or alphanumeric characters. + * @default - generated from the `id` + */ + readonly cachePolicyName?: string; + + /** + * A comment to describe the cache policy. + * @default - no comment + */ + readonly comment?: string; + + /** + * The default amount of time for objects to stay in the CloudFront cache. + * Only used when the origin does not send Cache-Control or Expires headers with the object. + * @default - The greater of 1 day and ``minTtl`` + */ + readonly defaultTtl?: Duration; + + /** + * The minimum amount of time for objects to stay in the CloudFront cache. + * @default Duration.seconds(0) + */ + readonly minTtl?: Duration; + + /** + * The maximum amount of time for objects to stay in the CloudFront cache. + * CloudFront uses this value only when the origin sends Cache-Control or Expires headers with the object. + * @default - The greater of 1 year and ``defaultTtl`` + */ + readonly maxTtl?: Duration; + + /** + * Determines whether any cookies in viewer requests are included in the cache key and automatically included in requests that CloudFront sends to the origin. + * @default CacheCookieBehavior.none() + */ + readonly cookieBehavior?: CacheCookieBehavior; + + /** + * Determines whether any HTTP headers are included in the cache key and automatically included in requests that CloudFront sends to the origin. + * @default CacheHeaderBehavior.none() + */ + readonly headerBehavior?: CacheHeaderBehavior; + + /** + * Determines whether any query strings are included in the cache key and automatically included in requests that CloudFront sends to the origin. + * @default CacheQueryStringBehavior.none() + */ + readonly queryStringBehavior?: CacheQueryStringBehavior; + + /** + * Whether to normalize and include the `Accept-Encoding` header in the cache key when the `Accept-Encoding` header is 'gzip'. + * @default false + */ + readonly enableAcceptEncodingGzip?: boolean; + + /** + * Whether to normalize and include the `Accept-Encoding` header in the cache key when the `Accept-Encoding` header is 'br'. + * @default false + */ + readonly enableAcceptEncodingBrotli?: boolean; +} + +/** + * A Cache Policy configuration. + * + * @resource AWS::CloudFront::CachePolicy + * @experimental + */ +export class CachePolicy extends Resource implements ICachePolicy { + + /** + * Optimize cache efficiency by minimizing the values that CloudFront includes in the cache key. + * Query strings and cookies are not included in the cache key, and only the normalized 'Accept-Encoding' header is included. + */ + public static readonly CACHING_OPTIMIZED = CachePolicy.fromManagedCachePolicy('658327ea-f89d-4fab-a63d-7e88639e58f6'); + /** + * Optimize cache efficiency by minimizing the values that CloudFront includes in the cache key. + * Query strings and cookies are not included in the cache key, and only the normalized 'Accept-Encoding' header is included. + * Disables cache compression. + */ + public static readonly CACHING_OPTIMIZED_FOR_UNCOMPRESSED_OBJECTS = CachePolicy.fromManagedCachePolicy('b2884449-e4de-46a7-ac36-70bc7f1ddd6d'); + /** Disables caching. This policy is useful for dynamic content and for requests that are not cacheable. */ + public static readonly CACHING_DISABLED = CachePolicy.fromManagedCachePolicy('4135ea2d-6df8-44a3-9df3-4b5a84be39ad'); + /** Designed for use with an origin that is an AWS Elemental MediaPackage endpoint. */ + public static readonly ELEMENTAL_MEDIA_PACKAGE = CachePolicy.fromManagedCachePolicy('08627262-05a9-4f76-9ded-b50ca2e3a84f'); + + /** Imports a Cache Policy from its id. */ + public static fromCachePolicyId(scope: Construct, id: string, cachePolicyId: string): ICachePolicy { + return new class extends Resource implements ICachePolicy { + public readonly cachePolicyId = cachePolicyId; + }(scope, id); + } + + /** Use an existing managed cache policy. */ + private static fromManagedCachePolicy(managedCachePolicyId: string): ICachePolicy { + return new class implements ICachePolicy { + public readonly cachePolicyId = managedCachePolicyId; + }(); + } + + public readonly cachePolicyId: string; + + constructor(scope: Construct, id: string, props: CachePolicyProps = {}) { + super(scope, id, { + physicalName: props.cachePolicyName, + }); + + const cachePolicyName = props.cachePolicyName ?? this.node.uniqueId; + if (!Token.isUnresolved(cachePolicyName) && !cachePolicyName.match(/^[\w-]+$/i)) { + throw new Error(`'cachePolicyName' can only include '-', '_', and alphanumeric characters, got: '${props.cachePolicyName}'`); + } + + const minTtl = (props.minTtl ?? Duration.seconds(0)).toSeconds(); + const defaultTtl = Math.max((props.defaultTtl ?? Duration.days(1)).toSeconds(), minTtl); + const maxTtl = Math.max((props.maxTtl ?? Duration.days(365)).toSeconds(), defaultTtl); + + const resource = new CfnCachePolicy(this, 'Resource', { + cachePolicyConfig: { + name: cachePolicyName, + comment: props.comment, + minTtl, + maxTtl, + defaultTtl, + parametersInCacheKeyAndForwardedToOrigin: this.renderCacheKey(props), + }, + }); + + this.cachePolicyId = resource.ref; + } + + private renderCacheKey(props: CachePolicyProps): CfnCachePolicy.ParametersInCacheKeyAndForwardedToOriginProperty { + const cookies = props.cookieBehavior ?? CacheCookieBehavior.none(); + const headers = props.headerBehavior ?? CacheHeaderBehavior.none(); + const queryStrings = props.queryStringBehavior ?? CacheQueryStringBehavior.none(); + + return { + cookiesConfig: { + cookieBehavior: cookies.behavior, + cookies: cookies.cookies, + }, + headersConfig: { + headerBehavior: headers.behavior, + headers: headers.headers, + }, + enableAcceptEncodingGzip: props.enableAcceptEncodingGzip ?? false, + enableAcceptEncodingBrotli: props.enableAcceptEncodingBrotli ?? false, + queryStringsConfig: { + queryStringBehavior: queryStrings.behavior, + queryStrings: queryStrings.queryStrings, + }, + }; + } +} + +/** + * Determines whether any cookies in viewer requests are included in the cache key and + * automatically included in requests that CloudFront sends to the origin. + * @experimental + */ +export class CacheCookieBehavior { + /** + * Cookies in viewer requests are not included in the cache key and + * are not automatically included in requests that CloudFront sends to the origin. + */ + public static none() { return new CacheCookieBehavior('none'); } + + /** + * All cookies in viewer requests are included in the cache key and are automatically included in requests that CloudFront sends to the origin. + */ + public static all() { return new CacheCookieBehavior('all'); } + + /** + * Only the provided `cookies` are included in the cache key and automatically included in requests that CloudFront sends to the origin. + */ + public static allowList(...cookies: string[]) { + if (cookies.length === 0) { + throw new Error('At least one cookie to allow must be provided'); + } + return new CacheCookieBehavior('whitelist', cookies); + } + + /** + * All cookies except the provided `cookies` are included in the cache key and + * automatically included in requests that CloudFront sends to the origin. + */ + public static denyList(...cookies: string[]) { + if (cookies.length === 0) { + throw new Error('At least one cookie to deny must be provided'); + } + return new CacheCookieBehavior('allExcept', cookies); + } + + /** The behavior of cookies: allow all, none, an allow list, or a deny list. */ + public readonly behavior: string; + /** The cookies to allow or deny, if the behavior is an allow or deny list. */ + public readonly cookies?: string[]; + + private constructor(behavior: string, cookies?: string[]) { + this.behavior = behavior; + this.cookies = cookies; + } +} + +/** + * Determines whether any HTTP headers are included in the cache key and automatically included in requests that CloudFront sends to the origin. + * @experimental + */ +export class CacheHeaderBehavior { + /** HTTP headers are not included in the cache key and are not automatically included in requests that CloudFront sends to the origin. */ + public static none() { return new CacheHeaderBehavior('none'); } + /** Listed headers are included in the cache key and are automatically included in requests that CloudFront sends to the origin. */ + public static allowList(...headers: string[]) { + if (headers.length === 0) { + throw new Error('At least one header to allow must be provided'); + } + return new CacheHeaderBehavior('whitelist', headers); + } + + /** If the no headers will be passed, or an allow list of headers. */ + public readonly behavior: string; + /** The headers for the allow/deny list, if applicable. */ + public readonly headers?: string[]; + + private constructor(behavior: string, headers?: string[]) { + this.behavior = behavior; + this.headers = headers; + } +} + +/** + * Determines whether any URL query strings in viewer requests are included in the cache key + * and automatically included in requests that CloudFront sends to the origin. + * @experimental + */ +export class CacheQueryStringBehavior { + /** + * Query strings in viewer requests are not included in the cache key and + * are not automatically included in requests that CloudFront sends to the origin. + */ + public static none() { return new CacheQueryStringBehavior('none'); } + + /** + * All query strings in viewer requests are included in the cache key and are automatically included in requests that CloudFront sends to the origin. + */ + public static all() { return new CacheQueryStringBehavior('all'); } + + /** + * Only the provided `queryStrings` are included in the cache key and automatically included in requests that CloudFront sends to the origin. + */ + public static allowList(...queryStrings: string[]) { + if (queryStrings.length === 0) { + throw new Error('At least one query string to allow must be provided'); + } + return new CacheQueryStringBehavior('whitelist', queryStrings); + } + + /** + * All query strings except the provided `queryStrings` are included in the cache key and + * automatically included in requests that CloudFront sends to the origin. + */ + public static denyList(...queryStrings: string[]) { + if (queryStrings.length === 0) { + throw new Error('At least one query string to deny must be provided'); + } + return new CacheQueryStringBehavior('allExcept', queryStrings); + } + + /** The behavior of query strings -- allow all, none, only an allow list, or a deny list. */ + public readonly behavior: string; + /** The query strings to allow or deny, if the behavior is an allow or deny list. */ + public readonly queryStrings?: string[]; + + private constructor(behavior: string, queryStrings?: string[]) { + this.behavior = behavior; + this.queryStrings = queryStrings; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 04e0de7784e81..806bf2a36c44b 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -3,9 +3,11 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import { IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; +import { ICachePolicy } from './cache-policy'; import { CfnDistribution } from './cloudfront.generated'; import { GeoRestriction } from './geo-restriction'; import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin'; +import { IOriginRequestPolicy } from './origin-request-policy'; import { CacheBehavior } from './private/cache-behavior'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -417,7 +419,7 @@ export class Distribution extends Resource implements IDistribution { const bucket = props.logBucket ?? new s3.Bucket(this, 'LoggingBucket'); return { - bucket: bucket.bucketDomainName, + bucket: bucket.bucketRegionalDomainName, includeCookies: props.logIncludesCookies, prefix: props.logFilePrefix, }; @@ -658,30 +660,30 @@ export interface AddBehaviorOptions { readonly cachedMethods?: CachedMethods; /** - * Whether you want CloudFront to automatically compress certain files for this cache behavior. - * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#compressed-content-cloudfront-file-types - * for file types CloudFront will compress. + * The cache policy for this behavior. The cache policy determines what values are included in the cache key, + * and the time-to-live (TTL) values for the cache. * - * @default false + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html. + * @default CachePolicy.CACHING_OPTIMIZED */ - readonly compress?: boolean; + readonly cachePolicy?: ICachePolicy; /** - * Whether CloudFront will forward query strings to the origin. - * If this is set to true, CloudFront will forward all query parameters to the origin, and cache - * based on all parameters. See `forwardQueryStringCacheKeys` for a way to limit the query parameters - * CloudFront caches on. + * Whether you want CloudFront to automatically compress certain files for this cache behavior. + * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#compressed-content-cloudfront-file-types + * for file types CloudFront will compress. * - * @default false + * @default true */ - readonly forwardQueryString?: boolean; + readonly compress?: boolean; /** - * A set of query string parameter names to use for caching if `forwardQueryString` is set to true. + * The origin request policy for this behavior. The origin request policy determines which values (e.g., headers, cookies) + * are included in requests that CloudFront sends to the origin. * - * @default [] + * @default - none */ - readonly forwardQueryStringCacheKeys?: string[]; + readonly originRequestPolicy?: IOriginRequestPolicy; /** * Set this to true to indicate you want to distribute media files in the Microsoft Smooth Streaming format using this behavior. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index cf78bf5cdd7e0..e04d76a72e48d 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -1,7 +1,9 @@ +export * from './cache-policy'; export * from './distribution'; export * from './geo-restriction'; export * from './origin'; export * from './origin_access_identity'; +export * from './origin-request-policy'; export * from './web_distribution'; // AWS::CloudFront CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts new file mode 100644 index 0000000000000..225de62cb12ec --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts @@ -0,0 +1,238 @@ +import { Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnOriginRequestPolicy } from './cloudfront.generated'; + +/** + * Represents a Origin Request Policy + * @experimental + */ +export interface IOriginRequestPolicy { + /** + * The ID of the origin request policy + * @attribute + */ + readonly originRequestPolicyId: string; +} + +/** + * Properties for creating a Origin Request Policy + * @experimental + */ +export interface OriginRequestPolicyProps { + /** + * A unique name to identify the origin request policy. + * The name must only include '-', '_', or alphanumeric characters. + * @default - generated from the `id` + */ + readonly originRequestPolicyName?: string; + + /** + * A comment to describe the origin request policy. + * @default - no comment + */ + readonly comment?: string; + + /** + * The cookies from viewer requests to include in origin requests. + * @default OriginRequestCookieBehavior.none() + */ + readonly cookieBehavior?: OriginRequestCookieBehavior; + + /** + * The HTTP headers to include in origin requests. These can include headers from viewer requests and additional headers added by CloudFront. + * @default OriginRequestHeaderBehavior.none() + */ + readonly headerBehavior?: OriginRequestHeaderBehavior; + + /** + * The URL query strings from viewer requests to include in origin requests. + * @default OriginRequestQueryStringBehavior.none() + */ + readonly queryStringBehavior?: OriginRequestQueryStringBehavior; +} + +/** + * A Origin Request Policy configuration. + * + * @resource AWS::CloudFront::OriginRequestPolicy + * @experimental + */ +export class OriginRequestPolicy extends Resource implements IOriginRequestPolicy { + + /** This policy includes only the User-Agent and Referer headers. It doesn’t include any query strings or cookies. */ + public static readonly USER_AGENT_REFERER_HEADERS = OriginRequestPolicy.fromManagedOriginRequestPolicy('acba4595-bd28-49b8-b9fe-13317c0390fa'); + /** This policy includes the header that enables cross-origin resource sharing (CORS) requests when the origin is a custom origin. */ + public static readonly CORS_CUSTOM_ORIGIN = OriginRequestPolicy.fromManagedOriginRequestPolicy('59781a5b-3903-41f3-afcb-af62929ccde1'); + /** This policy includes the headers that enable cross-origin resource sharing (CORS) requests when the origin is an Amazon S3 bucket. */ + public static readonly CORS_S3_ORIGIN = OriginRequestPolicy.fromManagedOriginRequestPolicy('88a5eaf4-2fd4-4709-b370-b4c650ea3fcf'); + /** This policy includes all values (query strings, headers, and cookies) in the viewer request. */ + public static readonly ALL_VIEWER = OriginRequestPolicy.fromManagedOriginRequestPolicy('216adef6-5c7f-47e4-b989-5492eafa07d3'); + /** This policy is designed for use with an origin that is an AWS Elemental MediaTailor endpoint. */ + public static readonly ELEMENTAL_MEDIA_TAILOR = OriginRequestPolicy.fromManagedOriginRequestPolicy('775133bc-15f2-49f9-abea-afb2e0bf67d2'); + + /** Imports a Origin Request Policy from its id. */ + public static fromOriginRequestPolicyId(scope: Construct, id: string, originRequestPolicyId: string): IOriginRequestPolicy { + return new class extends Resource implements IOriginRequestPolicy { + public readonly originRequestPolicyId = originRequestPolicyId; + }(scope, id); + } + + /** Use an existing managed origin request policy. */ + private static fromManagedOriginRequestPolicy(managedOriginRequestPolicyId: string): IOriginRequestPolicy { + return new class implements IOriginRequestPolicy { + public readonly originRequestPolicyId = managedOriginRequestPolicyId; + }(); + } + + public readonly originRequestPolicyId: string; + + constructor(scope: Construct, id: string, props: OriginRequestPolicyProps = {}) { + super(scope, id, { + physicalName: props.originRequestPolicyName, + }); + + const originRequestPolicyName = props.originRequestPolicyName ?? this.node.uniqueId; + if (!Token.isUnresolved(originRequestPolicyName) && !originRequestPolicyName.match(/^[\w-]+$/i)) { + throw new Error(`'originRequestPolicyName' can only include '-', '_', and alphanumeric characters, got: '${props.originRequestPolicyName}'`); + } + + const cookies = props.cookieBehavior ?? OriginRequestCookieBehavior.none(); + const headers = props.headerBehavior ?? OriginRequestHeaderBehavior.none(); + const queryStrings = props.queryStringBehavior ?? OriginRequestQueryStringBehavior.none(); + + const resource = new CfnOriginRequestPolicy(this, 'Resource', { + originRequestPolicyConfig: { + name: originRequestPolicyName, + comment: props.comment, + cookiesConfig: { + cookieBehavior: cookies.behavior, + cookies: cookies.cookies, + }, + headersConfig: { + headerBehavior: headers.behavior, + headers: headers.headers, + }, + queryStringsConfig: { + queryStringBehavior: queryStrings.behavior, + queryStrings: queryStrings.queryStrings, + }, + }, + }); + + this.originRequestPolicyId = resource.ref; + } +} + +/** + * Ddetermines whether any cookies in viewer requests (and if so, which cookies) + * are included in requests that CloudFront sends to the origin. + * @experimental + */ +export class OriginRequestCookieBehavior { + /** + * Cookies in viewer requests are not included in requests that CloudFront sends to the origin. + * Any cookies that are listed in a CachePolicy are still included in origin requests. + */ + public static none() { return new OriginRequestCookieBehavior('none'); } + + /** All cookies in viewer requests are included in requests that CloudFront sends to the origin. */ + public static all() { return new OriginRequestCookieBehavior('all'); } + + /** Only the provided `cookies` are included in requests that CloudFront sends to the origin. */ + public static allowList(...cookies: string[]) { + if (cookies.length === 0) { + throw new Error('At least one cookie to allow must be provided'); + } + return new OriginRequestCookieBehavior('whitelist', cookies); + } + + /** The behavior of cookies: allow all, none or an allow list. */ + public readonly behavior: string; + /** The cookies to allow, if the behavior is an allow list. */ + public readonly cookies?: string[]; + + private constructor(behavior: string, cookies?: string[]) { + this.behavior = behavior; + this.cookies = cookies; + } +} + +/** + * Determines whether any HTTP headers (and if so, which headers) are included in requests that CloudFront sends to the origin. + * @experimental + */ +export class OriginRequestHeaderBehavior { + /** + * HTTP headers are not included in requests that CloudFront sends to the origin. + * Any headers that are listed in a CachePolicy are still included in origin requests. + */ + public static none() { return new OriginRequestHeaderBehavior('none'); } + + /** + * All HTTP headers in viewer requests are included in requests that CloudFront sends to the origin. + * Additionally, any additional CloudFront headers provided are included; the additional headers are added by CloudFront. + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-cloudfront-headers.html + */ + public static all(...cloudfrontHeaders: string[]) { + if (cloudfrontHeaders.length > 0) { + if (!cloudfrontHeaders.every(header => header.startsWith('CloudFront-'))) { + throw new Error('additional CloudFront headers passed to `OriginRequestHeaderBehavior.all()` must begin with \'CloudFront-\''); + } + return new OriginRequestHeaderBehavior('allViewerAndWhitelistCloudFront', cloudfrontHeaders); + } else { + return new OriginRequestHeaderBehavior('allViewer'); + } + } + + /** Listed headers are included in requests that CloudFront sends to the origin. */ + public static allowList(...headers: string[]) { + if (headers.length === 0) { + throw new Error('At least one header to allow must be provided'); + } + return new OriginRequestHeaderBehavior('whitelist', headers); + } + + /** The behavior of headers: allow all, none or an allow list. */ + public readonly behavior: string; + /** The headers for the allow list or the included CloudFront headers, if applicable. */ + public readonly headers?: string[]; + + private constructor(behavior: string, headers?: string[]) { + this.behavior = behavior; + this.headers = headers; + } +} + +/** + * Determines whether any URL query strings in viewer requests (and if so, which query strings) + * are included in requests that CloudFront sends to the origin. + * @experimental + */ +export class OriginRequestQueryStringBehavior { + /** + * Query strings in viewer requests are not included in requests that CloudFront sends to the origin. + * Any query strings that are listed in a CachePolicy are still included in origin requests. + */ + public static none() { return new OriginRequestQueryStringBehavior('none'); } + + /** All query strings in viewer requests are included in requests that CloudFront sends to the origin. */ + public static all() { return new OriginRequestQueryStringBehavior('all'); } + + /** Only the provided `queryStrings` are included in requests that CloudFront sends to the origin. */ + public static allowList(...queryStrings: string[]) { + if (queryStrings.length === 0) { + throw new Error('At least one query string to allow must be provided'); + } + return new OriginRequestQueryStringBehavior('whitelist', queryStrings); + } + + /** The behavior of query strings -- allow all, none, or only an allow list. */ + public readonly behavior: string; + /** The query strings to allow, if the behavior is an allow list. */ + public readonly queryStrings?: string[]; + + private constructor(behavior: string, queryStrings?: string[]) { + this.behavior = behavior; + this.queryStrings = queryStrings; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 2eca5e66362f5..4b700e166cecc 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -1,4 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; +import { CachePolicy } from '../cache-policy'; import { CfnDistribution } from '../cloudfront.generated'; import { AddBehaviorOptions, EdgeLambda, LambdaEdgeEventType, ViewerProtocolPolicy } from '../distribution'; @@ -44,11 +45,9 @@ export class CacheBehavior { targetOriginId: this.originId, allowedMethods: this.props.allowedMethods?.methods, cachedMethods: this.props.cachedMethods?.methods, - compress: this.props.compress, - forwardedValues: { - queryString: this.props.forwardQueryString ?? false, - queryStringCacheKeys: this.props.forwardQueryStringCacheKeys, - }, + cachePolicyId: (this.props.cachePolicy ?? CachePolicy.CACHING_OPTIMIZED).cachePolicyId, + compress: this.props.compress ?? true, + originRequestPolicyId: this.props.originRequestPolicy?.originRequestPolicyId, smoothStreaming: this.props.smoothStreaming, viewerProtocolPolicy: this.props.viewerProtocolPolicy ?? ViewerProtocolPolicy.ALLOW_ALL, lambdaFunctionAssociations: this.props.edgeLambdas diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 3edc8a914563e..6889e50739f7e 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -899,7 +899,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu distributionConfig = { ...distributionConfig, logging: { - bucket: this.loggingBucket.bucketDomainName, + bucket: this.loggingBucket.bucketRegionalDomainName, includeCookies: props.loggingConfig.includeCookies || false, prefix: props.loggingConfig.prefix, }, diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index d5642eb629385..64fd49012e4da 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudFront", @@ -106,7 +107,7 @@ "features": [ { "name": "Higher level constructs for Distribution", - "stability": "Experimental" + "stability": "Developer Preview" }, { "name": "Higher level constructs for CloudFrontWebDistribution", @@ -151,7 +152,13 @@ "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerCertificate.aliases", "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerCertificate.props", "docs-public-apis:@aws-cdk/aws-cloudfront.ViewerCertificateOptions", - "props-default-doc:@aws-cdk/aws-cloudfront.ViewerCertificateOptions.aliases" + "props-default-doc:@aws-cdk/aws-cloudfront.ViewerCertificateOptions.aliases", + "construct-interface-extends-iconstruct:@aws-cdk/aws-cloudfront.ICachePolicy", + "resource-interface-extends-resource:@aws-cdk/aws-cloudfront.ICachePolicy", + "resource-attribute:@aws-cdk/aws-cloudfront.CachePolicy.cachePolicyLastModifiedTime", + "construct-interface-extends-iconstruct:@aws-cdk/aws-cloudfront.IOriginRequestPolicy", + "resource-interface-extends-resource:@aws-cdk/aws-cloudfront.IOriginRequestPolicy", + "resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime" ] }, "awscdkio": { diff --git a/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts b/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts new file mode 100644 index 0000000000000..66b5fa80d5749 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts @@ -0,0 +1,218 @@ +import '@aws-cdk/assert/jest'; +import { App, Aws, Duration, Stack } from '@aws-cdk/core'; +import { CachePolicy, CacheCookieBehavior, CacheHeaderBehavior, CacheQueryStringBehavior } from '../lib'; + +describe('CachePolicy', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '123456789012', region: 'testregion' }, + }); + }); + + test('import existing policy by id', () => { + const cachePolicyId = '344f6fe5-7ce5-4df0-a470-3f14177c549c'; + const cachePolicy = CachePolicy.fromCachePolicyId(stack, 'MyPolicy', cachePolicyId); + expect(cachePolicy.cachePolicyId).toEqual(cachePolicyId); + }); + + test('minimal example', () => { + new CachePolicy(stack, 'CachePolicy'); + + expect(stack).toHaveResource('AWS::CloudFront::CachePolicy', { + CachePolicyConfig: { + Name: 'StackCachePolicy0D6FCBC0', + MinTTL: 0, + DefaultTTL: 86400, + MaxTTL: 31536000, + ParametersInCacheKeyAndForwardedToOrigin: { + CookiesConfig: { + CookieBehavior: 'none', + }, + EnableAcceptEncodingGzip: false, + EnableAcceptEncodingBrotli: false, + HeadersConfig: { + HeaderBehavior: 'none', + }, + QueryStringsConfig: { + QueryStringBehavior: 'none', + }, + }, + }, + }); + }); + + test('maximum example', () => { + new CachePolicy(stack, 'CachePolicy', { + cachePolicyName: 'MyPolicy', + comment: 'A default policy', + defaultTtl: Duration.days(2), + minTtl: Duration.minutes(1), + maxTtl: Duration.days(10), + cookieBehavior: CacheCookieBehavior.all(), + headerBehavior: CacheHeaderBehavior.allowList('X-CustomHeader'), + queryStringBehavior: CacheQueryStringBehavior.denyList('username'), + enableAcceptEncodingGzip: true, + enableAcceptEncodingBrotli: true, + }); + + expect(stack).toHaveResource('AWS::CloudFront::CachePolicy', { + CachePolicyConfig: { + Name: 'MyPolicy', + Comment: 'A default policy', + MinTTL: 60, + DefaultTTL: 172800, + MaxTTL: 864000, + ParametersInCacheKeyAndForwardedToOrigin: { + CookiesConfig: { + CookieBehavior: 'all', + }, + HeadersConfig: { + HeaderBehavior: 'whitelist', + Headers: ['X-CustomHeader'], + }, + QueryStringsConfig: { + QueryStringBehavior: 'allExcept', + QueryStrings: ['username'], + }, + EnableAcceptEncodingGzip: true, + EnableAcceptEncodingBrotli: true, + }, + }, + }); + }); + + test('throws if given a cachePolicyName with invalid characters', () => { + const errorMessage = /'cachePolicyName' can only include '-', '_', and alphanumeric characters/; + expect(() => new CachePolicy(stack, 'CachePolicy1', { cachePolicyName: 'My Policy' })).toThrow(errorMessage); + expect(() => new CachePolicy(stack, 'CachePolicy2', { cachePolicyName: 'MyPolicy!' })).toThrow(errorMessage); + expect(() => new CachePolicy(stack, 'CachePolicy3', { cachePolicyName: 'MyPolicy,New' })).toThrow(errorMessage); + + expect(() => new CachePolicy(stack, 'CachePolicy4', { cachePolicyName: 'MyPolicy' })).not.toThrow(); + expect(() => new CachePolicy(stack, 'CachePolicy5', { cachePolicyName: 'My-Policy' })).not.toThrow(); + expect(() => new CachePolicy(stack, 'CachePolicy6', { cachePolicyName: 'My_Policy' })).not.toThrow(); + }); + + test('does not throw if cachePolicyName is a token', () => { + expect(() => new CachePolicy(stack, 'CachePolicy', { + cachePolicyName: Aws.STACK_NAME, + })).not.toThrow(); + }); + + describe('TTLs', () => { + test('default TTLs', () => { + new CachePolicy(stack, 'CachePolicy', { cachePolicyName: 'MyPolicy' }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::CachePolicy', { + CachePolicyConfig: { + MinTTL: 0, + DefaultTTL: 86400, + MaxTTL: 31536000, + }, + }); + }); + + test('defaultTTL defaults to minTTL if minTTL is more than 1 day', () => { + new CachePolicy(stack, 'CachePolicy', { + cachePolicyName: 'MyPolicy', + minTtl: Duration.days(2), + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::CachePolicy', { + CachePolicyConfig: { + MinTTL: 172800, + DefaultTTL: 172800, + MaxTTL: 31536000, + }, + }); + }); + + test('maxTTL defaults to defaultTTL if defaultTTL is more than 1 year', () => { + new CachePolicy(stack, 'CachePolicy', { + cachePolicyName: 'MyPolicy', + defaultTtl: Duration.days(400), + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::CachePolicy', { + CachePolicyConfig: { + MinTTL: 0, + DefaultTTL: 34560000, + MaxTTL: 34560000, + }, + }); + }); + }); +}); + +test('managed policies are provided', () => { + expect(CachePolicy.CACHING_OPTIMIZED.cachePolicyId).toEqual('658327ea-f89d-4fab-a63d-7e88639e58f6'); + expect(CachePolicy.CACHING_OPTIMIZED_FOR_UNCOMPRESSED_OBJECTS.cachePolicyId).toEqual('b2884449-e4de-46a7-ac36-70bc7f1ddd6d'); + expect(CachePolicy.CACHING_DISABLED.cachePolicyId).toEqual('4135ea2d-6df8-44a3-9df3-4b5a84be39ad'); + expect(CachePolicy.ELEMENTAL_MEDIA_PACKAGE.cachePolicyId).toEqual('08627262-05a9-4f76-9ded-b50ca2e3a84f'); +}); + +// CookieBehavior and QueryStringBehavior have identical behavior +describe.each([ + ['CookieBehavior', CacheCookieBehavior, 'cookie', (c: CacheCookieBehavior) => c.cookies], + ['QueryStringBehavior', CacheQueryStringBehavior, 'query string', (qs: CacheQueryStringBehavior) => qs.queryStrings], +])('%s', (_className, clazz, type, items) => { + test('none()', () => { + const behavior = clazz.none(); + + expect(behavior.behavior).toEqual('none'); + expect(items(behavior)).toBeUndefined(); + }); + + test('all()', () => { + const behavior = clazz.all(); + + expect(behavior.behavior).toEqual('all'); + expect(items(behavior)).toBeUndefined(); + }); + + test('allowList()', () => { + const behavior = clazz.allowList('SESSION_ID', 'secrets'); + + expect(behavior.behavior).toEqual('whitelist'); + expect(items(behavior)).toEqual(['SESSION_ID', 'secrets']); + }); + + test('allowList() throws if list is empty', () => { + expect(() => clazz.allowList()).toThrow(new RegExp(`At least one ${type} to allow must be provided`)); + }); + + test('denyList()', () => { + const behavior = clazz.denyList('SESSION_ID', 'secrets'); + + expect(behavior.behavior).toEqual('allExcept'); + expect(items(behavior)).toEqual(['SESSION_ID', 'secrets']); + }); + + test('denyList() throws if list is empty', () => { + expect(() => clazz.denyList()).toThrow(new RegExp(`At least one ${type} to deny must be provided`)); + }); + +}); + +describe('HeaderBehavior', () => { + test('none()', () => { + const headers = CacheHeaderBehavior.none(); + + expect(headers.behavior).toEqual('none'); + expect(headers.headers).toBeUndefined(); + }); + + test('allowList()', () => { + const headers = CacheHeaderBehavior.allowList('X-CustomHeader', 'X-AnotherHeader'); + + expect(headers.behavior).toEqual('whitelist'); + expect(headers.headers).toEqual(['X-CustomHeader', 'X-AnotherHeader']); + }); + + test('allowList() throws if list is empty', () => { + expect(() => CacheHeaderBehavior.allowList()).toThrow(/At least one header to allow must be provided/); + }); +}); diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index c36204b22df11..6cba1e07295f1 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -24,7 +24,8 @@ test('minimal example renders correctly', () => { expect(stack).toHaveResource('AWS::CloudFront::Distribution', { DistributionConfig: { DefaultCacheBehavior: { - ForwardedValues: { QueryString: false }, + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }, @@ -67,7 +68,8 @@ test('exhaustive example of props renders correctly', () => { DistributionConfig: { Aliases: ['example.com'], DefaultCacheBehavior: { - ForwardedValues: { QueryString: false }, + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }, @@ -77,7 +79,7 @@ test('exhaustive example of props renders correctly', () => { HttpVersion: 'http1.1', IPV6Enabled: false, Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, IncludeCookies: true, Prefix: 'logs/', }, @@ -132,13 +134,15 @@ describe('multiple behaviors', () => { expect(stack).toHaveResource('AWS::CloudFront::Distribution', { DistributionConfig: { DefaultCacheBehavior: { - ForwardedValues: { QueryString: false }, + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }, CacheBehaviors: [{ + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, PathPattern: 'api/*', - ForwardedValues: { QueryString: false }, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }], @@ -169,13 +173,15 @@ describe('multiple behaviors', () => { expect(stack).toHaveResource('AWS::CloudFront::Distribution', { DistributionConfig: { DefaultCacheBehavior: { - ForwardedValues: { QueryString: false }, + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }, CacheBehaviors: [{ + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, PathPattern: 'api/*', - ForwardedValues: { QueryString: false }, TargetOriginId: 'StackMyDistOrigin20B96F3AD', ViewerProtocolPolicy: 'allow-all', }], @@ -214,19 +220,22 @@ describe('multiple behaviors', () => { expect(stack).toHaveResource('AWS::CloudFront::Distribution', { DistributionConfig: { DefaultCacheBehavior: { - ForwardedValues: { QueryString: false }, + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }, CacheBehaviors: [{ + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, PathPattern: 'api/1*', - ForwardedValues: { QueryString: false }, TargetOriginId: 'StackMyDistOrigin20B96F3AD', ViewerProtocolPolicy: 'allow-all', }, { + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, PathPattern: 'api/2*', - ForwardedValues: { QueryString: false }, TargetOriginId: 'StackMyDistOrigin1D6D5E535', ViewerProtocolPolicy: 'allow-all', }], @@ -402,7 +411,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, }, }, }); @@ -419,7 +428,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyLoggingBucket4382CD04', 'DomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyLoggingBucket4382CD04', 'RegionalDomainName'] }, }, }, }); @@ -437,7 +446,7 @@ describe('logging', () => { expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { Logging: { - Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'DomainName'] }, + Bucket: { 'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName'] }, IncludeCookies: true, Prefix: 'logs/', }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json index 5b6c0af340e28..36a334898a57f 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-bucket-logging.expected.json @@ -36,7 +36,7 @@ "Bucket": { "Fn::GetAtt": [ "Bucket83908E77", - "DomainName" + "RegionalDomainName" ] }, "IncludeCookies": true, @@ -109,7 +109,7 @@ "Bucket": { "Fn::GetAtt": [ "AnAmazingWebsiteProbably2LoggingBucket222F7CE9", - "DomainName" + "RegionalDomainName" ] }, "IncludeCookies": false @@ -146,4 +146,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json index 26a9b80a80b67..504433198d635 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json @@ -5,9 +5,8 @@ "Properties": { "DistributionConfig": { "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "integdistributionbasicDistOrigin151B53FF1", "ViewerProtocolPolicy": "allow-all" }, @@ -27,4 +26,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json index 150d936cd5565..56829919d2306 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json @@ -11,9 +11,8 @@ "DistributionConfig": { "Comment": "a test", "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, "TargetOriginId": "integdistributionextensiveMyDistOrigin185F089B3", "ViewerProtocolPolicy": "allow-all" }, @@ -25,7 +24,7 @@ "Bucket": { "Fn::GetAtt": [ "MyDistLoggingBucket9B8976BC", - "DomainName" + "RegionalDomainName" ] }, "IncludeCookies": true, @@ -54,4 +53,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json index ef6030afded59..fde47299f760e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json @@ -70,9 +70,8 @@ "Properties": { "DistributionConfig": { "DefaultCacheBehavior": { - "ForwardedValues": { - "QueryString": false - }, + "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad", + "Compress": true, "LambdaFunctionAssociations": [ { "EventType": "origin-request", diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts index 52bc6c1a320c5..b6da02f0fa860 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts @@ -15,6 +15,7 @@ const lambdaFunction = new lambda.Function(stack, 'Lambda', { new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin: new TestOrigin('www.example.com'), + cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, edgeLambdas: [{ functionVersion: lambdaFunction.currentVersion, eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.expected.json new file mode 100644 index 0000000000000..bf8b74f747b46 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.expected.json @@ -0,0 +1,78 @@ +{ + "Resources": { + "CachePolicy26D8A535": { + "Type": "AWS::CloudFront::CachePolicy", + "Properties": { + "CachePolicyConfig": { + "DefaultTTL": 86400, + "MaxTTL": 31536000, + "MinTTL": 0, + "Name": "ACustomCachePolicy", + "ParametersInCacheKeyAndForwardedToOrigin": { + "CookiesConfig": { + "CookieBehavior": "none" + }, + "EnableAcceptEncodingGzip": false, + "EnableAcceptEncodingBrotli": false, + "HeadersConfig": { + "HeaderBehavior": "none" + }, + "QueryStringsConfig": { + "QueryStringBehavior": "none" + } + } + } + } + }, + "OriginRequestPolicy3EFDB4FA": { + "Type": "AWS::CloudFront::OriginRequestPolicy", + "Properties": { + "OriginRequestPolicyConfig": { + "CookiesConfig": { + "CookieBehavior": "none" + }, + "HeadersConfig": { + "HeaderBehavior": "allViewerAndWhitelistCloudFront", + "Headers": [ + "CloudFront-Forwarded-Proto" + ] + }, + "Name": "ACustomOriginRequestPolicy", + "QueryStringsConfig": { + "QueryStringBehavior": "none" + } + } + } + }, + "DistB3B78991": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": { + "Ref": "CachePolicy26D8A535" + }, + "Compress": true, + "OriginRequestPolicyId": { + "Ref": "OriginRequestPolicy3EFDB4FA" + }, + "TargetOriginId": "integdistributionpoliciesDistOrigin17849EF2C", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integdistributionpoliciesDistOrigin17849EF2C" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.ts new file mode 100644 index 0000000000000..8da36a18129a4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-policies.ts @@ -0,0 +1,25 @@ +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-distribution-policies'); + +const cachePolicy = new cloudfront.CachePolicy(stack, 'CachePolicy', { + cachePolicyName: 'ACustomCachePolicy', +}); + +const originRequestPolicy = new cloudfront.OriginRequestPolicy(stack, 'OriginRequestPolicy', { + originRequestPolicyName: 'ACustomOriginRequestPolicy', + headerBehavior: cloudfront.OriginRequestHeaderBehavior.all('CloudFront-Forwarded-Proto'), +}); + +new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { + origin: new TestOrigin('www.example.com'), + cachePolicy, + originRequestPolicy, + }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts b/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts new file mode 100644 index 0000000000000..e719225c4845c --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts @@ -0,0 +1,165 @@ +import '@aws-cdk/assert/jest'; +import { App, Aws, Stack } from '@aws-cdk/core'; +import { OriginRequestPolicy, OriginRequestCookieBehavior, OriginRequestHeaderBehavior, OriginRequestQueryStringBehavior } from '../lib'; + +describe('OriginRequestPolicy', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '123456789012', region: 'testregion' }, + }); + }); + + test('import existing policy by id', () => { + const originRequestPolicyId = '344f6fe5-7ce5-4df0-a470-3f14177c549c'; + const originRequestPolicy = OriginRequestPolicy.fromOriginRequestPolicyId(stack, 'MyPolicy', originRequestPolicyId); + expect(originRequestPolicy.originRequestPolicyId).toEqual(originRequestPolicyId); + }); + + test('minimal example', () => { + new OriginRequestPolicy(stack, 'OriginRequestPolicy'); + + expect(stack).toHaveResource('AWS::CloudFront::OriginRequestPolicy', { + OriginRequestPolicyConfig: { + Name: 'StackOriginRequestPolicy6B17D9ED', + CookiesConfig: { + CookieBehavior: 'none', + }, + HeadersConfig: { + HeaderBehavior: 'none', + }, + QueryStringsConfig: { + QueryStringBehavior: 'none', + }, + }, + }); + }); + + test('maximum example', () => { + new OriginRequestPolicy(stack, 'OriginRequestPolicy', { + originRequestPolicyName: 'MyPolicy', + comment: 'A default policy', + cookieBehavior: OriginRequestCookieBehavior.none(), + headerBehavior: OriginRequestHeaderBehavior.all('CloudFront-Is-Android-Viewer'), + queryStringBehavior: OriginRequestQueryStringBehavior.allowList('username'), + }); + + expect(stack).toHaveResource('AWS::CloudFront::OriginRequestPolicy', { + OriginRequestPolicyConfig: { + Name: 'MyPolicy', + Comment: 'A default policy', + CookiesConfig: { + CookieBehavior: 'none', + }, + HeadersConfig: { + HeaderBehavior: 'allViewerAndWhitelistCloudFront', + Headers: ['CloudFront-Is-Android-Viewer'], + }, + QueryStringsConfig: { + QueryStringBehavior: 'whitelist', + QueryStrings: ['username'], + }, + }, + }); + }); + + test('throws if given a originRequestPolicyName with invalid characters', () => { + const errorMessage = /'originRequestPolicyName' can only include '-', '_', and alphanumeric characters/; + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy1', { originRequestPolicyName: 'My Policy' })).toThrow(errorMessage); + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy2', { originRequestPolicyName: 'MyPolicy!' })).toThrow(errorMessage); + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy3', { originRequestPolicyName: 'MyPolicy,New' })).toThrow(errorMessage); + + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy4', { originRequestPolicyName: 'MyPolicy' })).not.toThrow(); + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy5', { originRequestPolicyName: 'My-Policy' })).not.toThrow(); + expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy6', { originRequestPolicyName: 'My_Policy' })).not.toThrow(); + }); + + test('does not throw if originRequestPolicyName is a token', () => { + expect(() => new OriginRequestPolicy(stack, 'CachePolicy', { + originRequestPolicyName: Aws.STACK_NAME, + })).not.toThrow(); + }); +}); + +test('managed policies are provided', () => { + expect(OriginRequestPolicy.USER_AGENT_REFERER_HEADERS.originRequestPolicyId).toEqual('acba4595-bd28-49b8-b9fe-13317c0390fa'); + expect(OriginRequestPolicy.CORS_CUSTOM_ORIGIN.originRequestPolicyId).toEqual('59781a5b-3903-41f3-afcb-af62929ccde1'); + expect(OriginRequestPolicy.CORS_S3_ORIGIN.originRequestPolicyId).toEqual('88a5eaf4-2fd4-4709-b370-b4c650ea3fcf'); + expect(OriginRequestPolicy.ALL_VIEWER.originRequestPolicyId).toEqual('216adef6-5c7f-47e4-b989-5492eafa07d3'); + expect(OriginRequestPolicy.ELEMENTAL_MEDIA_TAILOR.originRequestPolicyId).toEqual('775133bc-15f2-49f9-abea-afb2e0bf67d2'); +}); + +// OriginRequestCookieBehavior and OriginRequestQueryStringBehavior have identical behavior +describe.each([ + ['CookieBehavior', OriginRequestCookieBehavior, 'cookie', (c: OriginRequestCookieBehavior) => c.cookies], + ['QueryStringBehavior', OriginRequestQueryStringBehavior, 'query string', (qs: OriginRequestQueryStringBehavior) => qs.queryStrings], +])('%s', (_className, clazz, type, items) => { + test('none()', () => { + const behavior = clazz.none(); + + expect(behavior.behavior).toEqual('none'); + expect(items(behavior)).toBeUndefined(); + }); + + test('all()', () => { + const behavior = clazz.all(); + + expect(behavior.behavior).toEqual('all'); + expect(items(behavior)).toBeUndefined(); + }); + + test('allowList()', () => { + const behavior = clazz.allowList('SESSION_ID', 'secrets'); + + expect(behavior.behavior).toEqual('whitelist'); + expect(items(behavior)).toEqual(['SESSION_ID', 'secrets']); + }); + + test('allowList() throws if list is empty', () => { + expect(() => clazz.allowList()).toThrow(new RegExp(`At least one ${type} to allow must be provided`)); + }); +}); + +describe('HeaderBehavior', () => { + test('none()', () => { + const headers = OriginRequestHeaderBehavior.none(); + + expect(headers.behavior).toEqual('none'); + expect(headers.headers).toBeUndefined(); + }); + + test('allowList()', () => { + const headers = OriginRequestHeaderBehavior.allowList('X-CustomHeader', 'X-AnotherHeader'); + + expect(headers.behavior).toEqual('whitelist'); + expect(headers.headers).toEqual(['X-CustomHeader', 'X-AnotherHeader']); + }); + + test('allowList() throws if list is empty', () => { + expect(() => OriginRequestHeaderBehavior.allowList()).toThrow(/At least one header to allow must be provided/); + }); + + describe('all()', () => { + test('defaults to allViewer', () => { + const headers = OriginRequestHeaderBehavior.all(); + + expect(headers.behavior).toEqual('allViewer'); + expect(headers.headers).toBeUndefined(); + }); + + test('accepts additional CloudFront headers', () => { + const headers = OriginRequestHeaderBehavior.all('CloudFront-Is-Android-Viewer', 'CloudFront-Viewer-Country'); + + expect(headers.behavior).toEqual('allViewerAndWhitelistCloudFront'); + expect(headers.headers).toEqual(['CloudFront-Is-Android-Viewer', 'CloudFront-Viewer-Country']); + }); + + test('throws if provided a header that is not a CloudFront- header', () => { + const errorMessage = 'additional CloudFront headers passed to `OriginRequestHeaderBehavior.all()` must begin with \'CloudFront-\''; + expect(() => { OriginRequestHeaderBehavior.all('X-MyCustomHeader'); }).toThrow(errorMessage); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts index 58111d9fc2ef8..a1e315fdcdd96 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { AllowedMethods, CachedMethods, LambdaEdgeEventType, ViewerProtocolPolicy } from '../../lib'; +import { AllowedMethods, CachedMethods, CachePolicy, LambdaEdgeEventType, OriginRequestPolicy, ViewerProtocolPolicy } from '../../lib'; import { CacheBehavior } from '../../lib/private/cache-behavior'; let app: App; @@ -21,8 +21,9 @@ test('renders the minimum template with an origin and path specified', () => { expect(behavior._renderBehavior()).toEqual({ targetOriginId: 'origin_id', + cachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + compress: true, pathPattern: '*', - forwardedValues: { queryString: false }, viewerProtocolPolicy: 'allow-all', }); }); @@ -33,9 +34,9 @@ test('renders with all properties specified', () => { pathPattern: '*', allowedMethods: AllowedMethods.ALLOW_ALL, cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: CachePolicy.CACHING_OPTIMIZED, compress: true, - forwardQueryString: true, - forwardQueryStringCacheKeys: ['user_id', 'auth'], + originRequestPolicy: OriginRequestPolicy.ALL_VIEWER, smoothStreaming: true, viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY, edgeLambdas: [{ @@ -50,11 +51,9 @@ test('renders with all properties specified', () => { pathPattern: '*', allowedMethods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'PATCH', 'POST', 'DELETE'], cachedMethods: ['GET', 'HEAD', 'OPTIONS'], + cachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', compress: true, - forwardedValues: { - queryString: true, - queryStringCacheKeys: ['user_id', 'auth'], - }, + originRequestPolicyId: '216adef6-5c7f-47e4-b989-5492eafa07d3', smoothStreaming: true, viewerProtocolPolicy: 'https-only', lambdaFunctionAssociations: [{ diff --git a/packages/@aws-cdk/aws-cloudtrail/.npmignore b/packages/@aws-cdk/aws-cloudtrail/.npmignore index 34633f72bca87..f507135a52f29 100644 --- a/packages/@aws-cdk/aws-cloudtrail/.npmignore +++ b/packages/@aws-cdk/aws-cloudtrail/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index bdddf764b1fe6..4306f050b9d1c 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -49,7 +49,8 @@ "integ": "cdk-integ", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudTrail", @@ -77,7 +78,7 @@ "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "colors": "^1.4.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore b/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore +++ b/packages/@aws-cdk/aws-cloudwatch-actions/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/package.json b/packages/@aws-cdk/aws-cloudwatch-actions/package.json index 5cd9a914188f9..7a3b1456a6141 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/package.json +++ b/packages/@aws-cdk/aws-cloudwatch-actions/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudwatch/.npmignore b/packages/@aws-cdk/aws-cloudwatch/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-cloudwatch/.npmignore +++ b/packages/@aws-cdk/aws-cloudwatch/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index 094f36c166e4b..b22bfe7c5adb1 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudWatch", diff --git a/packages/monocdk-experiment/.eslintrc.js b/packages/@aws-cdk/aws-codeartifact/.eslintrc.js similarity index 100% rename from packages/monocdk-experiment/.eslintrc.js rename to packages/@aws-cdk/aws-codeartifact/.eslintrc.js diff --git a/packages/@aws-cdk/aws-codeartifact/.gitignore b/packages/@aws-cdk/aws-codeartifact/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js +junit.xml diff --git a/packages/@aws-cdk/aws-codeartifact/.npmignore b/packages/@aws-cdk/aws-codeartifact/.npmignore new file mode 100644 index 0000000000000..63ab95621c764 --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/.npmignore @@ -0,0 +1,27 @@ +# 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 +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/monocdk-experiment/LICENSE b/packages/@aws-cdk/aws-codeartifact/LICENSE similarity index 100% rename from packages/monocdk-experiment/LICENSE rename to packages/@aws-cdk/aws-codeartifact/LICENSE diff --git a/packages/monocdk-experiment/NOTICE b/packages/@aws-cdk/aws-codeartifact/NOTICE similarity index 100% rename from packages/monocdk-experiment/NOTICE rename to packages/@aws-cdk/aws-codeartifact/NOTICE diff --git a/packages/@aws-cdk/aws-codeartifact/README.md b/packages/@aws-cdk/aws-codeartifact/README.md new file mode 100644 index 0000000000000..f86672c07c50a --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/README.md @@ -0,0 +1,16 @@ +## AWS::CodeArtifact Construct Library + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import codeartifact = require('@aws-cdk/aws-codeartifact'); +``` diff --git a/packages/@aws-cdk/aws-codeartifact/jest.config.js b/packages/@aws-cdk/aws-codeartifact/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-codeartifact/lib/index.ts b/packages/@aws-cdk/aws-codeartifact/lib/index.ts new file mode 100644 index 0000000000000..0cba8c930ded5 --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::CodeArtifact CloudFormation Resources: +export * from './codeartifact.generated'; diff --git a/packages/@aws-cdk/aws-codeartifact/package.json b/packages/@aws-cdk/aws-codeartifact/package.json new file mode 100644 index 0000000000000..b34c43c558a78 --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/package.json @@ -0,0 +1,96 @@ +{ + "name": "@aws-cdk/aws-codeartifact", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::CodeArtifact", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CodeArtifact", + "packageId": "Amazon.CDK.AWS.CodeArtifact", + "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.codeartifact", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "codeartifact" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-codeartifact", + "module": "aws_cdk.aws_codeartifact" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codeartifact" + }, + "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", + "gen": "cfn2ts" + }, + "cdk-build": { + "cloudformation": "AWS::CodeArtifact", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::CodeArtifact", + "aws-codeartifact" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-codeartifact/test/codeartifact.test.ts b/packages/@aws-cdk/aws-codeartifact/test/codeartifact.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-codeartifact/test/codeartifact.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/aws-codebuild/.npmignore b/packages/@aws-cdk/aws-codebuild/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-codebuild/.npmignore +++ b/packages/@aws-cdk/aws-codebuild/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 754213cc15099..bcbb85a9ba588 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -793,6 +793,7 @@ export class Project extends ProjectBase { 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', ], resources: [renderReportGroupArn(this, `${this.projectName}-*`)], })); diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index b90117c6b9f15..25390ea5e64ae 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeBuild", diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json index 6eb429e6cb569..3551f01dadd54 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json @@ -84,7 +84,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json index c7162f3219c85..75491ab8fa653 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json @@ -84,7 +84,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 558dd072c7f3f..893cb2836245c 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -105,6 +105,7 @@ export = { 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', ], 'Effect': 'Allow', 'Resource': { @@ -277,6 +278,7 @@ export = { 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', ], 'Effect': 'Allow', 'Resource': { @@ -475,6 +477,7 @@ export = { 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', ], 'Effect': 'Allow', 'Resource': { diff --git a/packages/@aws-cdk/aws-codecommit/.npmignore b/packages/@aws-cdk/aws-codecommit/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-codecommit/.npmignore +++ b/packages/@aws-cdk/aws-codecommit/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 6bc68e1268bd8..cb5bf1f4945be 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeCommit", diff --git a/packages/@aws-cdk/aws-codedeploy/.npmignore b/packages/@aws-cdk/aws-codedeploy/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-codedeploy/.npmignore +++ b/packages/@aws-cdk/aws-codedeploy/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index 519188d2fd2fa..972ca7ef0a523 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -225,6 +225,36 @@ In order to deploy a new version of this function: 2. Re-deploy the stack (this will trigger a deployment). 3. Monitor the CodeDeploy deployment as traffic shifts between the versions. + +#### Create a custom Deployment Config + +CodeDeploy for Lambda comes with built-in configurations for traffic shifting. +If you want to specify your own strategy, +you can do so with the CustomLambdaDeploymentConfig construct, +letting you specify precisely how fast a new function version is deployed. + +```ts +const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: Duration.minutes(1), + percentage: 5, +}); +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { + application, + alias, + deploymentConfig: config, +}); +``` + +You can specify a custom name for your deployment config, but if you do you will not be able to update the interval/percentage through CDK. +```ts +const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: Duration.minutes(1), + percentage: 5, + deploymentConfigName: 'MyDeploymentConfig', +}); +``` #### Rollbacks and Alarms CodeDeploy will roll back if the deployment fails. You can optionally trigger a rollback when one or more alarms are in a failed state: diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts new file mode 100644 index 0000000000000..870937f046c32 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts @@ -0,0 +1,160 @@ +import { Duration, Resource } from '@aws-cdk/core'; +import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; +import { Construct } from 'constructs'; +import { arnForDeploymentConfig } from '../utils'; +import { ILambdaDeploymentConfig } from './deployment-config'; + +/** + * Lambda Deployment config type + */ +export enum CustomLambdaDeploymentConfigType { + /** + * Canary deployment type + */ + CANARY = 'Canary', + + /** + * Linear deployment type + */ + LINEAR = 'Linear' +} + +/** + * Properties of a reference to a CodeDeploy Lambda Deployment Configuration. + */ +export interface CustomLambdaDeploymentConfigProps { + + /** + * The type of deployment config, either CANARY or LINEAR + */ + readonly type: CustomLambdaDeploymentConfigType; + + /** + * The integer percentage of traffic to shift: + * - For LINEAR, the percentage to shift every interval + * - For CANARY, the percentage to shift until the interval passes, before the full deployment + */ + readonly percentage: number; + + /** + * The interval, in number of minutes: + * - For LINEAR, how frequently additional traffic is shifted + * - For CANARY, how long to shift traffic before the full deployment + */ + readonly interval: Duration; + + /** + * The verbatim name of the deployment config. Must be unique per account/region. + * Other parameters cannot be updated if this name is provided. + * @default - automatically generated name + */ + readonly deploymentConfigName?: string; +} + +/** + * A custom Deployment Configuration for a Lambda Deployment Group. + * @resource AWS::CodeDeploy::DeploymentGroup + */ +export class CustomLambdaDeploymentConfig extends Resource implements ILambdaDeploymentConfig { + + /** + * The name of the deployment config + * @attribute + */ + public readonly deploymentConfigName: string; + + /** + * The arn of the deployment config + * @attribute + */ + public readonly deploymentConfigArn: string; + + public constructor(scope: Construct, id: string, props: CustomLambdaDeploymentConfigProps) { + super(scope, id); + this.validateParameters(props); + + // In this section we make the argument for the AWS API call + const deploymentType = 'TimeBased' + props.type.toString(); + const intervalMinutes = props.interval.toMinutes().toString(); + const percentage = props.percentage.toString(); + let routingConfig; // The argument to the AWS API call + if (props.type == CustomLambdaDeploymentConfigType.CANARY) { + routingConfig = { + type: deploymentType, + timeBasedCanary: { + canaryInterval: intervalMinutes, + canaryPercentage: percentage, + }, + }; + } else if (props.type == CustomLambdaDeploymentConfigType.LINEAR) { + routingConfig = { + type: deploymentType, + timeBasedLinear: { + linearInterval: intervalMinutes, + linearPercentage: percentage, + }, + }; + } + + // Generates the name of the deployment config. It's also what you'll see in the AWS console + // The name of the config is .LambdaPercentMinutes + // Unless the user provides an explicit name + this.deploymentConfigName = props.deploymentConfigName !== undefined + ? props.deploymentConfigName + : `${this.node.uniqueId}.Lambda${props.type}${props.percentage}Percent${props.type === CustomLambdaDeploymentConfigType.LINEAR + ? 'Every' + : ''}${props.interval.toMinutes()}Minutes`; + this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName); + + // The AWS Custom Resource that calls CodeDeploy to create and delete the resource + new AwsCustomResource(this, 'DeploymentConfig', { + onCreate: { // Run on creation only, to make the resource + service: 'CodeDeploy', + action: 'createDeploymentConfig', + parameters: { + deploymentConfigName: this.deploymentConfigName, + computePlatform: 'Lambda', + trafficRoutingConfig: routingConfig, + }, + // This `resourceName` is the initial physical ID of the config + physicalResourceId: PhysicalResourceId.of(this.deploymentConfigName), + }, + onUpdate: { // Run on stack update + service: 'CodeDeploy', + action: 'createDeploymentConfig', + parameters: { + deploymentConfigName: this.deploymentConfigName, + computePlatform: 'Lambda', + trafficRoutingConfig: routingConfig, + }, + // If `resourceName` is different from the last stack update (or creation), + // the old config gets deleted and the new one is created + physicalResourceId: PhysicalResourceId.of(this.deploymentConfigName), + }, + onDelete: { // Run on deletion, or on resource replacement + service: 'CodeDeploy', + action: 'deleteDeploymentConfig', + parameters: { + deploymentConfigName: this.deploymentConfigName, + }, + }, + policy: AwsCustomResourcePolicy.fromSdkCalls({ + resources: AwsCustomResourcePolicy.ANY_RESOURCE, + }), + }); + } + + // Validate the inputs. The percentage/interval limits come from CodeDeploy + private validateParameters(props: CustomLambdaDeploymentConfigProps): void { + if ( !(1 <= props.percentage && props.percentage <= 99) ) { + throw new Error( + `Invalid deployment config percentage "${props.percentage.toString()}". \ + Step percentage must be an integer between 1 and 99.`); + } + if (props.interval.toMinutes() > 2880) { + throw new Error( + `Invalid deployment config interval "${props.interval.toString()}". \ + Traffic shifting intervals must be positive integers up to 2880 (2 days).`); + } + } +} diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 7586fd51728c5..c8b8152356091 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -197,6 +197,12 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy afterAllowTrafficHook: cdk.Lazy.stringValue({ produce: () => this.postHook && this.postHook.functionName }), }, }; + + // If the deployment config is a construct, add a dependency to ensure the deployment config + // is created before the deployment group is. + if (this.deploymentConfig instanceof Construct) { + this.node.addDependency(this.deploymentConfig); + } } /** diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/index.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/index.ts index 933062a88e18e..268df7f0fb93f 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/index.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/index.ts @@ -1,3 +1,4 @@ export * from './application'; +export * from './custom-deployment-config'; export * from './deployment-config'; export * from './deployment-group'; diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 27e9e2b715745..ddc7c0302bc57 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeDeploy", @@ -91,6 +92,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "constructs": "^3.0.4" }, "homepage": "https://github.com/aws/aws-cdk", @@ -104,6 +106,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/custom-resources": "0.0.0", "constructs": "^3.0.4" }, "engines": { @@ -163,7 +166,8 @@ "docs-public-apis:@aws-cdk/aws-codedeploy.IEcsApplication.applicationArn", "docs-public-apis:@aws-cdk/aws-codedeploy.IEcsApplication.applicationName", "docs-public-apis:@aws-cdk/aws-codedeploy.IEcsDeploymentConfig.deploymentConfigArn", - "docs-public-apis:@aws-cdk/aws-codedeploy.IEcsDeploymentConfig.deploymentConfigName" + "docs-public-apis:@aws-cdk/aws-codedeploy.IEcsDeploymentConfig.deploymentConfigName", + "props-physical-name:@aws-cdk/aws-codedeploy.CustomLambdaDeploymentConfigProps" ] }, "stability": "stable", diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/test.custom-deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.custom-deployment-config.ts new file mode 100644 index 0000000000000..61917637c19d9 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.custom-deployment-config.ts @@ -0,0 +1,228 @@ +import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import { ICallbackFunction, Test } from 'nodeunit'; +import * as codedeploy from '../../lib'; + +function mockFunction(stack: cdk.Stack, id: string) { + return new lambda.Function(stack, id, { + code: lambda.Code.fromInline('mock'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); +} +function mockAlias(stack: cdk.Stack) { + return new lambda.Alias(stack, 'Alias', { + aliasName: 'my-alias', + version: new lambda.Version(stack, 'Version', { + lambda: mockFunction(stack, 'Function'), + }), + }); +} + +let stack: cdk.Stack; +let application: codedeploy.LambdaApplication; +let alias: lambda.Alias; + +export = { + 'setUp'(cb: ICallbackFunction) { + stack = new cdk.Stack(); + application = new codedeploy.LambdaApplication(stack, 'MyApp'); + alias = mockAlias(stack); + cb(); + }, + 'custom resource created'(test: Test) { + + // WHEN + const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: cdk.Duration.minutes(1), + percentage: 5, + }); + new codedeploy.LambdaDeploymentGroup(stack, 'MyDG', { + application, + alias, + deploymentConfig: config, + }); + + // THEN + expect(stack).to(haveResourceLike('Custom::AWS', { + ServiceToken: { + 'Fn::GetAtt': [ + 'AWS679f53fac002430cb0da5b7982bd22872D164C4C', + 'Arn', + ], + }, + Create: { + action: 'createDeploymentConfig', + service: 'CodeDeploy', + parameters: { + computePlatform: 'Lambda', + deploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', + trafficRoutingConfig: { + timeBasedCanary: { + canaryPercentage: '5', + canaryInterval: '1', + }, + type: 'TimeBasedCanary', + }, + }, + physicalResourceId: { + id: 'CustomConfig.LambdaCanary5Percent1Minutes', + }, + }, + Update: { + action: 'createDeploymentConfig', + service: 'CodeDeploy', + parameters: { + computePlatform: 'Lambda', + deploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', + trafficRoutingConfig: { + timeBasedCanary: { + canaryPercentage: '5', + canaryInterval: '1', + }, + type: 'TimeBasedCanary', + }, + }, + physicalResourceId: { + id: 'CustomConfig.LambdaCanary5Percent1Minutes', + }, + }, + Delete: { + action: 'deleteDeploymentConfig', + service: 'CodeDeploy', + parameters: { + deploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', + }, + }, + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'codedeploy:CreateDeploymentConfig', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'codedeploy:DeleteDeploymentConfig', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + test.done(); + }, + 'custom resource created with specific name'(test: Test) { + + // WHEN + const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: cdk.Duration.minutes(1), + percentage: 5, + deploymentConfigName: 'MyDeploymentConfig', + }); + new codedeploy.LambdaDeploymentGroup(stack, 'MyDG', { + application, + alias, + deploymentConfig: config, + }); + + // THEN + expect(stack).to(haveResourceLike('Custom::AWS', { + Create: { + parameters: { + deploymentConfigName: 'MyDeploymentConfig', + }, + physicalResourceId: { + id: 'MyDeploymentConfig', + }, + }, + Update: { + parameters: { + deploymentConfigName: 'MyDeploymentConfig', + }, + physicalResourceId: { + id: 'MyDeploymentConfig', + }, + }, + Delete: { + parameters: { + deploymentConfigName: 'MyDeploymentConfig', + }, + }, + })); + test.done(); + }, + 'can create linear custom config'(test: Test) { + + // WHEN + const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.LINEAR, + interval: cdk.Duration.minutes(1), + percentage: 5, + }); + new codedeploy.LambdaDeploymentGroup(stack, 'MyDG', { + application, + alias, + deploymentConfig: config, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + DeploymentConfigName: 'CustomConfig.LambdaLinear5PercentEvery1Minutes', + })); + + test.done(); + }, + 'can create canary custom config'(test: Test) { + + // WHEN + const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: cdk.Duration.minutes(1), + percentage: 5, + }); + new codedeploy.LambdaDeploymentGroup(stack, 'MyDG', { + application, + alias, + deploymentConfig: config, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + DeploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', + })); + test.done(); + }, + 'dependency on the config exists to ensure ordering'(test: Test) { + + // WHEN + const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { + type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, + interval: cdk.Duration.minutes(1), + percentage: 5, + }); + new codedeploy.LambdaDeploymentGroup(stack, 'MyDG', { + application, + alias, + deploymentConfig: config, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeDeploy::DeploymentGroup', { + Properties: { + DeploymentConfigName: 'CustomConfig.LambdaCanary5Percent1Minutes', + }, + DependsOn: [ + 'CustomConfigDeploymentConfigCustomResourcePolicy0426B684', + 'CustomConfigDeploymentConfigE9E1F384', + ], + }, ResourcePart.CompleteDefinition)); + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-codeguruprofiler/.npmignore b/packages/@aws-cdk/aws-codeguruprofiler/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/.npmignore +++ b/packages/@aws-cdk/aws-codeguruprofiler/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codeguruprofiler/README.md b/packages/@aws-cdk/aws-codeguruprofiler/README.md index cf18c19ae296a..a0b31cb1f2246 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/README.md +++ b/packages/@aws-cdk/aws-codeguruprofiler/README.md @@ -6,6 +6,10 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index 8140b965fec4a..be1fb69d524d6 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeGuruProfiler", @@ -93,7 +94,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-codegurureviewer/.npmignore b/packages/@aws-cdk/aws-codegurureviewer/.npmignore index 72b7fcab0a22a..7b8fb69082a0f 100644 --- a/packages/@aws-cdk/aws-codegurureviewer/.npmignore +++ b/packages/@aws-cdk/aws-codegurureviewer/.npmignore @@ -24,3 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codegurureviewer/package.json b/packages/@aws-cdk/aws-codegurureviewer/package.json index ff4a52bd233d8..ab40869eed2a8 100644 --- a/packages/@aws-cdk/aws-codegurureviewer/package.json +++ b/packages/@aws-cdk/aws-codegurureviewer/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeGuruReviewer", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.npmignore b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/.npmignore +++ b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 39874e001cb4b..6578110867b33 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -42,6 +42,19 @@ pipeline.addStage({ }); ``` +If you want to use existing role which can be used by on commit event rule. +You can specify the role object in eventRole property. + +```ts +const eventRole = iam.Role.fromRoleArn(this, 'Event-role', 'roleArn'); +const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: repo, + output: new codepipeline.Artifact(), + eventRole, +}); +``` + The CodeCommit source action emits variables: ```typescript diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 62e8b83ba3424..8abca963a1943 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -77,6 +77,14 @@ export interface CodeCommitSourceActionProps extends codepipeline.CommonAwsActio * The CodeCommit repository. */ readonly repository: codecommit.IRepository; + + /** + * Role to be used by on commit event rule. + * Used only when trigger value is CodeCommitTrigger.EVENTS. + * + * @default a new role will be created. + */ + readonly eventRole?: iam.IRole; } /** @@ -124,7 +132,9 @@ export class CodeCommitSourceAction extends Action { if (createEvent) { const eventId = this.generateEventId(stage); this.props.repository.onCommit(eventId, { - target: new targets.CodePipeline(stage.pipeline), + target: new targets.CodePipeline(stage.pipeline, { + eventRole: this.props.eventRole, + }), branches: [this.branch], }); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts index fda7c79dc1800..520af74ba8af2 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/test.codecommit-source-action.ts @@ -2,6 +2,7 @@ import { countResources, expect, haveResourceLike, not } from '@aws-cdk/assert'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codecommit from '@aws-cdk/aws-codecommit'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as iam from '@aws-cdk/aws-iam'; import { Stack, Lazy } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; @@ -267,6 +268,62 @@ export = { test.done(); }, + + 'uses the role when passed'(test: Test) { + const stack = new Stack(); + + const pipeline = new codepipeline.Pipeline(stack, 'P', { + pipelineName: 'MyPipeline', + }); + + const triggerEventTestRole = new iam.Role(stack, 'Trigger-test-role', { + assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), + }); + triggerEventTestRole.addToPolicy(new iam.PolicyStatement({ + actions: ['codepipeline:StartPipelineExecution'], + resources: [pipeline.pipelineArn], + })); + + const sourceOutput = new codepipeline.Artifact(); + + const sourceAction = new cpactions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: new codecommit.Repository(stack, 'R', { + repositoryName: 'repository', + }), + branch: Lazy.stringValue({ produce: () => 'my-branch' }), + output: sourceOutput, + eventRole: triggerEventTestRole, + }); + + pipeline.addStage({ + stageName: 'Source', + actions: [sourceAction], + }); + + const buildAction = new cpactions.CodeBuildAction({ + actionName: 'Build', + project: new codebuild.PipelineProject(stack, 'CodeBuild'), + input: sourceOutput, + }); + + pipeline.addStage({ + stageName: 'build', + actions: [buildAction], + }); + + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: stack.resolve(pipeline.pipelineArn), + Id: 'Target0', + RoleArn: stack.resolve(triggerEventTestRole.roleArn), + }, + ], + })); + + test.done(); + }, }, }; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json index 437b7116f5501..2e48bb1667495 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json @@ -1446,7 +1446,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1663,7 +1664,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json index 7c31408b07441..906a2ebb7ccbd 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json @@ -67,7 +67,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -87,6 +88,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinelambdapipeline87a4b3d3", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucket22248F97": { "Type": "AWS::S3::Bucket", "Properties": { @@ -115,20 +130,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinelambdapipeline87a4b3d3", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKey01D58D69", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -423,7 +424,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json index 8d9b8f4381236..262def042dc8a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json @@ -77,7 +77,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -97,6 +98,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinealexadeploypipeline961107f5", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucket22248F97": { "Type": "AWS::S3::Bucket", "Properties": { @@ -125,20 +140,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinealexadeploypipeline961107f5", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKey01D58D69", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -422,7 +423,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json index b43b9531301af..3571c79c9a81f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json @@ -67,7 +67,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -84,7 +85,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -120,6 +122,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucket22248F97": { "Type": "AWS::S3::Bucket", "Properties": { @@ -148,20 +164,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKey01D58D69", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -508,7 +510,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { @@ -630,7 +633,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json index 63f2734f64275..d6bc02e90525c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json @@ -82,7 +82,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -118,6 +119,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelines3deploypipeline907bf1e7", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "PipelineArtifactsBucket22248F97": { "Type": "AWS::S3::Bucket", "Properties": { @@ -146,20 +161,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelines3deploypipeline907bf1e7", - "TargetKeyId": { - "Fn::GetAtt": [ - "PipelineArtifactsBucketEncryptionKey01D58D69", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "PipelineRoleD68726F7": { "Type": "AWS::IAM::Role", "Properties": { @@ -461,7 +462,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json index ed6aaa8b6df4e..c56170c78f20d 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-stepfunctions.expected.json @@ -110,7 +110,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -130,6 +131,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", + "TargetKeyId": { + "Fn::GetAtt": [ + "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "MyPipelineArtifactsBucket727923DD": { "Type": "AWS::S3::Bucket", "Properties": { @@ -158,20 +173,6 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, - "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { - "Type": "AWS::KMS::Alias", - "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", - "TargetKeyId": { - "Fn::GetAtt": [ - "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "MyPipelineRoleC0D47CA4": { "Type": "AWS::IAM::Role", "Properties": { @@ -468,7 +469,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-codepipeline/.npmignore b/packages/@aws-cdk/aws-codepipeline/.npmignore index b70bd617fba2d..1b4217fb1a9f0 100644 --- a/packages/@aws-cdk/aws-codepipeline/.npmignore +++ b/packages/@aws-cdk/aws-codepipeline/.npmignore @@ -24,3 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 5e9054460f74d..88d5a0e340de3 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodePipeline", diff --git a/packages/@aws-cdk/aws-codestar/.npmignore b/packages/@aws-cdk/aws-codestar/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-codestar/.npmignore +++ b/packages/@aws-cdk/aws-codestar/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestar/package.json b/packages/@aws-cdk/aws-codestar/package.json index 5eeb0b43db158..b011eb00c9e60 100644 --- a/packages/@aws-cdk/aws-codestar/package.json +++ b/packages/@aws-cdk/aws-codestar/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeStar", diff --git a/packages/@aws-cdk/aws-codestarconnections/.npmignore b/packages/@aws-cdk/aws-codestarconnections/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-codestarconnections/.npmignore +++ b/packages/@aws-cdk/aws-codestarconnections/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarconnections/package.json b/packages/@aws-cdk/aws-codestarconnections/package.json index 76647d2d6375e..872b1eef71c7a 100644 --- a/packages/@aws-cdk/aws-codestarconnections/package.json +++ b/packages/@aws-cdk/aws-codestarconnections/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeStarConnections", diff --git a/packages/@aws-cdk/aws-codestarnotifications/.npmignore b/packages/@aws-cdk/aws-codestarnotifications/.npmignore index a7c5b49852b3b..c2827f80c26db 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/.npmignore +++ b/packages/@aws-cdk/aws-codestarnotifications/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index ffbefc15ca846..97cfb682202cc 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CodeStarNotifications", diff --git a/packages/@aws-cdk/aws-cognito/.npmignore b/packages/@aws-cdk/aws-cognito/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-cognito/.npmignore +++ b/packages/@aws-cdk/aws-cognito/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 41eb58d9b0a22..749ca525f048c 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -14,6 +14,7 @@ --- + [Amazon Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html) provides authentication, authorization, and user management for your web and mobile apps. Your users can sign in directly with a user name and password, or through a third party such as Facebook, Amazon, Google or Apple. @@ -319,6 +320,8 @@ Lambda triggers can either be specified as part of the `UserPool` initialization on the construct, as so - ```ts +import * as lambda from '@aws-cdk/aws-lambda'; + const authChallengeFn = new lambda.Function(this, 'authChallengeFn', { // ... }); @@ -371,6 +374,7 @@ The following third-party identity providers are currentlhy supported in the CDK * [Login With Amazon](https://developer.amazon.com/apps-and-games/login-with-amazon) * [Facebook Login](https://developers.facebook.com/docs/facebook-login/) +* [Google Login](https://developers.google.com/identity/sign-in/web/sign-in) The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the @@ -566,7 +570,7 @@ const signInUrl = domain.signInUrl(client, { }) ``` -Exisiting domains can be imported into CDK apps using `UserPoolDomain.fromDomainName()` API +Existing domains can be imported into CDK apps using `UserPoolDomain.fromDomainName()` API ```ts const stack = new Stack(app, 'my-stack'); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 0e59e5c2a6d2b..630000df485ba 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -154,6 +154,12 @@ export class UserPoolClientIdentityProvider { */ public static readonly FACEBOOK = new UserPoolClientIdentityProvider('Facebook'); + /** + * Allow users to sign in using 'Google Login'. + * A `UserPoolIdentityProviderGoogle` must be attached to the user pool. + */ + public static readonly GOOGLE = new UserPoolClientIdentityProvider('Google'); + /** * Allow users to sign in using 'Login With Amazon'. * A `UserPoolIdentityProviderAmazon` must be attached to the user pool. diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts index 6987050e65e82..e32f59eca2de5 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts @@ -36,6 +36,25 @@ export class ProviderAttribute { /** The locale attribute provided by Facebook */ public static readonly FACEBOOK_LOCALE = new ProviderAttribute('locale'); + /** The name attribute provided by Google */ + public static readonly GOOGLE_NAMES = new ProviderAttribute('names'); + /** The gender attribute provided by Google */ + public static readonly GOOGLE_GENDER = new ProviderAttribute('gender'); + /** The birthday attribute provided by Google */ + public static readonly GOOGLE_BIRTHDAYS = new ProviderAttribute('birthdays'); + /** The birthday attribute provided by Google */ + public static readonly GOOGLE_PHONE_NUMBERS = new ProviderAttribute('phoneNumbers'); + /** The email attribute provided by Google */ + public static readonly GOOGLE_EMAIL = new ProviderAttribute('email'); + /** The name attribute provided by Google */ + public static readonly GOOGLE_NAME = new ProviderAttribute('name'); + /** The email attribute provided by Google */ + public static readonly GOOGLE_PICTURE = new ProviderAttribute('picture'); + /** The email attribute provided by Google */ + public static readonly GOOGLE_GIVEN_NAME = new ProviderAttribute('given_name'); + /** The email attribute provided by Google */ + public static readonly GOOGLE_FAMILY_NAME = new ProviderAttribute('family_name'); + /** * Use this to specify an attribute from the identity provider that is not pre-defined in the CDK. * @param attributeName the attribute value string as recognized by the provider diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/google.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/google.ts new file mode 100644 index 0000000000000..12275646691e8 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/google.ts @@ -0,0 +1,53 @@ +import { Construct } from 'constructs'; +import { CfnUserPoolIdentityProvider } from '../cognito.generated'; +import { UserPoolIdentityProviderBase, UserPoolIdentityProviderProps } from './base'; + +/** + * Properties to initialize UserPoolGoogleIdentityProvider + */ +export interface UserPoolIdentityProviderGoogleProps extends UserPoolIdentityProviderProps { + /** + * The client id recognized by Google APIs. + * @see https://developers.google.com/identity/sign-in/web/sign-in#specify_your_apps_client_id + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientId for Google APIs to authenticate the client. + * @see https://developers.google.com/identity/sign-in/web/sign-in + */ + readonly clientSecret: string; + /** + * The list of google permissions to obtain for getting access to the google profile + * @see https://developers.google.com/identity/sign-in/web/sign-in + * @default [ profile ] + */ + readonly scopes?: string[]; +} + +/** + * Represents a identity provider that integrates with 'Google' + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolIdentityProviderGoogle extends UserPoolIdentityProviderBase { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderGoogleProps) { + super(scope, id, props); + + const scopes = props.scopes ?? ['profile']; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: 'Google', // must be 'Google' when the type is 'Google' + providerType: 'Google', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(' '), + }, + attributeMapping: super.configureAttributeMapping(), + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts index e0efb718962c4..dbc63a9854f37 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts @@ -1,3 +1,4 @@ export * from './base'; export * from './amazon'; -export * from './facebook'; \ No newline at end of file +export * from './facebook'; +export * from './google'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 8a9dd34fb536f..c54873f73863e 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Cognito", @@ -76,7 +77,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, @@ -106,7 +107,8 @@ "resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret", "props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderFacebookProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json index 5e2c8662f3f60..ed77c3b7baa53 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.expected.json @@ -680,10 +680,16 @@ "myuserpool01998219": { "Type": "AWS::Cognito::UserPool", "Properties": { - "AccountRecoverySetting": { + "AccountRecoverySetting": { "RecoveryMechanisms": [ - { "Name": "verified_phone_number", "Priority": 1 }, - { "Name": "verified_email", "Priority": 2 } + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } ] }, "AdminCreateUserConfig": { @@ -701,16 +707,8 @@ "email", "phone_number" ], - "EmailConfiguration": { - "From": "noreply@myawesomeapp.com", - "ReplyToEmailAddress": "support@myawesomeapp.com" - }, "EmailVerificationMessage": "verification email body from the integ test. Code is {####}.", "EmailVerificationSubject": "verification email subject from the integ test", - "EnabledMfas": [ - "SMS_MFA", - "SOFTWARE_TOKEN_MFA" - ], "LambdaConfig": { "CreateAuthChallenge": { "Fn::GetAtt": [ @@ -773,7 +771,7 @@ ] } }, - "MfaConfiguration": "ON", + "MfaConfiguration": "OFF", "Policies": { "PasswordPolicy": { "MinimumLength": 12, @@ -786,14 +784,14 @@ }, "Schema": [ { + "Mutable": true, "Name": "name", - "Required": true, - "Mutable": true + "Required": true }, { + "Mutable": true, "Name": "email", - "Required": true, - "Mutable": true + "Required": true }, { "AttributeDataType": "String", @@ -881,4 +879,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts index 1f4f7fe8193c5..261251e1e2592 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-explicit-props.ts @@ -43,7 +43,7 @@ const userpool = new UserPool(stack, 'myuserpool', { 'some-boolean-attr': new BooleanAttribute(), 'some-datetime-attr': new DateTimeAttribute(), }, - mfa: Mfa.REQUIRED, + mfa: Mfa.OFF, mfaSecondFactor: { sms: true, otp: true, @@ -56,10 +56,6 @@ const userpool = new UserPool(stack, 'myuserpool', { requireUppercase: true, requireSymbols: true, }, - emailSettings: { - from: 'noreply@myawesomeapp.com', - replyTo: 'support@myawesomeapp.com', - }, lambdaTriggers: { createAuthChallenge: dummyTrigger('createAuthChallenge'), customMessage: dummyTrigger('customMessage'), diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.amazon.expected.json similarity index 100% rename from packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json rename to packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.amazon.expected.json diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.amazon.ts similarity index 94% rename from packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts rename to packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.amazon.ts index 31804f1ce95e8..7252dfc0a8a40 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.amazon.ts @@ -7,7 +7,7 @@ import { ProviderAttribute, UserPool, UserPoolIdentityProviderAmazon } from '../ * * If you plug in valid 'Login with Amazon' credentials, the federated log in should work. */ const app = new App(); -const stack = new Stack(app, 'integ-user-pool-idp'); +const stack = new Stack(app, 'integ-user-pool-idp-amazon'); const userpool = new UserPool(stack, 'pool'); diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.expected.json new file mode 100644 index 0000000000000..c767ee9d2d260 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.expected.json @@ -0,0 +1,117 @@ +{ + "Resources": { + "pool056F3F7E": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + } + }, + "poolclient2623294C": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + { + "Ref": "googleDB2C5242" + }, + "COGNITO" + ] + } + }, + "pooldomain430FA744": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "nija-test-pool", + "UserPoolId": { + "Ref": "pool056F3F7E" + } + } + }, + "googleDB2C5242": { + "Type": "AWS::Cognito::UserPoolIdentityProvider", + "Properties": { + "ProviderName": "Google", + "ProviderType": "Google", + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "AttributeMapping": { + "given_name": "given_name", + "family_name": "family_name", + "email": "email", + "gender": "gender", + "names": "names" + }, + "ProviderDetails": { + "client_id": "google-client-id", + "client_secret": "google-client-secret", + "authorize_scopes": "profile" + } + } + } + }, + "Outputs": { + "SignInLink": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "pooldomain430FA744" + }, + ".auth.", + { + "Ref": "AWS::Region" + }, + ".amazoncognito.com/login?client_id=", + { + "Ref": "poolclient2623294C" + }, + "&response_type=code&redirect_uri=https://example.com" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.ts new file mode 100644 index 0000000000000..fac20b8351d38 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.google.ts @@ -0,0 +1,41 @@ +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderGoogle } from '../lib'; + +/* + * Stack verification steps + * * Visit the URL provided by stack output 'SignInLink' in a browser, and verify the 'Google' sign in link shows up. + * * If you plug in valid 'Google' credentials, the federated log in should work. + */ +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-idp-google'); + +const userpool = new UserPool(stack, 'pool'); + +new UserPoolIdentityProviderGoogle(stack, 'google', { + userPool: userpool, + clientId: 'google-client-id', + clientSecret: 'google-client-secret', + attributeMapping: { + givenName: ProviderAttribute.GOOGLE_GIVEN_NAME, + familyName: ProviderAttribute.GOOGLE_FAMILY_NAME, + email: ProviderAttribute.GOOGLE_EMAIL, + gender: ProviderAttribute.GOOGLE_GENDER, + custom: { + names: ProviderAttribute.GOOGLE_NAMES, + }, + }, +}); + +const client = userpool.addClient('client'); + +const domain = userpool.addDomain('domain', { + cognitoDomain: { + domainPrefix: 'nija-test-pool', + }, +}); + +new CfnOutput(stack, 'SignInLink', { + value: domain.signInUrl(client, { + redirectUri: 'https://example.com', + }), +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 081d9d9e6526b..eeddb55bc1ff1 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -451,13 +451,14 @@ describe('User Pool Client', () => { UserPoolClientIdentityProvider.COGNITO, UserPoolClientIdentityProvider.FACEBOOK, UserPoolClientIdentityProvider.AMAZON, + UserPoolClientIdentityProvider.GOOGLE, ], }); // THEN expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { ClientName: 'AllEnabled', - SupportedIdentityProviders: ['COGNITO', 'Facebook', 'LoginWithAmazon'], + SupportedIdentityProviders: ['COGNITO', 'Facebook', 'LoginWithAmazon', 'Google'], }); }); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/google.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/google.test.ts new file mode 100644 index 0000000000000..41700abe1c92d --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/google.test.ts @@ -0,0 +1,101 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderGoogle } from '../../lib'; + +describe('UserPoolIdentityProvider', () => { + describe('google', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderGoogle(stack, 'userpoolidp', { + userPool: pool, + clientId: 'google-client-id', + clientSecret: 'google-client-secret', + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'Google', + ProviderType: 'Google', + ProviderDetails: { + client_id: 'google-client-id', + client_secret: 'google-client-secret', + authorize_scopes: 'profile', + }, + }); + }); + + test('scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderGoogle(stack, 'userpoolidp', { + userPool: pool, + clientId: 'google-client-id', + clientSecret: 'google-client-secret', + scopes: ['scope1', 'scope2'], + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'Google', + ProviderType: 'Google', + ProviderDetails: { + client_id: 'google-client-id', + client_secret: 'google-client-secret', + authorize_scopes: 'scope1 scope2', + }, + }); + }); + + test('registered with user pool', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + const provider = new UserPoolIdentityProviderGoogle(stack, 'userpoolidp', { + userPool: pool, + clientId: 'google-client-id', + clientSecret: 'google-client-secret', + }); + + // THEN + expect(pool.identityProviders).toContain(provider); + }); + + test('attribute mapping', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderGoogle(stack, 'userpoolidp', { + userPool: pool, + clientId: 'google-client-id', + clientSecret: 'google-client-secret', + attributeMapping: { + givenName: ProviderAttribute.GOOGLE_NAME, + address: ProviderAttribute.other('google-address'), + custom: { + customAttr1: ProviderAttribute.GOOGLE_EMAIL, + customAttr2: ProviderAttribute.other('google-custom-attr'), + }, + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + AttributeMapping: { + given_name: 'name', + address: 'google-address', + customAttr1: 'email', + customAttr2: 'google-custom-attr', + }, + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/.npmignore b/packages/@aws-cdk/aws-config/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-config/.npmignore +++ b/packages/@aws-cdk/aws-config/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/README.md b/packages/@aws-cdk/aws-config/README.md index c4dfa580b49e3..50aec9900ce8a 100644 --- a/packages/@aws-cdk/aws-config/README.md +++ b/packages/@aws-cdk/aws-config/README.md @@ -5,16 +5,20 @@ | Features | Stability | | --- | --- | | CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | -| Higher level constructs for Config Rules | ![Developer Preview](https://img.shields.io/badge/developer--preview-informational.svg?style=for-the-badge) | +| Higher level constructs for Config Rules | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | | Higher level constructs for initial set-up (delivery channel & configuration recorder) | ![Not Implemented](https://img.shields.io/badge/not--implemented-black.svg?style=for-the-badge) | > **CFN Resources:** All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. -> **Developer Preview:** Higher level constructs in this module that are marked as developer preview have completed their phase of active development and are looking for adoption and feedback. While the same caveats around non-backward compatible as Experimental constructs apply, they will undergo fewer breaking changes. Just as with Experimental constructs, these are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. +> **Stable:** Higher level constructs in this module that are marked stable will not undergo any breaking changes. They will strictly follow the [Semantic Versioning](https://semver.org/) model. --- +[AWS Config](https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html) provides a detailed view of the configuration of AWS resources in your AWS account. +This includes how the resources are related to one another and how they were configured in the +past so that you can see how the configurations and relationships change over time. + This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ### Initial Setup @@ -26,107 +30,222 @@ following resources per region: - `ConfigurationRecorder`: Configure which resources will be recorded for config changes. - `DeliveryChannel`: Configure where to store the recorded data. -Following are the guides to setup AWS Config: +The following guides provide the steps for getting started with AWS Config: - [Using the AWS Console](https://docs.aws.amazon.com/config/latest/developerguide/gs-console.html) - [Using the AWS CLI](https://docs.aws.amazon.com/config/latest/developerguide/gs-cli.html) ### Rules -#### AWS managed rules +AWS Config can evaluate the configuration settings of your AWS resources by creating AWS Config rules, +which represent your ideal configuration settings. + +See [Evaluating Resources with AWS Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html) to learn more about AWS Config rules. + +#### AWS Managed Rules + +AWS Config provides AWS managed rules, which are predefined, customizable rules that AWS Config +uses to evaluate whether your AWS resources comply with common best practices. + +For example, you could create a managed rule that checks whether active access keys are rotated +within the number of days specified. + +```ts +import * as config from '@aws-cdk/aws-config'; +import * as cdk from '@aws-cdk/core'; + +// https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html +new config.ManagedRule(this, 'AccessKeysRotated', { + identifier: config.ManagedRuleIdentifiers.ACCESS_KEYS_ROTATED, + inputParameters: { + maxAccessKeyAge: 60 // default is 90 days + }, + maximumExecutionFrequency: config.MaximumExecutionFrequency.TWELVE_HOURS // default is 24 hours +}); +``` + +Identifiers for AWS managed rules are available through static constants in the `ManagedRuleIdentifiers` class. +You can find supported input parameters in the [List of AWS Config Managed Rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html). + +The following higher level constructs for AWS managed rules are available. + +##### Access Key rotation + +Checks whether your active access keys are rotated within the number of days specified. -To set up a managed rule, define a `ManagedRule` and specify its identifier: +```ts +import * as config from '@aws-cdk/aws-config'; +import * as cdk from '@aws-cdk/aws-cdk'; + +// compliant if access keys have been rotated within the last 90 days +new config.AccessKeysRotated(this, 'AccessKeyRotated'); +``` + +##### CloudFormation Stack drift detection + +Checks whether your CloudFormation stack's actual configuration differs, or has drifted, +from it's expected configuration. ```ts -new ManagedRule(this, 'AccessKeysRotated', { - identifier: 'ACCESS_KEYS_ROTATED' +import * as config from '@aws-cdk/aws-config'; +import * as cdk from '@aws-cdk/aws-cdk'; + +// compliant if stack's status is 'IN_SYNC' +// non-compliant if the stack's drift status is 'DRIFTED' +new config.CloudFormationStackDriftDetectionCheck(stack, 'Drift', { + ownStackOnly: true, // checks only the stack containing the rule }); ``` -Available identifiers and parameters are listed in the [List of AWS Config Managed Rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html). +##### CloudFormation Stack notifications + +Checks whether your CloudFormation stacks are sending event notifications to a SNS topic. +```ts +import * as config from '@aws-cdk/aws-config'; +import * as cdk from '@aws-cdk/aws-cdk'; -Higher level constructs for managed rules are available, see [Managed Rules](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-config/lib/managed-rules.ts). Prefer to use those constructs when available (PRs welcome to add more of those). +// topics to which CloudFormation stacks may send event notifications +const topic1 = new sns.Topic(stack, 'AllowedTopic1'); +const topic2 = new sns.Topic(stack, 'AllowedTopic2'); + +// non-compliant if CloudFormation stack does not send notifications to 'topic1' or 'topic2' +new config.CloudFormationStackNotificationCheck(this, 'NotificationCheck', { + topics: [topic1, topic2], +}) +``` #### Custom rules -To set up a custom rule, define a `CustomRule` and specify the Lambda Function to run and the trigger types: +You can develop custom rules and add them to AWS Config. You associate each custom rule with an +AWS Lambda function, which contains the logic that evaluates whether your AWS resources comply +with the rule. + +#### Triggers + +AWS Lambda executes functions in response to events that are published by AWS Services. +The function for a custom Config rule receives an event that is published by AWS Config, +and is responsible for evaluating the compliance of the rule. + +Evaluations can be triggered by configuration changes, periodically, or both. +To create a custom rule, define a `CustomRule` and specify the Lambda Function +to run and the trigger types. ```ts -new CustomRule(this, 'CustomRule', { - lambdaFunction: myFn, +import * as config from '@aws-cdk/aws-config'; + +new config.CustomRule(this, 'CustomRule', { + lambdaFunction: evalComplianceFn, configurationChanges: true, - periodic: true + periodic: true, + maximumExecutionFrequency: config.MaximumExecutionFrequency.SIX_HOURS, // default is 24 hours }); ``` -#### Restricting the scope +When the trigger for a rule occurs, the Lambda function is invoked by publishing an event. +See [example events for AWS Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_example-events.html) + +The AWS documentation has examples of Lambda functions for evaluations that are +[triggered by configuration changes](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs-sample.html#event-based-example-rule) and [triggered periodically](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs-sample.html#periodic-example-rule) + + +#### Scope By default rules are triggered by changes to all [resources](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources). -Use the `scopeToResource()`, `scopeToResources()` or `scopeToTag()` APIs to restrict +Use the `RuleScope` APIs (`fromResource()`, `fromResources()` or `fromTag()`) to restrict the scope of both managed and custom rules: ```ts -const sshRule = new ManagedRule(this, 'SSH', { - identifier: 'INCOMING_SSH_DISABLED' -}); +import * as config from '@aws-cdk/aws-config'; -// Restrict to a specific security group -rule.scopeToResource('AWS::EC2::SecurityGroup', 'sg-1234567890abcdefgh'); +const sshRule = new config.ManagedRule(this, 'SSH', { + identifier: config.ManagedRuleIdentifiers.EC2_SECURITY_GROUPS_INCOMING_SSH_DISABLED, + ruleScope: config.RuleScope.fromResource(config.ResourceType.EC2_SECURITY_GROUP, 'sg-1234567890abcdefgh'), // restrict to specific security group +}); -const customRule = new CustomRule(this, 'CustomRule', { - lambdaFunction: myFn, +const customRule = new config.CustomRule(this, 'Lambda', { + lambdaFunction: evalComplianceFn, configurationChanges: true + ruleScope: config.RuleScope.fromResources([config.ResourceType.CLOUDFORMATION_STACK, config.ResourceType.S3_BUCKET]), // restrict to all CloudFormation stacks and S3 buckets }); -// Restrict to a specific tag -customRule.scopeToTag('Cost Center', 'MyApp'); +const tagRule = new config.CustomRule(this, 'CostCenterTagRule', { + lambdaFunction: evalComplianceFn, + configurationChanges: true + ruleScope: config.RuleScope.fromTag('Cost Center', 'MyApp'), // restrict to a specific tag +}); ``` -Only one type of scope restriction can be added to a rule (the last call to `scopeToXxx()` sets the scope). - #### Events -To define Amazon CloudWatch event rules, use the `onComplianceChange()` or `onReEvaluationStatus()` methods: +You can define Amazon EventBridge event rules which trigger when a compliance check fails +or when a rule is re-evaluated. + +Use the `onComplianceChange()` APIs to trigger an EventBridge event when a compliance check +of your AWS Config Rule fails: ```ts -const rule = new CloudFormationStackDriftDetectionCheck(this, 'Drift'); +import * as config from '@aws-cdk/aws-config'; +import * as sns from '@aws-cdk/aws-sns'; +import * as targets from '@aws-cdk/aws-events-targets'; + +// Topic to which compliance notification events will be published +const complianceTopic = new sns.Topic(this, 'ComplianceTopic'); + +const rule = new config.CloudFormationStackDriftDetectionCheck(this, 'Drift'); rule.onComplianceChange('TopicEvent', { - target: new targets.SnsTopic(topic)) + target: new targets.SnsTopic(complianceTopic), }); ``` +Use the `onReEvaluationStatus()` status to trigger an EventBridge event when an AWS Config +rule is re-evaluated. + +```ts +import * as config from '@aws-cdk/aws-config'; +import * as sns from '@aws-cdk/aws-sns'; +import * as targets from '@aws-cdk/aws-events-targets'; + +// Topic to which re-evaluation notification events will be published +const reEvaluationTopic = new sns.Topic(this, 'ComplianceTopic'); +rule.onReEvaluationStatus('ReEvaluationEvent', { + target: new targets.SnsTopic(reEvaluationTopic), +}) +``` + #### Example -The following example creates a custom rule that runs on configuration changes to EC2 instances and publishes -compliance events to an SNS topic. +The following example creates a custom rule that evaluates whether EC2 instances are compliant. +Compliance events are published to an SNS topic. ```ts import * as config from '@aws-cdk/aws-config'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as sns from '@aws-cdk/aws-sns'; +import * as targets from '@aws-cdk/aws-events-targets'; -// A custom rule that runs on configuration changes of EC2 instances -const fn = new lambda.Function(this, 'CustomFunction', { +// Lambda function containing logic that evaluates compliance with the rule. +const evalComplianceFn = new lambda.Function(this, 'CustomFunction', { code: lambda.AssetCode.fromInline('exports.handler = (event) => console.log(event);'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, }); +// A custom rule that runs on configuration changes of EC2 instances const customRule = new config.CustomRule(this, 'Custom', { configurationChanges: true, - lambdaFunction: fn, + lambdaFunction: evalComplianceFn, + ruleScope: config.RuleScope.fromResource([config.ResourceType.EC2_INSTANCE]), }); -customRule.scopeToResource('AWS::EC2::Instance'); - // A rule to detect stack drifts const driftRule = new config.CloudFormationStackDriftDetectionCheck(this, 'Drift'); // Topic to which compliance notification events will be published const complianceTopic = new sns.Topic(this, 'ComplianceTopic'); -// Send notification on compliance change +// Send notification on compliance change events driftRule.onComplianceChange('ComplianceChange', { target: new targets.SnsTopic(complianceTopic), }); diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index 69ddd7463aac5..cfbe2645367c6 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import { Duration, Lazy, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { ManagedRule, RuleProps } from './rule'; +import { ManagedRule, ManagedRuleIdentifiers, ResourceType, RuleProps, RuleScope } from './rule'; /** * Construction properties for a AccessKeysRotated @@ -28,7 +28,7 @@ export class AccessKeysRotated extends ManagedRule { constructor(scope: Construct, id: string, props: AccessKeysRotatedProps = {}) { super(scope, id, { ...props, - identifier: 'ACCESS_KEYS_ROTATED', + identifier: ManagedRuleIdentifiers.ACCESS_KEYS_ROTATED, inputParameters: { ...props.maxAge ? { @@ -76,13 +76,13 @@ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { constructor(scope: Construct, id: string, props: CloudFormationStackDriftDetectionCheckProps = {}) { super(scope, id, { ...props, - identifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK', + identifier: ManagedRuleIdentifiers.CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK, inputParameters: { cloudformationRoleArn: Lazy.stringValue({ produce: () => this.role.roleArn }), }, }); - this.scopeToResource('AWS::CloudFormation::Stack', props.ownStackOnly ? Stack.of(this).stackId : undefined); + this.ruleScope = RuleScope.fromResource( ResourceType.CLOUDFORMATION_STACK, props.ownStackOnly ? Stack.of(this).stackId : undefined ); this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('config.amazonaws.com'), @@ -121,13 +121,12 @@ export class CloudFormationStackNotificationCheck extends ManagedRule { super(scope, id, { ...props, - identifier: 'CLOUDFORMATION_STACK_NOTIFICATION_CHECK', + identifier: ManagedRuleIdentifiers.CLOUDFORMATION_STACK_NOTIFICATION_CHECK, inputParameters: props.topics && props.topics.reduce( (params, topic, idx) => ({ ...params, [`snsTopic${idx + 1}`]: topic.topicArn }), {}, ), + ruleScope: RuleScope.fromResources([ResourceType.CLOUDFORMATION_STACK]), }); - - this.scopeToResource('AWS::CloudFormation::Stack'); } } diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 9984a22fdac61..ac4044ac4ae99 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -110,49 +110,45 @@ abstract class RuleNew extends RuleBase { */ public abstract readonly configRuleComplianceType: string; - protected scope?: CfnConfigRule.ScopeProperty; + protected ruleScope?: RuleScope; protected isManaged?: boolean; protected isCustomWithChanges?: boolean; +} - /** - * Restrict scope of changes to a specific resource. - * - * @see https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources - * - * @param type the resource type - * @param identifier the resource identifier - */ - public scopeToResource(type: string, identifier?: string) { - this.scope = { - complianceResourceId: identifier, - complianceResourceTypes: [type], - }; +/** + * Determines which resources trigger an evaluation of an AWS Config rule. + */ +export class RuleScope { + /** restricts scope of changes to a specific resource type or resource identifier */ + public static fromResource(resourceType: ResourceType, resourceId?: string) { + return new RuleScope(resourceId, [resourceType]); } - - /** - * Restrict scope of changes to specific resource types. - * - * @see https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources - * - * @param types resource types - */ - public scopeToResources(...types: string[]) { - this.scope = { - complianceResourceTypes: types, - }; + /** restricts scope of changes to specific resource types */ + public static fromResources(resourceTypes: ResourceType[]) { + return new RuleScope(undefined, resourceTypes); + } + /** restricts scope of changes to a specific tag */ + public static fromTag(key: string, value?: string) { + return new RuleScope(undefined, undefined, key, value); } - /** - * Restrict scope of changes to a specific tag. - * - * @param key the tag key - * @param value the tag value - */ - public scopeToTag(key: string, value?: string) { - this.scope = { - tagKey: key, - tagValue: value, - }; + /** Resource types that will trigger evaluation of a rule */ + public readonly resourceTypes?: ResourceType[]; + + /** ID of the only AWS resource that will trigger evaluation of a rule */ + public readonly resourceId?: string; + + /** tag key applied to resources that will trigger evaluation of a rule */ + public readonly key?: string; + + /** tag value applied to resources that will trigger evaluation of a rule */ + public readonly value?: string; + + private constructor(resourceId?: string, resourceTypes?: ResourceType[], tagKey?: string, tagValue?: string) { + this.resourceTypes = resourceTypes; + this.resourceId = resourceId; + this.key = tagKey; + this.value = tagValue; } } @@ -217,7 +213,14 @@ export interface RuleProps { * * @default MaximumExecutionFrequency.TWENTY_FOUR_HOURS */ - readonly maximumExecutionFrequency?: MaximumExecutionFrequency + readonly maximumExecutionFrequency?: MaximumExecutionFrequency; + + /** + * Defines which resources trigger an evaluation for an AWS Config rule. + * + * @default - evaluations for the rule are triggered when any resource in the recording group changes. + */ + readonly ruleScope?: RuleScope; } /** @@ -255,12 +258,14 @@ export class ManagedRule extends RuleNew { physicalName: props.configRuleName, }); + this.ruleScope = props.ruleScope; + const rule = new CfnConfigRule(this, 'Resource', { configRuleName: this.physicalName, description: props.description, inputParameters: props.inputParameters, maximumExecutionFrequency: props.maximumExecutionFrequency, - scope: Lazy.anyValue({ produce: () => this.scope }), + scope: Lazy.anyValue({ produce: () => renderScope(this.ruleScope) }), // scope can use values such as stack id (see CloudFormationStackDriftDetectionCheck) source: { owner: 'AWS', sourceIdentifier: props.identifier, @@ -327,6 +332,7 @@ export class CustomRule extends RuleNew { } const sourceDetails: any[] = []; + this.ruleScope = props.ruleScope; if (props.configurationChanges) { sourceDetails.push({ @@ -365,7 +371,7 @@ export class CustomRule extends RuleNew { description: props.description, inputParameters: props.inputParameters, maximumExecutionFrequency: props.maximumExecutionFrequency, - scope: Lazy.anyValue({ produce: () => this.scope }), + scope: Lazy.anyValue({ produce: () => renderScope(this.ruleScope) }), // scope can use values such as stack id (see CloudFormationStackDriftDetectionCheck) source: { owner: 'CUSTOM_LAMBDA', sourceDetails, @@ -383,3 +389,1085 @@ export class CustomRule extends RuleNew { } } } + +/** + * Managed rules that are supported by AWS Config. + * @see https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html + */ +export class ManagedRuleIdentifiers { + /** + * Checks that the inline policies attached to your AWS Identity and Access Management users, + * roles, and groups do not allow blocked actions on all AWS Key Management Service keys. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-inline-policy-blocked-kms-actions.html + */ + public static readonly IAM_INLINE_POLICY_BLOCKED_KMS_ACTIONS = 'IAM_INLINE_POLICY_BLOCKED_KMS_ACTIONS'; + /** + * Checks that the managed AWS Identity and Access Management policies that you create do not + * allow blocked actions on all AWS AWS KMS keys. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-customer-policy-blocked-kms-actions.html + */ + public static readonly IAM_CUSTOMER_POLICY_BLOCKED_KMS_ACTIONS = 'IAM_CUSTOMER_POLICY_BLOCKED_KMS_ACTIONS'; + /** + * Checks whether the active access keys are rotated within the number of days specified in maxAccessKeyAge. + * @see https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html + */ + public static readonly ACCESS_KEYS_ROTATED = 'ACCESS_KEYS_ROTATED'; + /** + * Checks whether AWS account is part of AWS Organizations. + * @see https://docs.aws.amazon.com/config/latest/developerguide/account-part-of-organizations.html + */ + public static readonly ACCOUNT_PART_OF_ORGANIZATIONS = 'ACCOUNT_PART_OF_ORGANIZATIONS'; + /** + * Checks whether ACM Certificates in your account are marked for expiration within the specified number of days. + * @see https://docs.aws.amazon.com/config/latest/developerguide/acm-certificate-expiration-check.html + */ + public static readonly ACM_CERTIFICATE_EXPIRATION_CHECK = 'ACM_CERTIFICATE_EXPIRATION_CHECK'; + /** + * Checks if rule evaluates Application Load Balancers (ALBs) to ensure they are configured to drop http headers. + * @see https://docs.aws.amazon.com/config/latest/developerguide/alb-http-drop-invalid-header-enabled.html + */ + public static readonly ALB_HTTP_DROP_INVALID_HEADER_ENABLED = 'ALB_HTTP_DROP_INVALID_HEADER_ENABLED'; + /** + * Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancer. + * @see https://docs.aws.amazon.com/config/latest/developerguide/alb-http-to-https-redirection-check.html + */ + public static readonly ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK = 'ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK'; + /** + * Checks if Web Application Firewall (WAF) is enabled on Application Load Balancers (ALBs). + * @see https://docs.aws.amazon.com/config/latest/developerguide/alb-waf-enabled.html + */ + public static readonly ALB_WAF_ENABLED = 'ALB_WAF_ENABLED'; + /** + * Checks that all methods in Amazon API Gateway stages have caching enabled and encrypted. + * @see https://docs.aws.amazon.com/config/latest/developerguide/api-gw-cache-enabled-and-encrypted.html + */ + public static readonly API_GW_CACHE_ENABLED_AND_ENCRYPTED = 'API_GW_CACHE_ENABLED_AND_ENCRYPTED'; + /** + * Checks that Amazon API Gateway APIs are of the type specified in the rule parameter endpointConfigurationType. + * @see https://docs.aws.amazon.com/config/latest/developerguide/api-gw-endpoint-type-check.html + */ + public static readonly API_GW_ENDPOINT_TYPE_CHECK = 'API_GW_ENDPOINT_TYPE_CHECK'; + /** + * Checks that all methods in Amazon API Gateway stage has logging enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html + */ + public static readonly API_GW_EXECUTION_LOGGING_ENABLED = 'API_GW_EXECUTION_LOGGING_ENABLED'; + /** + * Checks whether running instances are using specified AMIs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/approved-amis-by-id.html + */ + public static readonly APPROVED_AMIS_BY_ID = 'APPROVED_AMIS_BY_ID'; + /** + * Checks whether running instances are using specified AMIs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/approved-amis-by-tag.html + */ + public static readonly APPROVED_AMIS_BY_TAG = 'APPROVED_AMIS_BY_TAG'; + /** + * Checks whether your Auto Scaling groups that are associated with a load balancer are using + * Elastic Load Balancing health checks. + * @see https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html + */ + public static readonly AUTOSCALING_GROUP_ELB_HEALTHCHECK_REQUIRED = 'AUTOSCALING_GROUP_ELB_HEALTHCHECK_REQUIRED'; + /** + * Checks whether an AWS CloudFormation stack's actual configuration differs, or has drifted, + * from it's expected configuration. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-drift-detection-check.html + */ + public static readonly CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK = 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK'; + /** + * Checks whether your CloudFormation stacks are sending event notifications to an SNS topic. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-notification-check.html + */ + public static readonly CLOUDFORMATION_STACK_NOTIFICATION_CHECK = 'CLOUDFORMATION_STACK_NOTIFICATION_CHECK'; + /** + * Checks if an Amazon CloudFront distribution is configured to return a specific object that is the default root object. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudfront-default-root-object-configured.html + */ + public static readonly CLOUDFRONT_DEFAULT_ROOT_OBJECT_CONFIGURED = 'CLOUDFRONT_DEFAULT_ROOT_OBJECT_CONFIGURED'; + /** + * Checks that Amazon CloudFront distribution with Amazon S3 Origin type has Origin Access Identity (OAI) configured. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudfront-origin-access-identity-enabled.html + */ + public static readonly CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ENABLED = 'CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ENABLED'; + /** Checks whether an origin group is configured for the distribution of at least 2 origins in the + * origin group for Amazon CloudFront. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudfront-origin-failover-enabled.html + */ + public static readonly CLOUDFRONT_ORIGIN_FAILOVER_ENABLED = 'CLOUDFRONT_ORIGIN_FAILOVER_ENABLED'; + /** + * Checks if Amazon CloudFront distributions are using a custom SSL certificate and are configured + * to use SNI to serve HTTPS requests. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudfront-sni-enabled.html + */ + public static readonly CLOUDFRONT_SNI_ENABLED = 'CLOUDFRONT_SNI_ENABLED'; + /** Checks whether your Amazon CloudFront distributions use HTTPS (directly or via a redirection). + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudfront-viewer-policy-https.html + */ + public static readonly CLOUDFRONT_VIEWER_POLICY_HTTPS = 'CLOUDFRONT_VIEWER_POLICY_HTTPS'; + /** + * Checks whether AWS CloudTrail trails are configured to send logs to Amazon CloudWatch Logs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-cloud-watch-logs-enabled.html + */ + public static readonly CLOUD_TRAIL_CLOUD_WATCH_LOGS_ENABLED = 'CLOUD_TRAIL_CLOUD_WATCH_LOGS_ENABLED'; + /** + * Checks whether AWS CloudTrail is enabled in your AWS account. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudtrail-enabled.html + */ + public static readonly CLOUD_TRAIL_ENABLED = 'CLOUD_TRAIL_ENABLED'; + /** + * Checks whether AWS CloudTrail is configured to use the server side encryption (SSE) + * AWS Key Management Service (AWS KMS) customer master key (CMK) encryption. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-encryption-enabled.html + */ + public static readonly CLOUD_TRAIL_ENCRYPTION_ENABLED = 'CLOUD_TRAIL_ENCRYPTION_ENABLED'; + /** + * Checks whether AWS CloudTrail creates a signed digest file with logs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-log-file-validation-enabled.html + */ + public static readonly CLOUD_TRAIL_LOG_FILE_VALIDATION_ENABLED = 'CLOUD_TRAIL_LOG_FILE_VALIDATION_ENABLED'; + /** + * Checks whether at least one AWS CloudTrail trail is logging Amazon S3 data events for all S3 buckets. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudtrail-s3-dataevents-enabled.html + */ + public static readonly CLOUDTRAIL_S3_DATAEVENTS_ENABLED = 'CLOUDTRAIL_S3_DATAEVENTS_ENABLED'; + /** + * Checks that there is at least one AWS CloudTrail trail defined with security best practices. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudtrail-security-trail-enabled.html + */ + public static readonly CLOUDTRAIL_SECURITY_TRAIL_ENABLED = 'CLOUDTRAIL_SECURITY_TRAIL_ENABLED'; + /** + * Checks whether CloudWatch alarms have at least one alarm action, one INSUFFICIENT_DATA action, + * or one OK action enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudwatch-alarm-action-check.html + */ + public static readonly CLOUDWATCH_ALARM_ACTION_CHECK = 'CLOUDWATCH_ALARM_ACTION_CHECK'; + /** + * Checks whether the specified resource type has a CloudWatch alarm for the specified metric. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudwatch-alarm-resource-check.html + */ + public static readonly CLOUDWATCH_ALARM_RESOURCE_CHECK = 'CLOUDWATCH_ALARM_RESOURCE_CHECK'; + /** + * Checks whether CloudWatch alarms with the given metric name have the specified settings. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudwatch-alarm-settings-check.html + */ + public static readonly CLOUDWATCH_ALARM_SETTINGS_CHECK = 'CLOUDWATCH_ALARM_SETTINGS_CHECK'; + /** + * Checks whether a log group in Amazon CloudWatch Logs is encrypted with + * a AWS Key Management Service (KMS) managed Customer Master Keys (CMK). + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudwatch-log-group-encrypted.html + */ + public static readonly CLOUDWATCH_LOG_GROUP_ENCRYPTED = 'CLOUDWATCH_LOG_GROUP_ENCRYPTED'; + /** + * Checks that key rotation is enabled for each key and matches to the key ID of the + * customer created customer master key (CMK). + * @see https://docs.aws.amazon.com/config/latest/developerguide/cmk-backing-key-rotation-enabled.html + */ + public static readonly CMK_BACKING_KEY_ROTATION_ENABLED = 'CMK_BACKING_KEY_ROTATION_ENABLED'; + /** + * Checks whether the project contains environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. + * @see https://docs.aws.amazon.com/config/latest/developerguide/codebuild-project-envvar-awscred-check.html + */ + public static readonly CODEBUILD_PROJECT_ENVVAR_AWSCRED_CHECK = 'CODEBUILD_PROJECT_ENVVAR_AWSCRED_CHECK'; + /** + * Checks whether the GitHub or Bitbucket source repository URL contains either personal access tokens + * or user name and password. + * @see https://docs.aws.amazon.com/config/latest/developerguide/codebuild-project-source-repo-url-check.html + */ + public static readonly CODEBUILD_PROJECT_SOURCE_REPO_URL_CHECK = 'CODEBUILD_PROJECT_SOURCE_REPO_URL_CHECK'; + /** + * Checks whether the first deployment stage of the AWS CodePipeline performs more than one deployment. + * @see https://docs.aws.amazon.com/config/latest/developerguide/codepipeline-deployment-count-check.html + */ + public static readonly CODEPIPELINE_DEPLOYMENT_COUNT_CHECK = 'CODEPIPELINE_DEPLOYMENT_COUNT_CHECK'; + /** + * Checks whether each stage in the AWS CodePipeline deploys to more than N times the number of + * the regions the AWS CodePipeline has deployed in all the previous combined stages, + * where N is the region fanout number. + * @see https://docs.aws.amazon.com/config/latest/developerguide/codepipeline-region-fanout-check.html + */ + public static readonly CODEPIPELINE_REGION_FANOUT_CHECK = 'CODEPIPELINE_REGION_FANOUT_CHECK'; + /** + * Checks whether Amazon CloudWatch LogGroup retention period is set to specific number of days. + * @see https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html + */ + public static readonly CW_LOGGROUP_RETENTION_PERIOD_CHECK = 'CW_LOGGROUP_RETENTION_PERIOD_CHECK'; + /** + * Checks that DynamoDB Accelerator (DAX) clusters are encrypted. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dax-encryption-enabled.html + */ + public static readonly DAX_ENCRYPTION_ENABLED = 'DAX_ENCRYPTION_ENABLED'; + /** + * Checks whether RDS DB instances have backups enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/db-instance-backup-enabled.html + */ + public static readonly RDS_DB_INSTANCE_BACKUP_ENABLED = 'DB_INSTANCE_BACKUP_ENABLED'; + /** + * Checks instances for specified tenancy. + * @see https://docs.aws.amazon.com/config/latest/developerguide/desired-instance-tenancy.html + */ + public static readonly EC2_DESIRED_INSTANCE_TENANCY = 'DESIRED_INSTANCE_TENANCY'; + /** + * Checks whether your EC2 instances are of the specified instance types. + * @see https://docs.aws.amazon.com/config/latest/developerguide/desired-instance-type.html + */ + public static readonly EC2_DESIRED_INSTANCE_TYPE = 'DESIRED_INSTANCE_TYPE'; + /** + * Checks whether AWS Database Migration Service replication instances are public. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dms-replication-not-public.html + */ + public static readonly DMS_REPLICATION_NOT_PUBLIC = 'DMS_REPLICATION_NOT_PUBLIC'; + /** + * Checks whether Auto Scaling or On-Demand is enabled on your DynamoDB tables and/or global secondary indexes. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-autoscaling-enabled.html + */ + public static readonly DYNAMODB_AUTOSCALING_ENABLED = 'DYNAMODB_AUTOSCALING_ENABLED'; + /** + * Checks whether Amazon DynamoDB table is present in AWS Backup plans. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-in-backup-plan.html + */ + public static readonly DYNAMODB_IN_BACKUP_PLAN = 'DYNAMODB_IN_BACKUP_PLAN'; + /** + * Checks that point in time recovery (PITR) is enabled for Amazon DynamoDB tables. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-pitr-enabled.html + */ + public static readonly DYNAMODB_PITR_ENABLED = 'DYNAMODB_PITR_ENABLED'; + /** + * Checks whether Amazon DynamoDB table is encrypted with AWS Key Management Service (KMS). + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-table-encrypted-kms.html + */ + public static readonly DYNAMODB_TABLE_ENCRYPTED_KMS = 'DYNAMODB_TABLE_ENCRYPTED_KMS'; + /** + * Checks whether the Amazon DynamoDB tables are encrypted and checks their status. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-table-encryption-enabled.html + */ + public static readonly DYNAMODB_TABLE_ENCRYPTION_ENABLED = 'DYNAMODB_TABLE_ENCRYPTION_ENABLED'; + /** + * Checks whether provisioned DynamoDB throughput is approaching the maximum limit for your account. + * @see https://docs.aws.amazon.com/config/latest/developerguide/dynamodb-throughput-limit-check.html + */ + public static readonly DYNAMODB_THROUGHPUT_LIMIT_CHECK = 'DYNAMODB_THROUGHPUT_LIMIT_CHECK'; + /** + * Checks if Amazon Elastic Block Store (Amazon EBS) volumes are added in backup plans of AWS Backup. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ebs-in-backup-plan.html + */ + public static readonly EBS_IN_BACKUP_PLAN = 'EBS_IN_BACKUP_PLAN'; + /** + * Checks whether Amazon Elastic File System (Amazon EFS) file systems are added + * in the backup plans of AWS Backup. + * @see https://docs.aws.amazon.com/config/latest/developerguide/efs-in-backup-plan.html + */ + public static readonly EFS_IN_BACKUP_PLAN = 'EFS_IN_BACKUP_PLAN'; + /** + * Check that Amazon Elastic Block Store (EBS) encryption is enabled by default. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-ebs-encryption-by-default.html + */ + public static readonly EC2_EBS_ENCRYPTION_BY_DEFAULT = 'EC2_EBS_ENCRYPTION_BY_DEFAULT'; + /** + * Checks whether EBS optimization is enabled for your EC2 instances that can be EBS-optimized. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ebs-optimized-instance.html + */ + public static readonly EBS_OPTIMIZED_INSTANCE = 'EBS_OPTIMIZED_INSTANCE'; + /** + * Checks whether Amazon Elastic Block Store snapshots are not publicly restorable. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ebs-snapshot-public-restorable-check.html + */ + public static readonly EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK = 'EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK'; + /** + * Checks whether detailed monitoring is enabled for EC2 instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-instance-detailed-monitoring-enabled.html + */ + public static readonly EC2_INSTANCE_DETAILED_MONITORING_ENABLED = 'EC2_INSTANCE_DETAILED_MONITORING_ENABLED'; + /** + * Checks whether the Amazon EC2 instances in your account are managed by AWS Systems Manager. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-instance-managed-by-systems-manager.html + */ + public static readonly EC2_INSTANCE_MANAGED_BY_SSM = 'EC2_INSTANCE_MANAGED_BY_SSM'; + /** + * Checks whether Amazon Elastic Compute Cloud (Amazon EC2) instances have a public IP association. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-instance-no-public-ip.html + */ + public static readonly EC2_INSTANCE_NO_PUBLIC_IP = 'EC2_INSTANCE_NO_PUBLIC_IP'; + /** + * Checks whether your EC2 instances belong to a virtual private cloud (VPC). + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-instances-in-vpc.html + */ + public static readonly EC2_INSTANCES_IN_VPC = 'INSTANCES_IN_VPC'; + /** + * Checks that none of the specified applications are installed on the instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-applications-blacklisted.html + */ + public static readonly EC2_MANAGED_INSTANCE_APPLICATIONS_BLOCKED = 'EC2_MANAGEDINSTANCE_APPLICATIONS_BLACKLISTED'; + /** + * Checks whether all of the specified applications are installed on the instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-applications-required.html + */ + public static readonly EC2_MANAGED_INSTANCE_APPLICATIONS_REQUIRED = 'EC2_MANAGEDINSTANCE_APPLICATIONS_REQUIRED'; + /** + * Checks whether the compliance status of AWS Systems Manager association compliance is COMPLIANT + * or NON_COMPLIANT after the association execution on the instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-association-compliance-status-check.html + */ + public static readonly EC2_MANAGED_INSTANCE_ASSOCIATION_COMPLIANCE_STATUS_CHECK = 'EC2_MANAGEDINSTANCE_ASSOCIATION_COMPLIANCE_STATUS_CHECK'; + /** + * Checks whether instances managed by AWS Systems Manager are configured to collect blocked inventory types. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-inventory-blacklisted.html + */ + public static readonly EC2_MANAGED_INSTANCE_INVENTORY_BLOCKED = 'EC2_MANAGEDINSTANCE_INVENTORY_BLACKLISTED'; + /** + * Checks whether the compliance status of the Amazon EC2 Systems Manager patch compliance is + * COMPLIANT or NON_COMPLIANT after the patch installation on the instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-patch-compliance-status-check.html + */ + public static readonly EC2_MANAGED_INSTANCE_PATCH_COMPLIANCE_STATUS_CHECK = 'EC2_MANAGEDINSTANCE_PATCH_COMPLIANCE_STATUS_CHECK'; + /** + * Checks whether EC2 managed instances have the desired configurations. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-platform-check.html + */ + public static readonly EC2_MANAGED_INSTANCE_PLATFORM_CHECK = 'EC2_MANAGEDINSTANCE_PLATFORM_CHECK'; + /** + * Checks that security groups are attached to Amazon Elastic Compute Cloud (Amazon EC2) instances + * or to an elastic network interface. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-security-group-attached-to-eni.html + */ + public static readonly EC2_SECURITY_GROUP_ATTACHED_TO_ENI = 'EC2_SECURITY_GROUP_ATTACHED_TO_ENI'; + /** + * Checks whether there are instances stopped for more than the allowed number of days. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-stopped-instance.html + */ + public static readonly EC2_STOPPED_INSTANCE = 'EC2_STOPPED_INSTANCE'; + /** + * Checks whether EBS volumes are attached to EC2 instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-volume-inuse-check.html + */ + public static readonly EC2_VOLUME_INUSE_CHECK = 'EC2_VOLUME_INUSE_CHECK'; + /** + * hecks whether Amazon Elastic File System (Amazon EFS) is configured to encrypt the file data + * using AWS Key Management Service (AWS KMS). + * @see https://docs.aws.amazon.com/config/latest/developerguide/efs-encrypted-check.html + */ + public static readonly EFS_ENCRYPTED_CHECK = 'EFS_ENCRYPTED_CHECK'; + /** + * Checks whether all Elastic IP addresses that are allocated to a VPC are attached to + * EC2 instances or in-use elastic network interfaces (ENIs). + * @see https://docs.aws.amazon.com/config/latest/developerguide/eip-attached.html + */ + public static readonly EIP_ATTACHED = 'EIP_ATTACHED'; + /** + * Checks whether Amazon Elasticsearch Service (Amazon ES) domains have encryption + * at rest configuration enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elasticsearch-encrypted-at-rest.html + */ + public static readonly ELASTICSEARCH_ENCRYPTED_AT_REST = 'ELASTICSEARCH_ENCRYPTED_AT_REST'; + /** + * Checks whether Amazon Elasticsearch Service (Amazon ES) domains are in + * Amazon Virtual Private Cloud (Amazon VPC). + * @see https://docs.aws.amazon.com/config/latest/developerguide/elasticsearch-in-vpc-only.html + */ + public static readonly ELASTICSEARCH_IN_VPC_ONLY = 'ELASTICSEARCH_IN_VPC_ONLY'; + /** + * Check if the Amazon ElastiCache Redis clusters have automatic backup turned on. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elasticache-redis-cluster-automatic-backup-check.html + */ + public static readonly ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK = 'ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK'; + /** + * Checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version + * is configured with Instance Metadata Service Version 2 (IMDSv2). + * @see https://docs.aws.amazon.com/config/latest/developerguide/ec2-imdsv2-check.html + */ + public static readonly EC2_IMDSV2_CHECK = 'EC2_IMDSV2_CHECK'; + /** + * Checks whether Amazon Elastic Kubernetes Service (Amazon EKS) endpoint is not publicly accessible. + * @see https://docs.aws.amazon.com/config/latest/developerguide/eks-endpoint-no-public-access.html + */ + public static readonly EKS_ENDPOINT_NO_PUBLIC_ACCESS = 'EKS_ENDPOINT_NO_PUBLIC_ACCESS'; + /** + * Checks whether Amazon Elastic Kubernetes Service clusters are configured to have Kubernetes + * secrets encrypted using AWS Key Management Service (KMS) keys. + * @see https://docs.aws.amazon.com/config/latest/developerguide/eks-secrets-encrypted.html + */ + public static readonly EKS_SECRETS_ENCRYPTED = 'EKS_SECRETS_ENCRYPTED'; + /** + * Check that Amazon ElasticSearch Service nodes are encrypted end to end. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elasticsearch-node-to-node-encryption-check.html + */ + public static readonly ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK = 'ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK'; + /** + * Checks if cross-zone load balancing is enabled for the Classic Load Balancers (CLBs). + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-cross-zone-load-balancing-enabled.html + */ + public static readonly ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED = 'ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED'; + /** + * Checks whether your Classic Load Balancer is configured with SSL or HTTPS listeners. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-tls-https-listeners-only.html + */ + public static readonly ELB_TLS_HTTPS_LISTENERS_ONLY = 'ELB_TLS_HTTPS_LISTENERS_ONLY'; + /** + * Checks whether the Classic Load Balancers use SSL certificates provided by AWS Certificate Manager. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-acm-certificate-required.html + */ + public static readonly ELB_ACM_CERTIFICATE_REQUIRED = 'ELB_ACM_CERTIFICATE_REQUIRED'; + /** + * Checks whether your Classic Load Balancer SSL listeners are using a custom policy. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-custom-security-policy-ssl-check.html + */ + public static readonly ELB_CUSTOM_SECURITY_POLICY_SSL_CHECK = 'ELB_CUSTOM_SECURITY_POLICY_SSL_CHECK'; + /** + * Checks whether Elastic Load Balancing has deletion protection enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-deletion-protection-enabled.html + */ + public static readonly ELB_DELETION_PROTECTION_ENABLED = 'ELB_DELETION_PROTECTION_ENABLED'; + /** + * Checks whether the Application Load Balancer and the Classic Load Balancer have logging enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-logging-enabled.html + */ + public static readonly ELB_LOGGING_ENABLED = 'ELB_LOGGING_ENABLED'; + /** + * Checks whether your Classic Load Balancer SSL listeners are using a predefined policy. + * @see https://docs.aws.amazon.com/config/latest/developerguide/elb-predefined-security-policy-ssl-check.html + */ + public static readonly ELB_PREDEFINED_SECURITY_POLICY_SSL_CHECK = 'ELB_PREDEFINED_SECURITY_POLICY_SSL_CHECK'; + /** + * Checks that Amazon EMR clusters have Kerberos enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/emr-kerberos-enabled.html + */ + public static readonly EMR_KERBEROS_ENABLED = 'EMR_KERBEROS_ENABLED'; + /** + * Checks whether Amazon Elastic MapReduce (EMR) clusters' master nodes have public IPs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/emr-master-no-public-ip.html + */ + public static readonly EMR_MASTER_NO_PUBLIC_IP = 'EMR_MASTER_NO_PUBLIC_IP'; + /** + * Checks whether the EBS volumes that are in an attached state are encrypted. + * @see https://docs.aws.amazon.com/config/latest/developerguide/encrypted-volumes.html + */ + public static readonly EBS_ENCRYPTED_VOLUMES = 'ENCRYPTED_VOLUMES'; + /** + * Checks whether the security groups associated inScope resources are compliant with the + * master security groups at each rule level based on allowSecurityGroup and denySecurityGroup flag. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-security-group-audit-policy-check.html + */ + public static readonly FMS_SECURITY_GROUP_AUDIT_POLICY_CHECK = 'FMS_SECURITY_GROUP_AUDIT_POLICY_CHECK'; + /** + * Checks whether AWS Firewall Manager created security groups content is the same as the master security groups. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-security-group-content-check.html + */ + public static readonly FMS_SECURITY_GROUP_CONTENT_CHECK = 'FMS_SECURITY_GROUP_CONTENT_CHECK'; + /** + * Checks whether Amazon EC2 or an elastic network interface is associated with AWS Firewall Manager security groups. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-security-group-resource-association-check.html + */ + public static readonly FMS_SECURITY_GROUP_RESOURCE_ASSOCIATION_CHECK = 'FMS_SECURITY_GROUP_RESOURCE_ASSOCIATION_CHECK'; + /** + * Checks whether an Application Load Balancer, Amazon CloudFront distributions, + * Elastic Load Balancer or Elastic IP has AWS Shield protection. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-shield-resource-policy-check.html + */ + public static readonly FMS_SHIELD_RESOURCE_POLICY_CHECK = 'FMS_SHIELD_RESOURCE_POLICY_CHECK'; + /** + * Checks whether the web ACL is associated with an Application Load Balancer, API Gateway stage, + * or Amazon CloudFront distributions. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-webacl-resource-policy-check.html + */ + public static readonly FMS_WEBACL_RESOURCE_POLICY_CHECK = 'FMS_WEBACL_RESOURCE_POLICY_CHECK'; + /** + * Checks that the rule groups associate with the web ACL at the correct priority. + * The correct priority is decided by the rank of the rule groups in the ruleGroups parameter. + * @see https://docs.aws.amazon.com/config/latest/developerguide/fms-webacl-rulegroup-association-check.html + */ + public static readonly FMS_WEBACL_RULEGROUP_ASSOCIATION_CHECK = 'FMS_WEBACL_RULEGROUP_ASSOCIATION_CHECK'; + /** + * Checks whether Amazon GuardDuty is enabled in your AWS account and region. If you provide an AWS account for centralization, + * the rule evaluates the Amazon GuardDuty results in the centralized account. + * @see https://docs.aws.amazon.com/config/latest/developerguide/guardduty-enabled-centralized.html + */ + public static readonly GUARDDUTY_ENABLED_CENTRALIZED = 'GUARDDUTY_ENABLED_CENTRALIZED'; + /** + * Checks whether the Amazon GuardDuty has findings that are non archived. + * @see https://docs.aws.amazon.com/config/latest/developerguide/guardduty-non-archived-findings.html + */ + public static readonly GUARDDUTY_NON_ARCHIVED_FINDINGS = 'GUARDDUTY_NON_ARCHIVED_FINDINGS'; + /** + * Checks that inline policy feature is not in use. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-no-inline-policy-check.html + */ + public static readonly IAM_NO_INLINE_POLICY_CHECK = 'IAM_NO_INLINE_POLICY_CHECK'; + /** + * Checks whether IAM groups have at least one IAM user. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-group-has-users-check.html + */ + public static readonly IAM_GROUP_HAS_USERS_CHECK = 'IAM_GROUP_HAS_USERS_CHECK'; + /** + * Checks whether the account password policy for IAM users meets the specified requirements + * indicated in the parameters. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-password-policy.html + */ + public static readonly IAM_PASSWORD_POLICY = 'IAM_PASSWORD_POLICY'; + /** + * Checks whether for each IAM resource, a policy ARN in the input parameter is attached to the IAM resource. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-policy-blacklisted-check.html + */ + public static readonly IAM_POLICY_BLOCKED_CHECK = 'IAM_POLICY_BLACKLISTED_CHECK'; + /** + * Checks whether the IAM policy ARN is attached to an IAM user, or an IAM group with one or more IAM users, + * or an IAM role with one or more trusted entity. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-policy-in-use.html + */ + public static readonly IAM_POLICY_IN_USE = 'IAM_POLICY_IN_USE'; + /** + * Checks the IAM policies that you create for Allow statements that grant permissions to all actions on all resources. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-policy-no-statements-with-admin-access.html + */ + public static readonly IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESS = 'IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESS'; + /** + * Checks that AWS Identity and Access Management (IAM) policies in a list of policies are attached to all AWS roles. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-role-managed-policy-check.html + */ + public static readonly IAM_ROLE_MANAGED_POLICY_CHECK = 'IAM_ROLE_MANAGED_POLICY_CHECK'; + /** + * Checks whether the root user access key is available. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-root-access-key-check.html + */ + public static readonly IAM_ROOT_ACCESS_KEY_CHECK = 'IAM_ROOT_ACCESS_KEY_CHECK'; + /** + * Checks whether IAM users are members of at least one IAM group. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-user-group-membership-check.html + */ + public static readonly IAM_USER_GROUP_MEMBERSHIP_CHECK = 'IAM_USER_GROUP_MEMBERSHIP_CHECK'; + /** + * Checks whether the AWS Identity and Access Management users have multi-factor authentication (MFA) enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-user-mfa-enabled.html + */ + public static readonly IAM_USER_MFA_ENABLED = 'IAM_USER_MFA_ENABLED'; + /** + * Checks that none of your IAM users have policies attached. IAM users must inherit permissions from IAM groups or roles. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-user-no-policies-check.html + */ + public static readonly IAM_USER_NO_POLICIES_CHECK = 'IAM_USER_NO_POLICIES_CHECK'; + /** + * Checks whether your AWS Identity and Access Management (IAM) users have passwords or + * active access keys that have not been used within the specified number of days you provided. + * @see https://docs.aws.amazon.com/config/latest/developerguide/iam-user-unused-credentials-check.html + */ + public static readonly IAM_USER_UNUSED_CREDENTIALS_CHECK = 'IAM_USER_UNUSED_CREDENTIALS_CHECK'; + /** + * Checks that Internet gateways (IGWs) are only attached to an authorized Amazon Virtual Private Cloud (VPCs). + * @see https://docs.aws.amazon.com/config/latest/developerguide/internet-gateway-authorized-vpc-only.html + */ + public static readonly INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY = 'INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY'; + /** + * Checks whether customer master keys (CMKs) are not scheduled for deletion in AWS Key Management Service (KMS). + * @see https://docs.aws.amazon.com/config/latest/developerguide/kms-cmk-not-scheduled-for-deletion.html + */ + public static readonly KMS_CMK_NOT_SCHEDULED_FOR_DELETION = 'KMS_CMK_NOT_SCHEDULED_FOR_DELETION'; + /** + * Checks whether the AWS Lambda function is configured with function-level concurrent execution limit. + * @see https://docs.aws.amazon.com/config/latest/developerguide/lambda-concurrency-check.html + */ + public static readonly LAMBDA_CONCURRENCY_CHECK = 'LAMBDA_CONCURRENCY_CHECK'; + /** + * Checks whether an AWS Lambda function is configured with a dead-letter queue. + * @see https://docs.aws.amazon.com/config/latest/developerguide/lambda-dlq-check.html + */ + public static readonly LAMBDA_DLQ_CHECK = 'LAMBDA_DLQ_CHECK'; + /** + * Checks whether the AWS Lambda function policy attached to the Lambda resource prohibits public access. + * @see https://docs.aws.amazon.com/config/latest/developerguide/lambda-function-public-access-prohibited.html + */ + public static readonly LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED = 'LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED'; + /** + * Checks that the lambda function settings for runtime, role, timeout, and memory size match the expected values. + * @see https://docs.aws.amazon.com/config/latest/developerguide/lambda-function-settings-check.html + */ + public static readonly LAMBDA_FUNCTION_SETTINGS_CHECK = 'LAMBDA_FUNCTION_SETTINGS_CHECK'; + /** + * Checks whether an AWS Lambda function is in an Amazon Virtual Private Cloud. + * @see https://docs.aws.amazon.com/config/latest/developerguide/lambda-inside-vpc.html + */ + public static readonly LAMBDA_INSIDE_VPC = 'LAMBDA_INSIDE_VPC'; + /** + * Checks whether AWS Multi-Factor Authentication (MFA) is enabled for all IAM users that use a console password. + * @see https://docs.aws.amazon.com/config/latest/developerguide/mfa-enabled-for-iam-console-access.html + */ + public static readonly MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS = 'MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS'; + /** + * Checks that there is at least one multi-region AWS CloudTrail. + * @see https://docs.aws.amazon.com/config/latest/developerguide/multi-region-cloudtrail-enabled.html + */ + public static readonly CLOUDTRAIL_MULTI_REGION_ENABLED = 'MULTI_REGION_CLOUD_TRAIL_ENABLED'; + /** + * Checks if an Amazon Relational Database Service (Amazon RDS) cluster has deletion protection enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-cluster-deletion-protection-enabled.html + */ + public static readonly RDS_CLUSTER_DELETION_PROTECTION_ENABLED = 'RDS_CLUSTER_DELETION_PROTECTION_ENABLED'; + /** + * Checks if an Amazon Relational Database Service (Amazon RDS) instance has deletion protection enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-instance-deletion-protection-enabled.html + */ + public static readonly RDS_INSTANCE_DELETION_PROTECTION_ENABLED = 'RDS_INSTANCE_DELETION_PROTECTION_ENABLED'; + /** + * Checks if an Amazon RDS instance has AWS Identity and Access Management (IAM) authentication enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-instance-iam-authentication-enabled.html + */ + public static readonly RDS_INSTANCE_IAM_AUTHENTICATION_ENABLED = 'RDS_INSTANCE_IAM_AUTHENTICATION_ENABLED'; + /** + * Checks that respective logs of Amazon Relational Database Service (Amazon RDS) are enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-logging-enabled.html + */ + public static readonly RDS_LOGGING_ENABLED = 'RDS_LOGGING_ENABLED'; + /** + * Checks that Amazon Redshift automated snapshots are enabled for clusters. + * @see https://docs.aws.amazon.com/config/latest/developerguide/redshift-backup-enabled.html + */ + public static readonly REDSHIFT_BACKUP_ENABLED = 'REDSHIFT_BACKUP_ENABLED'; + /** + * Checks whether enhanced monitoring is enabled for Amazon Relational Database Service (Amazon RDS) instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-enhanced-monitoring-enabled.html + */ + public static readonly RDS_ENHANCED_MONITORING_ENABLED = 'RDS_ENHANCED_MONITORING_ENABLED'; + /** + * Checks whether Amazon Relational Database Service (Amazon RDS) DB snapshots are encrypted. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-snapshot-encrypted.html + */ + public static readonly RDS_SNAPSHOT_ENCRYPTED = 'RDS_SNAPSHOT_ENCRYPTED'; + /** + * Checks whether Amazon Redshift clusters require TLS/SSL encryption to connect to SQL clients. + * @see https://docs.aws.amazon.com/config/latest/developerguide/redshift-require-tls-ssl.html + */ + public static readonly REDSHIFT_REQUIRE_TLS_SSL = 'REDSHIFT_REQUIRE_TLS_SSL'; + /** + * Checks whether Amazon RDS database is present in back plans of AWS Backup. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-in-backup-plan.html + */ + public static readonly RDS_IN_BACKUP_PLAN = 'RDS_IN_BACKUP_PLAN'; + /** + * Check whether the Amazon Relational Database Service instances are not publicly accessible. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-instance-public-access-check.html + */ + public static readonly RDS_INSTANCE_PUBLIC_ACCESS_CHECK = 'RDS_INSTANCE_PUBLIC_ACCESS_CHECK'; + /** + * Checks whether high availability is enabled for your RDS DB instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-multi-az-support.html + */ + public static readonly RDS_MULTI_AZ_SUPPORT = 'RDS_MULTI_AZ_SUPPORT'; + /** + * Checks if Amazon Relational Database Service (Amazon RDS) snapshots are public. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-snapshots-public-prohibited.html + */ + public static readonly RDS_SNAPSHOTS_PUBLIC_PROHIBITED = 'RDS_SNAPSHOTS_PUBLIC_PROHIBITED'; + /** + * Checks whether storage encryption is enabled for your RDS DB instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/rds-storage-encrypted.html + */ + public static readonly RDS_STORAGE_ENCRYPTED = 'RDS_STORAGE_ENCRYPTED'; + /** + * Checks whether Amazon Redshift clusters have the specified settings. + * @see https://docs.aws.amazon.com/config/latest/developerguide/redshift-cluster-configuration-check.html + */ + public static readonly REDSHIFT_CLUSTER_CONFIGURATION_CHECK = 'REDSHIFT_CLUSTER_CONFIGURATION_CHECK'; + /** + * Checks whether Amazon Redshift clusters have the specified maintenance settings. + * @see https://docs.aws.amazon.com/config/latest/developerguide/redshift-cluster-maintenancesettings-check.html + */ + public static readonly REDSHIFT_CLUSTER_MAINTENANCE_SETTINGS_CHECK = 'REDSHIFT_CLUSTER_MAINTENANCESETTINGS_CHECK'; + /** + * Checks whether Amazon Redshift clusters are not publicly accessible. + * @see https://docs.aws.amazon.com/config/latest/developerguide/redshift-cluster-public-access-check.html + */ + public static readonly REDSHIFT_CLUSTER_PUBLIC_ACCESS_CHECK = 'REDSHIFT_CLUSTER_PUBLIC_ACCESS_CHECK'; + /** + * Checks whether your resources have the tags that you specify. + * For example, you can check whether your Amazon EC2 instances have the CostCenter tag. + * @see https://docs.aws.amazon.com/config/latest/developerguide/required-tags.html + */ + public static readonly REQUIRED_TAGS = 'REQUIRED_TAGS'; + /** + * Checks whether the security groups in use do not allow unrestricted incoming TCP traffic to the specified ports. + * @see https://docs.aws.amazon.com/config/latest/developerguide/restricted-common-ports.html + */ + public static readonly EC2_SECURITY_GROUPS_RESTRICTED_INCOMING_TRAFFIC = 'RESTRICTED_INCOMING_TRAFFIC'; + /** + * Checks whether the incoming SSH traffic for the security groups is accessible. + * @see https://docs.aws.amazon.com/config/latest/developerguide/restricted-ssh.html + */ + public static readonly EC2_SECURITY_GROUPS_INCOMING_SSH_DISABLED = 'INCOMING_SSH_DISABLED'; + /** + * Checks whether your AWS account is enabled to use multi-factor authentication (MFA) hardware + * device to sign in with root credentials. + * @see https://docs.aws.amazon.com/config/latest/developerguide/root-account-hardware-mfa-enabled.html + */ + public static readonly ROOT_ACCOUNT_HARDWARE_MFA_ENABLED = 'ROOT_ACCOUNT_HARDWARE_MFA_ENABLED'; + /** + * Checks whether users of your AWS account require a multi-factor authentication (MFA) device + * to sign in with root credentials. + * @see https://docs.aws.amazon.com/config/latest/developerguide/root-account-mfa-enabled.html + */ + public static readonly ROOT_ACCOUNT_MFA_ENABLED = 'ROOT_ACCOUNT_MFA_ENABLED'; + /** + * Checks whether Amazon Simple Storage Service (Amazon S3) bucket has lock enabled, by default. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-default-lock-enabled.html + */ + public static readonly S3_BUCKET_DEFAULT_LOCK_ENABLED = 'S3_BUCKET_DEFAULT_LOCK_ENABLED'; + /** + * Checks whether the Amazon Simple Storage Service (Amazon S3) buckets are encrypted + * with AWS Key Management Service (AWS KMS). + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-default-encryption-kms.html + */ + public static readonly S3_DEFAULT_ENCRYPTION_KMS = 'S3_DEFAULT_ENCRYPTION_KMS'; + /** + * Checks that AWS Security Hub is enabled for an AWS account. + * @see https://docs.aws.amazon.com/config/latest/developerguide/securityhub-enabled.html + */ + public static readonly SECURITYHUB_ENABLED = 'SECURITYHUB_ENABLED'; + /** + * Checks whether Amazon SNS topic is encrypted with AWS Key Management Service (AWS KMS). + * @see https://docs.aws.amazon.com/config/latest/developerguide/sns-encrypted-kms.html + */ + public static readonly SNS_ENCRYPTED_KMS = 'SNS_ENCRYPTED_KMS'; + /** + * Checks whether the required public access block settings are configured from account level. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-account-level-public-access-blocks.html + */ + public static readonly S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS = 'S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS'; + /** + * Checks that the Amazon Simple Storage Service bucket policy does not allow + * blocked bucket-level and object-level actions on resources in the bucket + * for principals from other AWS accounts. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-blacklisted-actions-prohibited.html + */ + public static readonly S3_BUCKET_BLOCKED_ACTIONS_PROHIBITED = 'S3_BUCKET_BLACKLISTED_ACTIONS_PROHIBITED'; + /** + * Verifies that your Amazon Simple Storage Service bucket policies do not allow + * other inter-account permissions than the control Amazon S3 bucket policy provided. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-policy-not-more-permissive.html + */ + public static readonly S3_BUCKET_POLICY_NOT_MORE_PERMISSIVE = 'S3_BUCKET_POLICY_NOT_MORE_PERMISSIVE'; + /** + * Checks whether logging is enabled for your S3 buckets. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-logging-enabled.html + */ + public static readonly S3_BUCKET_LOGGING_ENABLED = 'S3_BUCKET_LOGGING_ENABLED'; + /** + * Checks that the access granted by the Amazon S3 bucket is restricted by any of the AWS principals, + * federated users, service principals, IP addresses, or VPCs that you provide. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-policy-grantee-check.html + */ + public static readonly S3_BUCKET_POLICY_GRANTEE_CHECK = 'S3_BUCKET_POLICY_GRANTEE_CHECK'; + /** + * Checks that your Amazon S3 buckets do not allow public read access. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-public-read-prohibited.html + */ + public static readonly S3_BUCKET_PUBLIC_READ_PROHIBITED = 'S3_BUCKET_PUBLIC_READ_PROHIBITED'; + /** + * Checks that your Amazon S3 buckets do not allow public write access. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-public-write-prohibited.html + */ + public static readonly S3_BUCKET_PUBLIC_WRITE_PROHIBITED = 'S3_BUCKET_PUBLIC_WRITE_PROHIBITED'; + /** + * Checks whether S3 buckets have cross-region replication enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-replication-enabled.html + */ + public static readonly S3_BUCKET_REPLICATION_ENABLED = 'S3_BUCKET_REPLICATION_ENABLED'; + /** + * Checks that your Amazon S3 bucket either has Amazon S3 default encryption enabled or that the + * S3 bucket policy explicitly denies put-object requests without server side encryption that + * uses AES-256 or AWS Key Management Service. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-server-side-encryption-enabled.html + */ + public static readonly S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED = 'S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED'; + /** + * Checks whether S3 buckets have policies that require requests to use Secure Socket Layer (SSL). + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-ssl-requests-only.html + */ + public static readonly S3_BUCKET_SSL_REQUESTS_ONLY= 'S3_BUCKET_SSL_REQUESTS_ONLY'; + /** + * Checks whether versioning is enabled for your S3 buckets. + * @see https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-versioning-enabled.html + */ + public static readonly S3_BUCKET_VERSIONING_ENABLED = 'S3_BUCKET_VERSIONING_ENABLED'; + /** + * Checks whether AWS Key Management Service (KMS) key is configured for an Amazon SageMaker endpoint configuration. + * @see https://docs.aws.amazon.com/config/latest/developerguide/sagemaker-endpoint-configuration-kms-key-configured.html + */ + public static readonly SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED = 'SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED'; + /** + * Check whether an AWS Key Management Service (KMS) key is configured for SageMaker notebook instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/sagemaker-notebook-instance-kms-key-configured.html + */ + public static readonly SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED = 'SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED'; + /** + * Checks whether direct internet access is disabled for an Amazon SageMaker notebook instance. + * @see https://docs.aws.amazon.com/config/latest/developerguide/sagemaker-notebook-no-direct-internet-access.html + */ + public static readonly SAGEMAKER_NOTEBOOK_NO_DIRECT_INTERNET_ACCESS = 'SAGEMAKER_NOTEBOOK_NO_DIRECT_INTERNET_ACCESS'; + /** + * Checks whether AWS Secrets Manager secret has rotation enabled. + * @see https://docs.aws.amazon.com/config/latest/developerguide/secretsmanager-rotation-enabled-check.html + */ + public static readonly SECRETSMANAGER_ROTATION_ENABLED_CHECK = 'SECRETSMANAGER_ROTATION_ENABLED_CHECK'; + /** + * Checks whether AWS Secrets Manager secret rotation has rotated successfully as per the rotation schedule. + * @see https://docs.aws.amazon.com/config/latest/developerguide/secretsmanager-scheduled-rotation-success-check.html + */ + public static readonly SECRETSMANAGER_SCHEDULED_ROTATION_SUCCESS_CHECK = 'SECRETSMANAGER_SCHEDULED_ROTATION_SUCCESS_CHECK'; + /** + * Checks whether Service Endpoint for the service provided in rule parameter is created for each Amazon VPC. + * @see https://docs.aws.amazon.com/config/latest/developerguide/service-vpc-endpoint-enabled.html + */ + public static readonly SERVICE_VPC_ENDPOINT_ENABLED = 'SERVICE_VPC_ENDPOINT_ENABLED'; + /** + * Checks whether EBS volumes are attached to EC2 instances. + * @see https://docs.aws.amazon.com/config/latest/developerguide/shield-advanced-enabled-autorenew.html + */ + public static readonly SHIELD_ADVANCED_ENABLED_AUTO_RENEW = 'SHIELD_ADVANCED_ENABLED_AUTORENEW'; + /** + * Verify that DDoS response team (DRT) can access AWS account. + * @see https://docs.aws.amazon.com/config/latest/developerguide/shield-drt-access.html + */ + public static readonly SHIELD_DRT_ACCESS = 'SHIELD_DRT_ACCESS'; + /** + * Checks that the default security group of any Amazon Virtual Private Cloud (VPC) does not + * allow inbound or outbound traffic. The rule returns NOT_APPLICABLE if the security group + * is not default. + * @see https://docs.aws.amazon.com/config/latest/developerguide/vpc-default-security-group-closed.html + */ + public static readonly VPC_DEFAULT_SECURITY_GROUP_CLOSED = 'VPC_DEFAULT_SECURITY_GROUP_CLOSED'; + /** + * Checks whether Amazon Virtual Private Cloud flow logs are found and enabled for Amazon VPC. + * @see https://docs.aws.amazon.com/config/latest/developerguide/vpc-flow-logs-enabled.html + */ + public static readonly VPC_FLOW_LOGS_ENABLED = 'VPC_FLOW_LOGS_ENABLED'; + /** + * Checks whether the security group with 0.0.0.0/0 of any Amazon Virtual Private Cloud (Amazon VPC) + * allows only specific inbound TCP or UDP traffic. + * @see https://docs.aws.amazon.com/config/latest/developerguide/vpc-sg-open-only-to-authorized-ports.html + */ + public static readonly VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS = 'VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS'; + /** + * Checks that both AWS Virtual Private Network tunnels provided by AWS Site-to-Site VPN are in + * UP status. + * @see https://docs.aws.amazon.com/config/latest/developerguide/vpc-vpn-2-tunnels-up.html + */ + public static readonly VPC_VPN_2_TUNNELS_UP = 'VPC_VPN_2_TUNNELS_UP'; + /** + * Checks if logging is enabled on AWS Web Application Firewall (WAF) classic global web ACLs. + * @see https://docs.aws.amazon.com/config/latest/developerguide/waf-classic-logging-enabled.html + */ + public static readonly WAF_CLASSIC_LOGGING_ENABLED = 'WAF_CLASSIC_LOGGING_ENABLED'; + /** + * Checks whether logging is enabled on AWS Web Application Firewall (WAFV2) regional and global + * web access control list (ACLs). + * @see https://docs.aws.amazon.com/config/latest/developerguide/wafv2-logging-enabled.html + */ + public static readonly WAFV2_LOGGING_ENABLED = 'WAFV2_LOGGING_ENABLED'; + + // utility class + private constructor() { } +} + +/** + * Resources types that are supported by AWS Config + * @see https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html + */ +export class ResourceType { + /** API Gateway Stage */ + public static readonly APIGATEWAY_STAGE = new ResourceType('AWS::ApiGateway::Stage'); + /** API Gatewayv2 Stage */ + public static readonly APIGATEWAYV2_STAGE = new ResourceType('AWS::ApiGatewayV2::Stage'); + /** API Gateway REST API */ + public static readonly APIGATEWAY_REST_API = new ResourceType('AWS::ApiGateway::RestApi'); + /** API Gatewayv2 API */ + public static readonly APIGATEWAYV2_API = new ResourceType('AWS::ApiGatewayV2::Api'); + /** Amazon CloudFront Distribution */ + public static readonly CLOUDFRONT_DISTRIBUTION = new ResourceType('AWS::CloudFront::Distribution'); + /** Amazon CloudFront streaming distribution */ + public static readonly CLOUDFRONT_STREAMING_DISTRIBUTION = new ResourceType('AWS::CloudFront::StreamingDistribution'); + /** Amazon CloudWatch Alarm */ + public static readonly CLOUDWATCH_ALARM = new ResourceType('AWS::CloudWatch::Alarm'); + /** Amazon DynamoDB Table */ + public static readonly DYNAMODB_TABLE = new ResourceType('AWS::DynamoDB::Table'); + /** Elastic Block Store (EBS) volume */ + public static readonly EBS_VOLUME = new ResourceType('AWS::EC2::Volume'); + /** EC2 host */ + public static readonly EC2_HOST = new ResourceType('AWS::EC2::Host'); + /** EC2 Elastic IP */ + public static readonly EC2_EIP = new ResourceType('AWS::EC2::EIP'); + /** EC2 instance */ + public static readonly EC2_INSTANCE = new ResourceType('AWS::EC2::Instance'); + /** EC2 security group */ + public static readonly EC2_SECURITY_GROUP = new ResourceType('AWS::EC2::SecurityGroup'); + /** EC2 NAT gateway */ + public static readonly EC2_NAT_GATEWAY = new ResourceType('AWS::EC2::NatGateway'); + /** EC2 Egress only internet gateway */ + public static readonly EC2_EGRESS_ONLY_INTERNET_GATEWAY = new ResourceType('AWS::EC2::EgressOnlyInternetGateway'); + /** EC2 flow log */ + public static readonly EC2_FLOW_LOG = new ResourceType('AWS::EC2::FlowLog'); + /** EC2 VPC endpoint */ + public static readonly EC2_VPC_ENDPOINT = new ResourceType('AWS::EC2::VPCEndpoint'); + /** EC2 VPC endpoint service */ + public static readonly EC2_VPC_ENDPOINT_SERVICE = new ResourceType('AWS::EC2::VPCEndpointService'); + /** EC2 VPC peering connection */ + public static readonly EC2_VPC_PEERING_CONNECTION = new ResourceType('AWS::EC2::VPCPeeringConnection'); + /** Amazon ElasticSearch domain */ + public static readonly ELASTICSEARCH_DOMAIN = new ResourceType('AWS::Elasticsearch::Domain'); + /** Amazon QLDB ledger */ + public static readonly QLDB_LEDGER = new ResourceType('AWS::QLDB::Ledger'); + /** Amazon Redshift cluster */ + public static readonly REDSHIFT_CLUSTER = new ResourceType('AWS::Redshift::Cluster'); + /** Amazon Redshift cluster parameter group */ + public static readonly REDSHIFT_CLUSTER_PARAMETER_GROUP = new ResourceType('AWS::Redshift::ClusterParameterGroup'); + /** Amazon Redshift cluster security group */ + public static readonly REDSHIFT_CLUSTER_SECURITY_GROUP = new ResourceType('AWS::Redshift::ClusterSecurityGroup'); + /** Amazon Redshift cluster snapshot */ + public static readonly REDSHIFT_CLUSTER_SNAPSHOT = new ResourceType('AWS::Redshift::ClusterSnapshot'); + /** Amazon Redshift cluster subnet group */ + public static readonly REDSHIFT_CLUSTER_SUBNET_GROUP = new ResourceType('AWS::Redshift::ClusterSubnetGroup'); + /** Amazon Redshift event subscription */ + public static readonly REDSHIFT_EVENT_SUBSCRIPTION = new ResourceType('AWS::Redshift::EventSubscription'); + /** Amazon RDS database instance */ + public static readonly RDS_DB_INSTANCE = new ResourceType('AWS::RDS::DBInstance'); + /** Amazon RDS database security group */ + public static readonly RDS_DB_SECURITY_GROUP = new ResourceType('AWS::RDS::DBSecurityGroup'); + /** Amazon RDS database snapshot */ + public static readonly RDS_DB_SNAPSHOT = new ResourceType('AWS::RDS::DBSnapshot'); + /** Amazon RDS database subnet group */ + public static readonly RDS_DB_SUBNET_GROUP = new ResourceType('AWS::RDS::DBSubnetGroup'); + /** Amazon RDS event subscription */ + public static readonly RDS_EVENT_SUBSCRIPTION = new ResourceType('AWS::RDS::EventSubscription'); + /** Amazon RDS database cluster */ + public static readonly RDS_DB_CLUSTER = new ResourceType('AWS::RDS::DBCluster'); + /** Amazon RDS database cluster snapshot */ + public static readonly RDS_DB_CLUSTER_SNAPSHOT = new ResourceType('AWS::RDS::DBClusterSnapshot'); + /** Amazon SQS queue */ + public static readonly SQS_QUEUE = new ResourceType('AWS::SQS::Queue'); + /** Amazon SNS topic */ + public static readonly SNS_TOPIC = new ResourceType('AWS::SNS::Topic'); + /** Amazon S3 bucket */ + public static readonly S3_BUCKET = new ResourceType('AWS::S3::Bucket'); + /** Amazon S3 account public access block */ + public static readonly S3_ACCOUNT_PUBLIC_ACCESS_BLOCK = new ResourceType('AWS::S3::AccountPublicAccessBlock'); + /** Amazon EC2 customer gateway */ + public static readonly EC2_CUSTOMER_GATEWAY = new ResourceType('AWS::EC2::CustomerGateway'); + /** Amazon EC2 internet gateway */ + public static readonly EC2_INTERNET_GATEWAY = new ResourceType('AWS::EC2::CustomerGateway'); + /** Amazon EC2 network ACL */ + public static readonly EC2_NETWORK_ACL = new ResourceType('AWS::EC2::NetworkAcl'); + /** Amazon EC2 route table */ + public static readonly EC2_ROUTE_TABLE = new ResourceType('AWS::EC2::RouteTable'); + /** Amazon EC2 subnet table */ + public static readonly EC2_SUBNET = new ResourceType('AWS::EC2::Subnet'); + /** Amazon EC2 VPC */ + public static readonly EC2_VPC = new ResourceType('AWS::EC2::VPC'); + /** Amazon EC2 VPN connection */ + public static readonly EC2_VPN_CONNECTION = new ResourceType('AWS::EC2::VPNConnection'); + /** Amazon EC2 VPN gateway */ + public static readonly EC2_VPN_GATEWAY = new ResourceType('AWS::EC2::VPNGateway'); + /** AWS Auto Scaling group */ + public static readonly AUTO_SCALING_GROUP = new ResourceType('AWS::AutoScaling::AutoScalingGroup'); + /** AWS Auto Scaling launch configuration */ + public static readonly AUTO_SCALING_LAUNCH_CONFIGURATION = new ResourceType('AWS::AutoScaling::LaunchConfiguration'); + /** AWS Auto Scaling policy */ + public static readonly AUTO_SCALING_POLICY = new ResourceType('AWS::AutoScaling::ScalingPolicy'); + /** AWS Auto Scaling scheduled action */ + public static readonly AUTO_SCALING_SCHEDULED_ACTION = new ResourceType('AWS::AutoScaling::ScheduledAction'); + /** AWS Certificate manager certificate */ + public static readonly ACM_CERTIFICATE = new ResourceType('AWS::ACM::Certificate'); + /** AWS CloudFormation stack */ + public static readonly CLOUDFORMATION_STACK = new ResourceType('AWS::CloudFormation::Stack'); + /** AWS CloudTrail trail */ + public static readonly CLOUDTRAIL_TRAIL = new ResourceType('AWS::CloudTrail::Trail'); + /** AWS CodeBuild project */ + public static readonly CODEBUILD_PROJECT = new ResourceType('AWS::CodeBuild::Project'); + /** AWS CodePipeline pipeline */ + public static readonly CODEPIPELINE_PIPELINE = new ResourceType('AWS::CodePipeline::Pipeline'); + /** AWS Elastic Beanstalk (EB) application */ + public static readonly ELASTIC_BEANSTALK_APPLICATION = new ResourceType('AWS::ElasticBeanstalk::Application'); + /** AWS Elastic Beanstalk (EB) application version */ + public static readonly ELASTIC_BEANSTALK_APPLICATION_VERSION = new ResourceType('AWS::ElasticBeanstalk::ApplicationVersion'); + /** AWS Elastic Beanstalk (EB) environment */ + public static readonly ELASTIC_BEANSTALK_ENVIRONMENT = new ResourceType('AWS::ElasticBeanstalk::Environment'); + /** AWS IAM user */ + public static readonly IAM_USER = new ResourceType('AWS::IAM::User'); + /** AWS IAM group */ + public static readonly IAM_GROUP = new ResourceType('AWS::IAM::Group'); + /** AWS IAM role */ + public static readonly IAM_ROLE = new ResourceType('AWS::IAM::Role'); + /** AWS IAM policy */ + public static readonly IAM_POLICY = new ResourceType('AWS::IAM::Policy'); + /** AWS KMS Key */ + public static readonly KMS_KEY = new ResourceType('AWS::KMS::Key'); + /** AWS Lambda function */ + public static readonly LAMBDA_FUNCTION = new ResourceType('AWS::Lambda::Function'); + /**AWS Secrets Manager secret */ + public static readonly SECRETS_MANAGER_SECRET = new ResourceType('AWS::SecretsManager::Secret'); + /** AWS Service Catalog CloudFormation product */ + public static readonly SERVICE_CATALOG_CLOUDFORMATION_PRODUCT = new ResourceType('AWS::ServiceCatalog::CloudFormationProduct'); + /** AWS Service Catalog CloudFormation provisioned product */ + public static readonly SERVICE_CATALOG_CLOUDFORMATION_PROVISIONED_PRODUCT = new ResourceType( + 'AWS::ServiceCatalog::CloudFormationProvisionedProduct'); + /** AWS Service Catalog portfolio */ + public static readonly SERVICE_CATALOG_PORTFOLIO = new ResourceType('AWS::ServiceCatalog::Portfolio'); + /** AWS Shield protection */ + public static readonly SHIELD_PROTECTION = new ResourceType('AWS::Shield::Protection'); + /** AWS Shield regional protection */ + public static readonly SHIELD_REGIONAL_PROTECTION = new ResourceType('AWS::ShieldRegional::Protection'); + /** AWS Systems Manager managed instance inventory */ + public static readonly SYSTEMS_MANAGER_MANAGED_INSTANCE_INVENTORY = new ResourceType('AWS::SSM::ManagedInstanceInventory'); + /** AWS Systems Manager patch compliance */ + public static readonly SYSTEMS_MANAGER_PATCH_COMPLIANCE = new ResourceType('AWS::SSM::PatchCompliance'); + /** AWS Systems Manager association compliance */ + public static readonly SYSTEMS_MANAGER_ASSOCIATION_COMPLIANCE = new ResourceType('AWS::SSM::AssociationCompliance'); + /** AWS Systems Manager file data */ + public static readonly SYSTEMS_MANAGER_FILE_DATA = new ResourceType('AWS::SSM::FileData'); + /** AWS WAF rate based rule */ + public static readonly WAF_RATE_BASED_RULE = new ResourceType('AWS::WAF::RateBasedRule'); + /** AWS WAF rule */ + public static readonly WAF_RULE = new ResourceType('AWS::WAF::Rule'); + /** AWS WAF web ACL */ + public static readonly WAF_WEB_ACL = new ResourceType('AWS::WAF::WebACL'); + /** AWS WAF rule group */ + public static readonly WAF_RULE_GROUP = new ResourceType('AWS::WAF::RuleGroup'); + /** AWS WAF regional rate based rule */ + public static readonly WAF_REGIONAL_RATE_BASED_RULE = new ResourceType('AWS::WAFRegional::RateBasedRule'); + /** AWS WAF regional rule */ + public static readonly WAF_REGIONAL_RULE = new ResourceType('AWS::WAFRegional::Rule'); + /** AWS WAF web ACL */ + public static readonly WAF_REGIONAL_WEB_ACL = new ResourceType('AWS::WAFRegional::WebACL'); + /** AWS WAF regional rule group */ + public static readonly WAF_REGIONAL_RULE_GROUP = new ResourceType('AWS::WAFRegional::RuleGroup'); + /** AWS WAFv2 web ACL */ + public static readonly WAFV2_WEB_ACL = new ResourceType('AWS::WAFv2::WebACL'); + /** AWS WAFv2 rule group */ + public static readonly WAFV2_RULE_GROUP = new ResourceType('AWS::WAFv2::RuleGroup'); + /** AWS WAFv2 managed rule set */ + public static readonly WAFV2_MANAGED_RULE_SET = new ResourceType('AWS::WAFv2::ManagedRuleSet'); + /** AWS X-Ray encryption configuration */ + public static readonly XRAY_ENCRYPTION_CONFIGURATION = new ResourceType('AWS::XRay::EncryptionConfig'); + /** AWS ELB classic load balancer */ + public static readonly ELB_LOAD_BALANCER = new ResourceType('AWS::ElasticLoadBalancing::LoadBalancer'); + /** AWS ELBv2 network load balancer or AWS ELBv2 application load balancer */ + public static readonly ELBV2_LOAD_BALANCER = new ResourceType('AWS::ElasticLoadBalancingV2::LoadBalancer'); + + /** A custom resource type to support future cases. */ + public static of(type: string): ResourceType { + return new ResourceType(type); + } + + /** + * Valid value of resource type. + */ + public readonly complianceResourceType: string; + + private constructor(type: string) { + this.complianceResourceType = type; + } + +} + +function renderScope(ruleScope?: RuleScope): CfnConfigRule.ScopeProperty | undefined { + return ruleScope ? { + complianceResourceId: ruleScope.resourceId, + complianceResourceTypes: ruleScope.resourceTypes?.map(resource => resource.complianceResourceType), + tagKey: ruleScope.key, + tagValue: ruleScope.value, + } : undefined; +} diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 3d3e033d053a2..a29b1ced124bb 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Config", @@ -99,12 +100,12 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "developer-preview", + "stability": "stable", + "maturity": "stable", "features": [ { "name": "Higher level constructs for Config Rules", - "stability": "Developer Preview" + "stability": "Stable" }, { "name": "Higher level constructs for initial set-up (delivery channel & configuration recorder)", diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts index 5c15530c6c87a..45fef0c54d4f2 100644 --- a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts @@ -18,13 +18,12 @@ class ConfigStack extends cdk.Stack { runtime: lambda.Runtime.NODEJS_10_X, }); - const customRule = new config.CustomRule(this, 'Custom', { + new config.CustomRule(this, 'Custom', { configurationChanges: true, lambdaFunction: fn, + ruleScope: config.RuleScope.fromResources([config.ResourceType.EC2_INSTANCE]), }); - customRule.scopeToResource('AWS::EC2::Instance'); - // A rule to detect stacks drifts const driftRule = new config.CloudFormationStackDriftDetectionCheck(this, 'Drift'); diff --git a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts index aee8392f402f0..1203e179e0c62 100644 --- a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts +++ b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts @@ -12,11 +12,10 @@ const fn = new lambda.Function(stack, 'CustomFunction', { runtime: lambda.Runtime.NODEJS_10_X, }); -const customRule = new config.CustomRule(stack, 'Custom', { +new config.CustomRule(stack, 'Custom', { lambdaFunction: fn, periodic: true, + ruleScope: config.RuleScope.fromResources([config.ResourceType.EC2_INSTANCE]), }); -customRule.scopeToResource('AWS::EC2::Instance'); - app.synth(); diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index c13dacf2c8a3f..1fdac054bc321 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -140,12 +140,12 @@ export = { 'scope to resource'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const rule = new config.ManagedRule(stack, 'Rule', { - identifier: 'AWS_SUPER_COOL', - }); // WHEN - rule.scopeToResource('AWS::EC2::Instance', 'i-1234'); + new config.ManagedRule(stack, 'Rule', { + identifier: 'AWS_SUPER_COOL', + ruleScope: config.RuleScope.fromResource(config.ResourceType.EC2_INSTANCE, 'i-1234'), + }); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -163,12 +163,12 @@ export = { 'scope to resources'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const rule = new config.ManagedRule(stack, 'Rule', { - identifier: 'AWS_SUPER_COOL', - }); // WHEN - rule.scopeToResources('AWS::S3::Bucket', 'AWS::CloudFormation::Stack'); + new config.ManagedRule(stack, 'Rule', { + identifier: 'AWS_SUPER_COOL', + ruleScope: config.RuleScope.fromResources([config.ResourceType.S3_BUCKET, config.ResourceType.CLOUDFORMATION_STACK]), + }); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -186,12 +186,12 @@ export = { 'scope to tag'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const rule = new config.ManagedRule(stack, 'Rule', { - identifier: 'RULE', - }); // WHEN - rule.scopeToTag('key', 'value'); + new config.ManagedRule(stack, 'Rule', { + identifier: 'RULE', + ruleScope: config.RuleScope.fromTag('key', 'value'), + }); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -213,14 +213,12 @@ export = { runtime: lambda.Runtime.NODEJS_10_X, }); - // WHEN - const rule = new config.CustomRule(stack, 'Rule', { + // THEN + test.doesNotThrow(() => new config.CustomRule(stack, 'Rule', { lambdaFunction: fn, periodic: true, - }); - - // THEN - test.doesNotThrow(() => rule.scopeToResource('resource')); + ruleScope: config.RuleScope.fromResources([config.ResourceType.of('resource')]), + })); test.done(); }, diff --git a/packages/@aws-cdk/aws-datapipeline/.npmignore b/packages/@aws-cdk/aws-datapipeline/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-datapipeline/.npmignore +++ b/packages/@aws-cdk/aws-datapipeline/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 349c70d00ebfb..972bee3e20e85 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DataPipeline", diff --git a/packages/@aws-cdk/aws-dax/.npmignore b/packages/@aws-cdk/aws-dax/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-dax/.npmignore +++ b/packages/@aws-cdk/aws-dax/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index d2aba2cfb2787..4bd5a02d080d5 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DAX", diff --git a/packages/@aws-cdk/aws-detective/.npmignore b/packages/@aws-cdk/aws-detective/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-detective/.npmignore +++ b/packages/@aws-cdk/aws-detective/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-detective/package.json b/packages/@aws-cdk/aws-detective/package.json index d99077efe02f7..fce28548ba5ff 100644 --- a/packages/@aws-cdk/aws-detective/package.json +++ b/packages/@aws-cdk/aws-detective/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Detective", diff --git a/packages/@aws-cdk/aws-directoryservice/.npmignore b/packages/@aws-cdk/aws-directoryservice/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-directoryservice/.npmignore +++ b/packages/@aws-cdk/aws-directoryservice/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index 2625ba019a9c6..3db4839b33c51 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DirectoryService", diff --git a/packages/@aws-cdk/aws-dlm/.npmignore b/packages/@aws-cdk/aws-dlm/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-dlm/.npmignore +++ b/packages/@aws-cdk/aws-dlm/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index 6f4a8134dd5e6..e5ef692a59834 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DLM", diff --git a/packages/@aws-cdk/aws-dms/.npmignore b/packages/@aws-cdk/aws-dms/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-dms/.npmignore +++ b/packages/@aws-cdk/aws-dms/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index af3bd1ce5e56c..5214671a6b4ad 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DMS", diff --git a/packages/@aws-cdk/aws-docdb/.npmignore b/packages/@aws-cdk/aws-docdb/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-docdb/.npmignore +++ b/packages/@aws-cdk/aws-docdb/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 4e1f18ea39357..3d3f83fbed1f7 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -50,7 +50,8 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DocDB", diff --git a/packages/@aws-cdk/aws-dynamodb-global/.npmignore b/packages/@aws-cdk/aws-dynamodb-global/.npmignore index e418511182841..ecc7155058048 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/.npmignore +++ b/packages/@aws-cdk/aws-dynamodb-global/.npmignore @@ -23,4 +23,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/.npmignore b/packages/@aws-cdk/aws-dynamodb/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-dynamodb/.npmignore +++ b/packages/@aws-cdk/aws-dynamodb/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index d6d65ad693812..d1c6836acd133 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -565,10 +565,7 @@ abstract class TableBase extends Resource implements ITable { return iam.Grant.addToPrincipal({ grantee, actions: ['dynamodb:ListStreams'], - resourceArns: [ - Lazy.stringValue({ produce: () => `${this.tableArn}/stream/*` }), - ...this.regionalArns.map(arn => Lazy.stringValue({ produce: () => `${arn}/stream/*` })), - ], + resourceArns: ['*'], }); } diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index c0d29b0258414..bd0deb1d79d30 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::DynamoDB", @@ -78,7 +79,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "sinon": "^9.1.0", "ts-jest": "^26.4.1" diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 990e9bcd42094..4d0ab34e5a757 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -600,20 +600,7 @@ test('if an encryption key is included, decrypt permissions are also added for g { 'Action': 'dynamodb:ListStreams', 'Effect': 'Allow', - 'Resource': { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'TableA3D7B5AFA', - 'Arn', - ], - }, - '/stream/*', - ], - ], - }, + 'Resource': '*', }, { 'Action': [ @@ -1112,7 +1099,7 @@ test('error when adding a global secondary index with projection type KEYS_ONLY, })).toThrow(/non-key attributes should not be specified when not using INCLUDE projection type/); }); -test('error when adding a global secondary index with projection type INCLUDE, but with more than 20 non-key attributes', () => { +test('error when adding a global secondary index with projection type INCLUDE, but with more than 100 non-key attributes', () => { const stack = new Stack(); const table = new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }); const gsiNonKeyAttributeGenerator = NON_KEY_ATTRIBUTE_GENERATOR(GSI_NON_KEY); @@ -1784,7 +1771,7 @@ describe('grants', () => { { 'Action': 'dynamodb:ListStreams', 'Effect': 'Allow', - 'Resource': { 'Fn::Join': ['', [{ 'Fn::GetAtt': ['mytable0324D45C', 'Arn'] }, '/stream/*']] }, + 'Resource': '*', }, ], 'Version': '2012-10-17', @@ -1830,7 +1817,7 @@ describe('grants', () => { { 'Action': 'dynamodb:ListStreams', 'Effect': 'Allow', - 'Resource': { 'Fn::Join': ['', [{ 'Fn::GetAtt': ['mytable0324D45C', 'Arn'] }, '/stream/*']] }, + 'Resource': '*', }, { 'Action': [ @@ -2145,7 +2132,7 @@ describe('import', () => { { Action: 'dynamodb:ListStreams', Effect: 'Allow', - Resource: stack.resolve(`${table.tableArn}/stream/*`), + Resource: '*', }, ], Version: '2012-10-17', @@ -2173,7 +2160,7 @@ describe('import', () => { { Action: 'dynamodb:ListStreams', Effect: 'Allow', - Resource: stack.resolve(`${table.tableArn}/stream/*`), + Resource: '*', }, { Action: ['dynamodb:DescribeStream', 'dynamodb:GetRecords', 'dynamodb:GetShardIterator'], @@ -2638,56 +2625,7 @@ describe('global', () => { { Action: 'dynamodb:ListStreams', Effect: 'Allow', - Resource: [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':dynamodb:us-east-1:', - { - Ref: 'AWS::AccountId', - }, - ':table/my-table/stream/*', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':dynamodb:eu-west-2:', - { - Ref: 'AWS::AccountId', - }, - ':table/my-table/stream/*', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':dynamodb:eu-central-1:', - { - Ref: 'AWS::AccountId', - }, - ':table/my-table/stream/*', - ], - ], - }, - ], + Resource: '*', }, ], Version: '2012-10-17', diff --git a/packages/@aws-cdk/aws-ec2/.npmignore b/packages/@aws-cdk/aws-ec2/.npmignore index 5d8f93d8a9c7e..6b3aee5fcab7c 100644 --- a/packages/@aws-cdk/aws-ec2/.npmignore +++ b/packages/@aws-cdk/aws-ec2/.npmignore @@ -23,4 +23,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out jest.config.js -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 2704685007a0e..0a3114dc34d27 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -738,14 +738,14 @@ EBS volume for the bastion host can be encrypted like: ### Block Devices -To add EBS block device mappings, specify the `blockDeviceMappings` property. The follow example sets the EBS-backed +To add EBS block device mappings, specify the `blockDevices` property. The following example sets the EBS-backed root device (`/dev/sda1`) size to 50 GiB, and adds another EBS-backed device mapped to `/dev/sdm` that is 100 GiB in size: ```ts new ec2.Instance(this, 'Instance', { // ... - blockDeviceMappings: [ + blockDevices: [ { deviceName: '/dev/sda1', volume: ec2.BlockDeviceVolume.ebs(50), @@ -894,6 +894,11 @@ new ec2.FlowLog(this, 'FlowLog', { resourceType: ec2.FlowLogResourceType.fromVpc(vpc), destination: ec2.FlowLogDestination.toS3(bucket) }); + +new ec2.FlowLog(this, 'FlowLogWithKeyPrefix', { + resourceType: ec2.FlowLogResourceType.fromVpc(vpc), + destination: ec2.FlowLogDestination.toS3(bucket, 'prefix/') +}); ``` ## User Data diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index d6ce443bb6291..0933227713eda 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -193,6 +193,16 @@ export enum InstanceClass { */ COMPUTE5_NVME_DRIVE = 'c5d', + /** + * Compute optimized instances based on AMD EPYC, 5th generation. + */ + COMPUTE5_AMD = 'c5a', + + /** + * Compute optimized instances based on AMD EPYC, 5th generation + */ + C5A = 'c5a', + /** * Compute optimized instances with local NVME drive, 5th generation */ @@ -300,6 +310,16 @@ export enum InstanceClass { */ T3A = 't3a', + /** + * Burstable instances, 4th generation with Graviton2 processors + */ + BURSTABLE4_GRAVITON = 't4g', + + /** + * Burstable instances, 4th generation with Graviton2 processors + */ + T4G = 't4g', + /** * Memory-intensive instances, 1st generation */ @@ -535,4 +555,4 @@ export class InstanceType { public toString(): string { return this.instanceTypeIdentifier; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts index bcfb8602aa595..0edf1867236a7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts @@ -129,10 +129,11 @@ export abstract class FlowLogDestination { /** * Use S3 as the destination */ - public static toS3(bucket?: s3.IBucket): FlowLogDestination { + public static toS3(bucket?: s3.IBucket, keyPrefix?: string): FlowLogDestination { return new S3Destination({ logDestinationType: FlowLogDestinationType.S3, s3Bucket: bucket, + keyPrefix, }); } @@ -175,6 +176,13 @@ export interface FlowLogDestinationConfig { * @default - undefined */ readonly s3Bucket?: s3.IBucket; + + /** + * S3 bucket key prefix to publish the flow logs to + * + * @default - undefined + */ + readonly keyPrefix?: string; } /** @@ -198,6 +206,7 @@ class S3Destination extends FlowLogDestination { return { logDestinationType: FlowLogDestinationType.S3, s3Bucket, + keyPrefix: this.props.keyPrefix, }; } } @@ -344,6 +353,11 @@ export class FlowLog extends FlowLogBase { */ public readonly bucket?: s3.IBucket; + /** + * S3 bucket key prefix to publish the flow logs under + */ + readonly keyPrefix?: string; + /** * The iam role used to publish logs to CloudWatch */ @@ -365,6 +379,12 @@ export class FlowLog extends FlowLogBase { this.logGroup = destinationConfig.logGroup; this.bucket = destinationConfig.s3Bucket; this.iamRole = destinationConfig.iamRole; + this.keyPrefix = destinationConfig.keyPrefix; + + let logDestination: string | undefined = undefined; + if (this.bucket) { + logDestination = this.keyPrefix ? this.bucket.arnForObjects(this.keyPrefix) : this.bucket.bucketArn; + } const flowLog = new CfnFlowLog(this, 'FlowLog', { deliverLogsPermissionArn: this.iamRole ? this.iamRole.roleArn : undefined, @@ -375,7 +395,7 @@ export class FlowLog extends FlowLogBase { trafficType: props.trafficType ? props.trafficType : FlowLogTrafficType.ALL, - logDestination: this.bucket ? this.bucket.bucketArn : undefined, + logDestination, }); this.flowLogId = flowLog.ref; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 409a7599e787e..62d202992b7ee 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -1,6 +1,6 @@ import * as net from 'net'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import * as cdk from '@aws-cdk/core'; +import { IResource, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnCustomerGateway, @@ -10,7 +10,7 @@ import { } from './ec2.generated'; import { IVpc, SubnetSelection } from './vpc'; -export interface IVpnConnection extends cdk.IResource { +export interface IVpnConnection extends IResource { /** * The id of the VPN connection. */ @@ -35,7 +35,7 @@ export interface IVpnConnection extends cdk.IResource { /** * The virtual private gateway interface */ -export interface IVpnGateway extends cdk.IResource { +export interface IVpnGateway extends IResource { /** * The virtual private gateway Id @@ -148,7 +148,7 @@ export enum VpnConnectionType { * * @resource AWS::EC2::VPNGateway */ -export class VpnGateway extends cdk.Resource implements IVpnGateway { +export class VpnGateway extends Resource implements IVpnGateway { /** * The virtual private gateway Id @@ -170,7 +170,7 @@ export class VpnGateway extends cdk.Resource implements IVpnGateway { * * @resource AWS::EC2::VPNConnection */ -export class VpnConnection extends cdk.Resource implements IVpnConnection { +export class VpnConnection extends Resource implements IVpnConnection { /** * Return the given named metric for all VPN connections in the account/region. */ @@ -252,7 +252,7 @@ export class VpnConnection extends cdk.Resource implements IVpnConnection { } props.tunnelOptions.forEach((options, index) => { - if (options.preSharedKey && !/^[a-zA-Z1-9._][a-zA-Z\d._]{7,63}$/.test(options.preSharedKey)) { + if (options.preSharedKey && !Token.isUnresolved(options.preSharedKey) && !/^[a-zA-Z1-9._][a-zA-Z\d._]{7,63}$/.test(options.preSharedKey)) { /* eslint-disable max-len */ throw new Error(`The \`preSharedKey\` ${options.preSharedKey} for tunnel ${index + 1} is invalid. Allowed characters are alphanumeric characters and ._. Must be between 8 and 64 characters in length and cannot start with zero (0).`); /* eslint-enable max-len */ diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index e61318e44f6cf..a5d37a1f53a96 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EC2", diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 286c732e09db3..dea92b5b3d8cd 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -19,12 +19,12 @@ nodeunitShim({ new Instance(stack, 'Instance', { vpc, machineImage: new AmazonLinuxImage(), - instanceType: InstanceType.of(InstanceClass.MEMORY6_GRAVITON, InstanceSize.LARGE), + instanceType: InstanceType.of(InstanceClass.BURSTABLE4_GRAVITON, InstanceSize.LARGE), }); // THEN cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { - InstanceType: 'r6g.large', + InstanceType: 't4g.large', })); test.done(); diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json index cedf8d1bdea84..ab9eb13b2c415 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json @@ -550,6 +550,37 @@ ] } }, + "VPCFlowLogsS3KeyPrefixFlowLogB57F1746": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "VPCB9E5F0B4" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "LogDestination": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "/prefix/" + ] + ] + }, + "LogDestinationType": "s3", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] + } + }, "FlowLogsCWIAMRole017AD736": { "Type": "AWS::IAM::Role", "Properties": { @@ -634,6 +665,11 @@ "Ref": "FlowLogsCWLogGroup0398E8F8" } } + }, + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.ts index debba35a82a3e..c9b87c51bf7fd 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.ts @@ -1,5 +1,6 @@ /// !cdk-integ * -import { App, Stack, StackProps } from '@aws-cdk/core'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core'; import { FlowLog, FlowLogDestination, FlowLogResourceType, Vpc } from '../lib'; const app = new App(); @@ -17,6 +18,14 @@ class TestStack extends Stack { vpc.addFlowLog('FlowLogsS3', { destination: FlowLogDestination.toS3(), }); + + const bucket = new s3.Bucket(this, 'Bucket', { + removalPolicy: RemovalPolicy.DESTROY, + }); + + vpc.addFlowLog('FlowLogsS3KeyPrefix', { + destination: FlowLogDestination.toS3(bucket, 'prefix/'), + }); } } diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.expected.json new file mode 100644 index 0000000000000..e35654d4ac5ac --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.expected.json @@ -0,0 +1,652 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.10.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.0.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet2Subnet492B6BFB": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.32.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTable1DF17386": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTableAssociation227DE78D": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet3Subnet57EEE236": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.64.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTable15028F08": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTableAssociation5C27DDA4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + } + } + }, + "MyVpcPublicSubnet3DefaultRoute3A83AB36": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet3EIPC5ACADAB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3NATGatewayD4B50EBE": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet3EIPC5ACADAB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.96.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcPrivateSubnet2Subnet0040C983": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.128.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableAssociation86A610DA": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "MyVpcPrivateSubnet3Subnet772D6AD7": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.10.160.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableB790927C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc/PrivateSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableAssociationD951741C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + } + }, + "MyVpcPrivateSubnet3DefaultRouteEC11C0C5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet3NATGatewayD4B50EBE" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "MyVpcVpnGateway11FB05E5": { + "Type": "AWS::EC2::VPNGateway", + "Properties": { + "Type": "ipsec.1", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ] + } + }, + "MyVpcVPCVPNGW0CB969B3": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "VpnGatewayId": { + "Ref": "MyVpcVpnGateway11FB05E5" + } + } + }, + "MyVpcRoutePropagation122FC3BE": { + "Type": "AWS::EC2::VPNGatewayRoutePropagation", + "Properties": { + "RouteTableIds": [ + { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + } + ], + "VpnGatewayId": { + "Ref": "MyVpcVpnGateway11FB05E5" + } + }, + "DependsOn": [ + "MyVpcVPCVPNGW0CB969B3" + ] + }, + "MyVpcDynamicCustomerGatewayFB63DFBF": { + "Type": "AWS::EC2::CustomerGateway", + "Properties": { + "BgpAsn": 65000, + "IpAddress": "52.85.255.164", + "Type": "ipsec.1", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ] + } + }, + "MyVpcDynamic739F3519": { + "Type": "AWS::EC2::VPNConnection", + "Properties": { + "CustomerGatewayId": { + "Ref": "MyVpcDynamicCustomerGatewayFB63DFBF" + }, + "Type": "ipsec.1", + "StaticRoutesOnly": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ], + "VpnGatewayId": { + "Ref": "MyVpcVpnGateway11FB05E5" + }, + "VpnTunnelOptionsSpecifications": [ + { + "PreSharedKey": "{{resolve:ssm-secure:ssm-pw:1}}" + } + ] + } + }, + "MyVpcStaticCustomerGateway43D01906": { + "Type": "AWS::EC2::CustomerGateway", + "Properties": { + "BgpAsn": 65000, + "IpAddress": "52.85.255.197", + "Type": "ipsec.1", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ] + } + }, + "MyVpcStaticABA7F625": { + "Type": "AWS::EC2::VPNConnection", + "Properties": { + "CustomerGatewayId": { + "Ref": "MyVpcStaticCustomerGateway43D01906" + }, + "Type": "ipsec.1", + "StaticRoutesOnly": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpn/MyVpc" + } + ], + "VpnGatewayId": { + "Ref": "MyVpcVpnGateway11FB05E5" + } + } + }, + "MyVpcStaticRoute192168100240A24A5CC": { + "Type": "AWS::EC2::VPNConnectionRoute", + "Properties": { + "DestinationCidrBlock": "192.168.10.0/24", + "VpnConnectionId": { + "Ref": "MyVpcStaticABA7F625" + } + } + }, + "MyVpcStaticRoute19216820024CD4B642F": { + "Type": "AWS::EC2::VPNConnectionRoute", + "Properties": { + "DestinationCidrBlock": "192.168.20.0/24", + "VpnConnectionId": { + "Ref": "MyVpcStaticABA7F625" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.ts new file mode 100644 index 0000000000000..fb5bb45119b08 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpn-pre-shared-key-token.ts @@ -0,0 +1,29 @@ +import * as cdk from '@aws-cdk/core'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpn'); + +const vpc = new ec2.Vpc(stack, 'MyVpc', { + cidr: '10.10.0.0/16', + vpnConnections: { + Dynamic: { // Dynamic routing + ip: '52.85.255.164', + tunnelOptions: [ + { + preSharedKey: cdk.SecretValue.ssmSecure('ssm-pw', '1').toString(), + }, + ], + }, + }, +}); + +vpc.addVpnConnection('Static', { // Static routing + ip: '52.85.255.197', + staticRoutes: [ + '192.168.10.0/24', + '192.168.20.0/24', + ], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-flow-logs.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc-flow-logs.test.ts index 965c538bab5a0..8d341924c3bad 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc-flow-logs.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc-flow-logs.test.ts @@ -79,6 +79,26 @@ nodeunitShim({ })); test.done(); }, + 'with s3 as the destination, allows use of key prefix'(test: Test) { + const stack = getTestStack(); + + new FlowLog(stack, 'FlowLogs', { + resourceType: FlowLogResourceType.fromNetworkInterfaceId('eni-123456'), + destination: FlowLogDestination.toS3( + new s3.Bucket(stack, 'TestBucket', { + bucketName: 'testbucket', + }), + 'FlowLogs/', + ), + }); + + expect(stack).notTo(haveResource('AWS::Logs::LogGroup')); + expect(stack).notTo(haveResource('AWS::IAM::Role')); + expect(stack).to(haveResource('AWS::S3::Bucket', { + BucketName: 'testbucket', + })); + test.done(); + }, 'with s3 as the destination and all the defaults set, it successfully creates all the resources'( test: Test, ) { diff --git a/packages/@aws-cdk/aws-ecr-assets/.npmignore b/packages/@aws-cdk/aws-ecr-assets/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-ecr-assets/.npmignore +++ b/packages/@aws-cdk/aws-ecr-assets/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/.npmignore b/packages/@aws-cdk/aws-ecr/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-ecr/.npmignore +++ b/packages/@aws-cdk/aws-ecr/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 23022426c5a0b..d84f095e0ea01 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ECR", diff --git a/packages/@aws-cdk/aws-ecs-patterns/.npmignore b/packages/@aws-cdk/aws-ecs-patterns/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/.npmignore +++ b/packages/@aws-cdk/aws-ecs-patterns/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/package.json b/packages/@aws-cdk/aws-ecs-patterns/package.json index a8d7905e89cf2..ead8ced0e0ee1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/package.json +++ b/packages/@aws-cdk/aws-ecs-patterns/package.json @@ -69,7 +69,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-ecs/.npmignore b/packages/@aws-cdk/aws-ecs/.npmignore index d683303ca5c16..40fbede1f02dc 100644 --- a/packages/@aws-cdk/aws-ecs/.npmignore +++ b/packages/@aws-cdk/aws-ecs/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index c3d6d784e8364..17d2965b792eb 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ECS", diff --git a/packages/@aws-cdk/aws-efs/.npmignore b/packages/@aws-cdk/aws-efs/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-efs/.npmignore +++ b/packages/@aws-cdk/aws-efs/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-efs/README.md b/packages/@aws-cdk/aws-efs/README.md index f1082eeddbc83..853ae097327cb 100644 --- a/packages/@aws-cdk/aws-efs/README.md +++ b/packages/@aws-cdk/aws-efs/README.md @@ -76,7 +76,8 @@ const fileSystem = new efs.FileSystem(this, 'MyEfsFileSystem', { encrypted: true, lifecyclePolicy: efs.LifecyclePolicy.AFTER_14_DAYS, performanceMode: efs.PerformanceMode.GENERAL_PURPOSE, - throughputMode: efs.ThroughputMode.BURSTING + throughputMode: efs.ThroughputMode.BURSTING, + enableAutomaticBackups: true }); const inst = new Instance(this, 'inst', { diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index ce5fc4eac2e9c..2c28375667fe4 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -171,6 +171,13 @@ export interface FileSystemProps { * @default RemovalPolicy.RETAIN */ readonly removalPolicy?: RemovalPolicy; + + /** + * Whether to enable automatic backups for the file system. + * + * @default false + */ + readonly enableAutomaticBackups?: boolean; } /** @@ -252,6 +259,7 @@ export class FileSystem extends Resource implements IFileSystem { performanceMode: props.performanceMode, throughputMode: props.throughputMode, provisionedThroughputInMibps: props.provisionedThroughputPerSecond?.toMebibytes(), + backupPolicy: props.enableAutomaticBackups ? { status: 'ENABLED' } : undefined, }); filesystem.applyRemovalPolicy(props.removalPolicy); diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 2e297e49cf73c..d936597b3db85 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EFS", diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index 02369563bd9a5..dfdb56bcc46f6 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -7,7 +7,7 @@ import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode } from '.. let stack = new Stack(); let vpc = new ec2.Vpc(stack, 'VPC'); -beforeEach( () => { +beforeEach(() => { stack = new Stack(); vpc = new ec2.Vpc(stack, 'VPC'); }); @@ -216,11 +216,24 @@ test('auto-named if none provided', () => { }); test('removalPolicy is DESTROY', () => { + // WHEN new FileSystem(stack, 'EfsFileSystem', { vpc, removalPolicy: RemovalPolicy.DESTROY }); + // THEN expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete', }, ResourcePart.CompleteDefinition)); +}); + +test('can specify backup policy', () => { + // WHEN + new FileSystem(stack, 'EfsFileSystem', { vpc, enableAutomaticBackups: true }); + // THEN + expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { + BackupPolicy: { + Status: 'ENABLED', + }, + })); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/.npmignore b/packages/@aws-cdk/aws-eks-legacy/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-eks-legacy/.npmignore +++ b/packages/@aws-cdk/aws-eks-legacy/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index 0cbb2b783a0b4..ddbc04d379dc2 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EKS" diff --git a/packages/@aws-cdk/aws-eks/.npmignore b/packages/@aws-cdk/aws-eks/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-eks/.npmignore +++ b/packages/@aws-cdk/aws-eks/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index a49a11aab2624..895c954a9b692 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -36,6 +36,7 @@ Table Of Contents * [Applying Kubernetes Resources](#applying-kubernetes-resources) * [Kubernetes Manifests](#kubernetes-manifests) * [Helm Charts](#helm-charts) + * [CDK8s Charts](#cdk8s-charts) * [Patching Kuberentes Resources](#patching-kubernetes-resources) * [Querying Kubernetes Resources](#querying-kubernetes-resources) * [Using existing clusters](#using-existing-clusters) @@ -51,7 +52,7 @@ This example defines an Amazon EKS cluster with the following configuration: ```ts // provisiong a cluster const cluster = new eks.Cluster(this, 'hello-eks', { - version: eks.KubernetesVersion.V1_16, + version: eks.KubernetesVersion.V1_18, }); // apply a kubernetes manifest to the cluster @@ -144,7 +145,7 @@ Creating a new cluster is done using the `Cluster` or `FargateCluster` construct ```typescript new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, }); ``` @@ -152,7 +153,7 @@ You can also use `FargateCluster` to provision a cluster that uses only fargate ```typescript new eks.FargateCluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, }); ``` @@ -176,7 +177,7 @@ At cluster instantiation time, you can customize the number of instances and the ```typescript new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, defaultCapacity: 5, defaultCapacityInstance: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.SMALL), }); @@ -188,7 +189,7 @@ Additional customizations are available post instantiation. To apply them, set t ```typescript const cluster = new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, defaultCapacity: 0, }); @@ -230,6 +231,8 @@ cluster.addNodegroupCapacity('extra-ng', { > For more details visit [Launch Template Support](https://docs.aws.amazon.com/en_ca/eks/latest/userguide/launch-templates.html). +Graviton 2 instance types are supported including `c6g`, `m6g`, `r6g` and `t4g`. + ### Fargate profiles AWS Fargate is a technology that provides on-demand, right-sized compute @@ -267,7 +270,7 @@ The following code defines an Amazon EKS cluster with a default Fargate Profile ```ts const cluster = new eks.FargateCluster(this, 'MyCluster', { - version: eks.KubernetesVersion.V1_16, + version: eks.KubernetesVersion.V1_18, }); ``` @@ -313,7 +316,7 @@ You can also configure the cluster to use an auto-scaling group as the default c ```ts cluster = new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, defaultCapacityType: eks.DefaultCapacityType.EC2, }); ``` @@ -391,7 +394,7 @@ You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/ ```typescript const cluster = new eks.Cluster(this, 'hello-eks', { - version: eks.KubernetesVersion.V1_16, + version: eks.KubernetesVersion.V1_18, endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC. }); ``` @@ -404,7 +407,7 @@ You can specify the VPC of the cluster using the `vpc` and `vpcSubnets` properti const vpc = new ec2.Vpc(this, 'Vpc'); new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, vpc, vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }] }); @@ -444,7 +447,7 @@ The resources are created in the cluster by running `kubectl apply` from a pytho ```typescript const cluster = new eks.Cluster(this, 'hello-eks', { - version: eks.KubernetesVersion.V1_16, + version: eks.KubernetesVersion.V1_18, kubectlEnvironment: { 'http_proxy': 'http://proxy.myproxy.com' } @@ -512,7 +515,7 @@ When you create a cluster, you can specify a `mastersRole`. The `Cluster` constr ```ts const role = new iam.Role(...); new eks.Cluster(this, 'HelloEKS', { - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, mastersRole: role, }); ``` @@ -811,6 +814,127 @@ const chart2 = cluster.addHelmChart(...); chart2.node.addDependency(chart1); ``` +#### CDK8s Charts + +[CDK8s](https://cdk8s.io/) is an open-source library that enables Kubernetes manifest authoring using familiar programming languages. It is founded on the same technologies as the AWS CDK, such as [`constructs`](https://github.com/aws/constructs) and [`jsii`](https://github.com/aws/jsii). + +> To learn more about cdk8s, visit the [Getting Started](https://github.com/awslabs/cdk8s/tree/master/docs/getting-started) tutorials. + +The EKS module natively integrates with cdk8s and allows you to apply cdk8s charts on AWS EKS clusters via the `cluster.addCdk8sChart` method. + +In addition to `cdk8s`, you can also use [`cdk8s+`](https://github.com/awslabs/cdk8s/tree/master/packages/cdk8s-plus), which provides higher level abstraction for the core kubernetes api objects. +You can think of it like the `L2` constructs for Kubernetes. Any other `cdk8s` based libraries are also supported, for example [`cdk8s-debore`](https://github.com/toricls/cdk8s-debore). + +To get started, add the following dependencies to your `package.json` file: + +```json +"dependencies": { + "cdk8s": "0.30.0", + "cdk8s-plus": "0.30.0", + "constructs": "3.0.4" +} +``` + +> Note that the version of `cdk8s` must be `>=0.30.0`. + +Similarly to how you would create a stack by extending `@aws-cdk/core.Stack`, we recommend you create a chart of your own that extends `cdk8s.Chart`, +and add your kubernetes resources to it. You can use `aws-cdk` construct attributes and properties inside your `cdk8s` construct freely. + +In this example we create a chart that accepts an `s3.Bucket` and passes its name to a kubernetes pod as an environment variable. + +Notice that the chart must accept a `constructs.Construct` type as its scope, not an `@aws-cdk/core.Construct` as you would normally use. +For this reason, to avoid possible confusion, we will create the chart in a separate file: + +`+ my-chart.ts` +```ts +import * as s3 from '@aws-cdk/aws-s3'; +import * as constructs from 'constructs'; +import * as cdk8s from 'cdk8s'; +import * as kplus from 'cdk8s-plus'; + +export interface MyChartProps { + readonly bucket: s3.Bucket; +} + +export class MyChart extends cdk8s.Chart { + constructor(scope: constructs.Construct, id: string, props: MyChartProps) { + super(scope, id); + + new kplus.Pod(this, 'Pod', { + spec: { + containers: [ + new kplus.Container({ + image: 'my-image', + env: { + BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), + }, + }), + ], + }, + }); + } +} +``` + +Then, in your AWS CDK app: + +```ts +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk8s from 'cdk8s'; +import { MyChart } from './my-chart'; + +// some bucket.. +const bucket = new s3.Bucket(this, 'Bucket'); + +// create a cdk8s chart and use `cdk8s.App` as the scope. +const myChart = new MyChart(new cdk8s.App(), 'MyChart', { bucket }); + +// add the cdk8s chart to the cluster +cluster.addCdk8sChart('my-chart', myChart); +``` + +##### Custom CDK8s Constructs + +You can also compose a few stock `cdk8s+` constructs into your own custom construct. However, since mixing scopes between `aws-cdk` and `cdk8s` is currently not supported, the `Construct` class +you'll need to use is the one from the [`constructs`](https://github.com/aws/constructs) module, and not from `@aws-cdk/core` like you normally would. +This is why we used `new cdk8s.App()` as the scope of the chart above. + +```ts +import * as constructs from 'constructs'; +import * as cdk8s from 'cdk8s'; +import * as kplus from 'cdk8s-plus'; + +export interface LoadBalancedWebService { + readonly port: number; + readonly image: string; + readonly replicas: number; +} + +export class LoadBalancedWebService extends constructs.Construct { + constructor(scope: constructs.Construct, id: string, props: LoadBalancedWebService) { + super(scope, id); + + const deployment = new kplus.Deployment(chart, 'Deployment', { + spec: { + replicas: props.replicas, + podSpecTemplate: { + containers: [ new kplus.Container({ image: props.image }) ] + } + }, + }); + + deployment.expose({port: props.port, serviceType: kplus.ServiceType.LOAD_BALANCER}) + + } +} +``` + +##### Manually importing k8s specs and CRD's + +If you find yourself unable to use `cdk8s+`, or just like to directly use the `k8s` native objects or CRD's, you can do so by manually importing them using the `cdk8s-cli`. + +See [Importing kubernetes objects](https://github.com/awslabs/cdk8s/tree/master/packages/cdk8s-cli#import) for detailed instructions. + ## Patching Kubernetes Resources The `KubernetesPatch` construct can be used to update existing kubernetes diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index cf1b74743a1f9..7388c497a9906 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -132,6 +132,16 @@ export interface ICluster extends IResource, ec2.IConnectable { * @returns a `HelmChart` construct */ addHelmChart(id: string, options: HelmChartOptions): HelmChart; + + /** + * Defines a CDK8s chart in this cluster. + * + * @param id logical id of this chart. + * @param chart the cdk8s chart. + * @returns a `KubernetesManifest` construct representing the chart. + */ + addCdk8sChart(id: string, chart: Construct): KubernetesManifest; + } /** @@ -568,6 +578,11 @@ export class KubernetesVersion { */ public static readonly V1_17 = KubernetesVersion.of('1.17'); + /** + * Kubernetes version 1.18 + */ + public static readonly V1_18 = KubernetesVersion.of('1.18'); + /** * Custom cluster version * @param version custom version number @@ -617,6 +632,25 @@ abstract class ClusterBase extends Resource implements ICluster { public addHelmChart(id: string, options: HelmChartOptions): HelmChart { return new HelmChart(this, `chart-${id}`, { cluster: this, ...options }); } + + /** + * Defines a CDK8s chart in this cluster. + * + * @param id logical id of this chart. + * @param chart the cdk8s chart. + * @returns a `KubernetesManifest` construct representing the chart. + */ + public addCdk8sChart(id: string, chart: Construct): KubernetesManifest { + + const cdk8sChart = chart as any; + + // see https://github.com/awslabs/cdk8s/blob/master/packages/cdk8s/src/chart.ts#L84 + if (typeof cdk8sChart.toJson !== 'function') { + throw new Error(`Invalid cdk8s chart. Must contain a 'toJson' method, but found ${typeof cdk8sChart.toJson}`); + } + + return this.addManifest(id, ...cdk8sChart.toJson()); + } } /** diff --git a/packages/@aws-cdk/aws-eks/lib/instance-types.ts b/packages/@aws-cdk/aws-eks/lib/instance-types.ts index 0656757bd0120..e08b3e43054cd 100644 --- a/packages/@aws-cdk/aws-eks/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-eks/lib/instance-types.ts @@ -2,5 +2,5 @@ export const INSTANCE_TYPES = { gpu: ['p2', 'p3', 'g2', 'g3', 'g4'], inferentia: ['inf1'], graviton: ['a1'], - graviton2: ['c6g', 'm6g', 'r6g'], + graviton2: ['c6g', 'm6g', 'r6g', 't4g'], }; diff --git a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts index 148e65166e1da..5fead1f4818e4 100644 --- a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts @@ -371,6 +371,10 @@ export class LegacyCluster extends Resource implements ICluster { throw new Error('legacy cluster does not support adding helm charts'); } + public addCdk8sChart(_id: string, _chart: Construct): KubernetesManifest { + throw new Error('legacy cluster does not support adding cdk8s charts'); + } + /** * Opportunistically tag subnets with the required tags. * @@ -429,6 +433,10 @@ class ImportedCluster extends Resource implements ICluster { throw new Error('legacy cluster does not support adding helm charts'); } + public addCdk8sChart(_id: string, _chart: Construct): KubernetesManifest { + throw new Error('legacy cluster does not support adding cdk8s charts'); + } + public get vpc() { if (!this.props.vpc) { throw new Error('"vpc" is not defined for this imported cluster'); diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 7aa6b16bdd7d4..919b153e5f811 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EKS", @@ -79,7 +80,9 @@ "cfn2ts": "0.0.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", - "sinon": "^9.1.0" + "sinon": "^9.1.0", + "cdk8s-plus": "^0.29.0", + "cdk8s": "^0.32.0" }, "dependencies": { "@aws-cdk/aws-autoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index 9b4e03ad40ebe..ac924ac20038d 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -10,7 +10,7 @@ class EksClusterStack extends cdk.Stack { const cluster = new eks.Cluster(this, 'EKSCluster', { vpc, - version: eks.KubernetesVersion.V1_16, + version: eks.KubernetesVersion.V1_18, }); /// !show diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json index 13bba49cd6cf4..bf6763aa4ece1 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json @@ -772,7 +772,7 @@ ] }, "Config": { - "version": "1.16", + "version": "1.18", "roleArn": { "Fn::GetAtt": [ "ClusterRoleFA261979", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts index 581acb75d033e..b359c548c43ae 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts @@ -5,6 +5,9 @@ import { App } from '@aws-cdk/core'; import * as eks from '../lib'; import { TestStack } from './util'; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_18; + + class EksClusterStack extends TestStack { constructor(scope: App, id: string) { super(scope, id); @@ -21,7 +24,7 @@ class EksClusterStack extends TestStack { vpc, mastersRole, defaultCapacity: 2, - version: eks.KubernetesVersion.V1_16, + version: CLUSTER_VERSION, endpointAccess: eks.EndpointAccess.PRIVATE, }); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index b015b80826d45..1d47ee8f4d1c1 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -949,7 +949,7 @@ ] }, "Config": { - "version": "1.17", + "version": "1.18", "roleArn": { "Fn::GetAtt": [ "ClusterRoleFA261979", @@ -1688,7 +1688,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "t2.medium", "IamInstanceProfile": { @@ -2007,7 +2007,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2arm64recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2arm64recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "m6g.medium", "IamInstanceProfile": { @@ -2326,7 +2326,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsservicebottlerocketawsk8s117x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsservicebottlerocketawsk8s118x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "t3.small", "IamInstanceProfile": { @@ -2659,7 +2659,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "t3.large", "IamInstanceProfile": { @@ -3011,7 +3011,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "inf1.2xlarge", "IamInstanceProfile": { @@ -3342,11 +3342,6 @@ } ], "ForceUpdateEnabled": true, - "ScalingConfig": { - "DesiredSize": 1, - "MaxSize": 1, - "MinSize": 1 - }, "LaunchTemplate": { "Id": { "Ref": "LaunchTemplate" @@ -3357,6 +3352,11 @@ "DefaultVersionNumber" ] } + }, + "ScalingConfig": { + "DesiredSize": 1, + "MaxSize": 1, + "MinSize": 1 } } }, @@ -3416,6 +3416,43 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "Clustermanifestcdk8schart6B444884": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"data\":{\"clusterName\":\"", + { + "Ref": "Cluster9EE0221C" + }, + "\"},\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"chart-config-map-configmap-cccf3117\"}}]" + ] + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + } + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "ClustermanifestnginxnamespaceA68B4CE0": { "Type": "Custom::AWSCDK-EKS-KubernetesResource", "Properties": { @@ -3710,7 +3747,7 @@ }, "/", { - "Ref": "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfS3Bucket0E3AAAB9" + "Ref": "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cS3Bucket1CB7A187" }, "/", { @@ -3720,7 +3757,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfS3VersionKey8C90C4EF" + "Ref": "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cS3VersionKey7C13F243" } ] } @@ -3733,7 +3770,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfS3VersionKey8C90C4EF" + "Ref": "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cS3VersionKey7C13F243" } ] } @@ -3749,17 +3786,17 @@ "Arn" ] }, - "referencetoawscdkeksclustertestAssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3BucketAAB1A713Ref": { - "Ref": "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3Bucket086F94BB" + "referencetoawscdkeksclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket1516DB0ARef": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9" }, - "referencetoawscdkeksclustertestAssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKey481F0807Ref": { - "Ref": "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKeyA4B5C598" + "referencetoawscdkeksclustertestAssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKey2B8F3ED3Ref": { + "Ref": "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F" }, - "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3Bucket85526CA7Ref": { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, - "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey46BA60C1Ref": { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } } } @@ -3777,7 +3814,7 @@ }, "/", { - "Ref": "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11S3BucketC456B560" + "Ref": "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64S3BucketAA0CCE0D" }, "/", { @@ -3787,7 +3824,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11S3VersionKeyA1DAD649" + "Ref": "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64S3VersionKey3012C8DD" } ] } @@ -3800,7 +3837,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11S3VersionKeyA1DAD649" + "Ref": "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64S3VersionKey3012C8DD" } ] } @@ -3843,11 +3880,11 @@ "ClusterSecurityGroupId" ] }, - "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3Bucket85526CA7Ref": { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, - "referencetoawscdkeksclustertestAssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey46BA60C1Ref": { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } } } @@ -4271,7 +4308,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" }, "S3Key": { "Fn::Join": [ @@ -4284,7 +4321,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -4297,7 +4334,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5" + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" } ] } @@ -4458,29 +4495,29 @@ } }, "Parameters": { - "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3Bucket086F94BB": { + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3Bucket14D204F9": { "Type": "String", - "Description": "S3 bucket for asset \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" + "Description": "S3 bucket for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" }, - "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4S3VersionKeyA4B5C598": { + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaS3VersionKeyDE8A2F1F": { "Type": "String", - "Description": "S3 key for asset version \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" + "Description": "S3 key for asset version \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" }, - "AssetParameters7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4ArtifactHash9B26D532": { + "AssetParameters87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dbaArtifactHash54822A43": { "Type": "String", - "Description": "Artifact hash for asset \"7997347617940455774a736af2df2e6238c13b755ad25353a3d081446cfc80a4\"" + "Description": "Artifact hash for asset \"87b1e2c41f84590d14f7ab8cb0f338c51d6fa3efe78943867af07fa959593dba\"" }, - "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3BucketD25BCC90": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { "Type": "String", - "Description": "S3 bucket for asset \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" + "Description": "S3 bucket for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1S3VersionKey72DFE7A5": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F": { "Type": "String", - "Description": "S3 key for asset version \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" + "Description": "S3 key for asset version \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, - "AssetParameters34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1ArtifactHashAA0236EE": { + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1ArtifactHashA521A16F": { "Type": "String", - "Description": "Artifact hash for asset \"34131c2e554ab57ad3a47fc0a13173a5c2a4b65a7582fe9622277b3d04c8e1e1\"" + "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" }, "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { "Type": "String", @@ -4530,45 +4567,45 @@ "Type": "String", "Description": "Artifact hash for asset \"2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43\"" }, - "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfS3Bucket0E3AAAB9": { + "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cS3Bucket1CB7A187": { "Type": "String", - "Description": "S3 bucket for asset \"04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cf\"" + "Description": "S3 bucket for asset \"a69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0c\"" }, - "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfS3VersionKey8C90C4EF": { + "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cS3VersionKey7C13F243": { "Type": "String", - "Description": "S3 key for asset version \"04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cf\"" + "Description": "S3 key for asset version \"a69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0c\"" }, - "AssetParameters04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cfArtifactHash1CDB946A": { + "AssetParametersa69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0cArtifactHashBADE945D": { "Type": "String", - "Description": "Artifact hash for asset \"04fa2d485a51abd8261468eb6fa053d3a72242fc068fa75683232a52960b30cf\"" + "Description": "Artifact hash for asset \"a69aadbed84d554dd9f2eb7987ffe5d8f76b53a86f1909059df07050e57bef0c\"" }, - "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11S3BucketC456B560": { + "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64S3BucketAA0CCE0D": { "Type": "String", - "Description": "S3 bucket for asset \"215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11\"" + "Description": "S3 bucket for asset \"8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64\"" }, - "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11S3VersionKeyA1DAD649": { + "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64S3VersionKey3012C8DD": { "Type": "String", - "Description": "S3 key for asset version \"215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11\"" + "Description": "S3 key for asset version \"8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64\"" }, - "AssetParameters215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11ArtifactHash95B6846D": { + "AssetParameters8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64ArtifactHashFBD3EEB7": { "Type": "String", - "Description": "Artifact hash for asset \"215e9f40bd76e7102c690b24b0922eb4963d2d24938eec175e107db683455d11\"" + "Description": "Artifact hash for asset \"8a716921b900641df041bd35d4d1817780375a004ec952e2cab0cd621c5a4d64\"" }, - "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/eks/optimized-ami/1.17/amazon-linux-2/recommended/image_id" + "Default": "/aws/service/eks/optimized-ami/1.18/amazon-linux-2/recommended/image_id" }, - "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2arm64recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2arm64recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/eks/optimized-ami/1.17/amazon-linux-2-arm64/recommended/image_id" + "Default": "/aws/service/eks/optimized-ami/1.18/amazon-linux-2-arm64/recommended/image_id" }, - "SsmParameterValueawsservicebottlerocketawsk8s117x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsservicebottlerocketawsk8s118x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/bottlerocket/aws-k8s-1.17/x86_64/latest/image_id" + "Default": "/aws/service/bottlerocket/aws-k8s-1.18/x86_64/latest/image_id" }, - "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/eks/optimized-ami/1.17/amazon-linux-2-gpu/recommended/image_id" + "Default": "/aws/service/eks/optimized-ami/1.18/amazon-linux-2-gpu/recommended/image_id" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json index 7d8d3e659bd0d..bf7ee135b983d 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json @@ -755,7 +755,7 @@ ] } ], - "Version": "1.16" + "Version": "1.18" } }, "EKSClusterNodesInstanceSecurityGroup460A275E": { @@ -954,7 +954,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "t2.medium", "IamInstanceProfile": { @@ -1086,9 +1086,9 @@ } }, "Parameters": { - "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsserviceeksoptimizedami118amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2/recommended/image_id" + "Default": "/aws/service/eks/optimized-ami/1.18/amazon-linux-2/recommended/image_id" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts index 81a749fef2c93..6bf5fd6f7dad0 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts @@ -4,6 +4,8 @@ import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; import { TestStack } from './util'; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_18; + class EksClusterStack extends TestStack { constructor(scope: cdk.App, id: string) { super(scope, id); @@ -15,7 +17,7 @@ class EksClusterStack extends TestStack { const cluster = new eks.LegacyCluster(this, 'EKSCluster', { vpc, defaultCapacity: 0, - version: eks.KubernetesVersion.V1_16, + version: CLUSTER_VERSION, secretsEncryptionKey, }); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index 689d6cee92068..d8522eb543619 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -3,6 +3,9 @@ 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 { App, CfnOutput, Duration, Token, Fn } from '@aws-cdk/core'; +import * as cdk8s from 'cdk8s'; +import * as kplus from 'cdk8s-plus'; +import * as constructs from 'constructs'; import * as eks from '../lib'; import * as hello from './hello-k8s'; import { Pinger } from './pinger/pinger'; @@ -32,7 +35,7 @@ class EksClusterStack extends TestStack { vpc: this.vpc, mastersRole, defaultCapacity: 2, - version: eks.KubernetesVersion.V1_17, + version: eks.KubernetesVersion.V1_18, secretsEncryptionKey, }); @@ -58,6 +61,8 @@ class EksClusterStack extends TestStack { this.assertSimpleHelmChart(); + this.assertSimpleCdk8sChart(); + this.assertCreateNamespace(); this.assertServiceAccount(); @@ -101,6 +106,26 @@ class EksClusterStack extends TestStack { } + + private assertSimpleCdk8sChart() { + + class Chart extends cdk8s.Chart { + constructor(scope: constructs.Construct, ns: string, cluster: eks.ICluster) { + super(scope, ns); + + new kplus.ConfigMap(this, 'config-map', { + data: { + clusterName: cluster.clusterName, + }, + }); + + } + } + const app = new cdk8s.App(); + const chart = new Chart(app, 'Chart', this.cluster); + + this.cluster.addCdk8sChart('cdk8s-chart', chart); + } private assertSimpleHelmChart() { // deploy the Kubernetes dashboard through a helm chart this.cluster.addHelmChart('dashboard', { diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json index de489ef72836e..b8f3d7dad9a74 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.expected.json @@ -842,7 +842,7 @@ ] }, "Config": { - "version": "1.17", + "version": "1.18", "roleArn": { "Fn::GetAtt": [ "FargateClusterRole8E36B33A", diff --git a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts index 870b3059b1a2b..704878c4091a7 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.fargate-cluster.ts @@ -3,13 +3,16 @@ import { App } from '@aws-cdk/core'; import * as eks from '../lib'; import { TestStack } from './util'; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_18; + + class EksFargateClusterStack extends TestStack { constructor(scope: App, id: string) { super(scope, id); new eks.FargateCluster(this, 'FargateCluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index afd71faa9c308..10909690f99af 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -7,6 +7,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; +import * as cdk8s from 'cdk8s'; import * as constructs from 'constructs'; import { Test } from 'nodeunit'; import * as YAML from 'yaml'; @@ -17,16 +18,91 @@ import { testFixture, testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_18; export = { + 'throws when a non cdk8s chart construct is added as cdk8s chart'(test: Test) { + + const { stack } = testFixture(); + + const cluster = new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + }); + + // create a plain construct, not a cdk8s chart + const someConstruct = new constructs.Construct(stack, 'SomeConstruct'); + + test.throws(() => cluster.addCdk8sChart('chart', someConstruct), /Invalid cdk8s chart. Must contain a \'toJson\' method, but found undefined/); + test.done(); + + }, + + 'throws when a core construct is added as cdk8s chart'(test: Test) { + + const { stack } = testFixture(); + + const cluster = new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + }); + + // create a plain construct, not a cdk8s chart + const someConstruct = new cdk.Construct(stack, 'SomeConstruct'); + + test.throws(() => cluster.addCdk8sChart('chart', someConstruct), /Invalid cdk8s chart. Must contain a \'toJson\' method, but found undefined/); + test.done(); + + }, + + 'cdk8s chart can be added to cluster'(test: Test) { + + const { stack } = testFixture(); + + const cluster = new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + }); + + const app = new cdk8s.App(); + const chart = new cdk8s.Chart(app, 'Chart'); + + new cdk8s.ApiObject(chart, 'FakePod', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { + name: 'fake-pod', + labels: { + // adding aws-cdk token to cdk8s chart + clusterName: cluster.clusterName, + }, + }, + }); + + cluster.addCdk8sChart('cdk8s-chart', chart); + + expect(stack).to(haveResourceLike('Custom::AWSCDK-EKS-KubernetesResource', { + Manifest: { + 'Fn::Join': [ + '', + [ + '[{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"clusterName":"', + { + Ref: 'Cluster9EE0221C', + }, + '"},"name":"fake-pod"}}]', + ], + ], + }, + })); + + test.done(); + }, + 'cluster connections include both control plane and cluster security group'(test: Test) { const { stack } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); test.deepEqual(cluster.connections.securityGroups.map(sg => stack.resolve(sg.securityGroupId)), [ @@ -46,7 +122,7 @@ export = { constructor(scope: constructs.Construct, id: string, props: { sg: ec2.ISecurityGroup, vpc: ec2.IVpc }) { super(scope, id); this.eksCluster = new eks.Cluster(this, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, securityGroup: props.sg, vpc: props.vpc, }); @@ -84,7 +160,7 @@ export = { constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.eksCluster = new eks.Cluster(this, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } @@ -135,7 +211,7 @@ export = { constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.eksCluster = new eks.Cluster(this, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } @@ -177,7 +253,7 @@ export = { constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.eksCluster = new eks.Cluster(this, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } @@ -210,7 +286,7 @@ export = { constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.eksCluster = new eks.Cluster(this, 'Cluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } @@ -227,7 +303,7 @@ export = { instanceType: new ec2.InstanceType('t3.medium'), vpc: props.cluster.vpc, machineImage: new eks.EksOptimizedImage({ - kubernetesVersion: eks.KubernetesVersion.V1_16.version, + kubernetesVersion: CLUSTER_VERSION.version, nodeType: eks.NodeType.STANDARD, }), }); @@ -257,7 +333,7 @@ export = { constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.eksCluster = new eks.Cluster(this, 'EKSCluster', { - version: eks.KubernetesVersion.V1_17, + version: CLUSTER_VERSION, }); } } @@ -291,7 +367,7 @@ export = { expect(stack).to(haveResourceLike('Custom::AWSCDK-EKS-Cluster', { Config: { roleArn: { 'Fn::GetAtt': ['ClusterRoleFA261979', 'Arn'] }, - version: '1.16', + version: '1.18', resourcesVpcConfig: { securityGroupIds: [{ 'Fn::GetAtt': ['ClusterControlPlaneSecurityGroupD274242C', 'GroupId'] }], subnetIds: [ @@ -1132,7 +1208,7 @@ export = { const { app, stack } = testFixtureNoVpc(); // WHEN - new eks.EksOptimizedImage({ kubernetesVersion: '1.15' }).getImage(stack); + new eks.EksOptimizedImage({ kubernetesVersion: '1.18' }).getImage(stack); // THEN const assembly = app.synth(); @@ -1143,7 +1219,7 @@ export = { ), 'EKS STANDARD AMI should be in ssm parameters'); test.ok(Object.entries(parameters).some( ([k, v]) => k.startsWith('SsmParameterValueawsserviceeksoptimizedami') && - (v as any).Default.includes('/1.15/'), + (v as any).Default.includes('/1.18/'), ), 'kubernetesVersion should be in ssm parameters'); test.done(); }, @@ -1186,6 +1262,48 @@ export = { test.done(); }, + 'addNodegroupCapacity with T4g instance type comes with nodegroup with correct AmiType'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'cluster', { + defaultCapacity: 0, + version: CLUSTER_VERSION, + defaultCapacityInstance: new ec2.InstanceType('t4g.medium'), + }).addNodegroupCapacity('ng', { + instanceType: new ec2.InstanceType('t4g.medium'), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::EKS::Nodegroup', { + AmiType: 'AL2_ARM_64', + })); + test.done(); + }, + + 'addAutoScalingGroupCapacity with T4g instance type comes with nodegroup with correct AmiType'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'cluster', { + defaultCapacity: 0, + version: CLUSTER_VERSION, + }).addAutoScalingGroupCapacity('ng', { + instanceType: new ec2.InstanceType('t4g.medium'), + }); + + // THEN + const assembly = app.synth(); + const parameters = assembly.getStackByName(stack.stackName).template.Parameters; + test.ok(Object.entries(parameters).some( + ([k, v]) => k.startsWith('SsmParameterValueawsserviceeksoptimizedami') && + (v as any).Default.includes('amazon-linux-2-arm64/'), + ), 'Amazon Linux 2 AMI for ARM64 should be in ssm parameters'); + test.done(); + }, + 'EKS-Optimized AMI with GPU support when addAutoScalingGroupCapacity'(test: Test) { // GIVEN const { app, stack } = testFixtureNoVpc(); @@ -1233,7 +1351,7 @@ export = { const { app, stack } = testFixtureNoVpc(); // WHEN - new BottleRocketImage({ kubernetesVersion: '1.17' }).getImage(stack); + new BottleRocketImage({ kubernetesVersion: '1.18' }).getImage(stack); // THEN const assembly = app.synth(); @@ -1244,7 +1362,7 @@ export = { ), 'BottleRocket AMI should be in ssm parameters'); test.ok(Object.entries(parameters).some( ([k, v]) => k.startsWith('SsmParameterValueawsservicebottlerocketaws') && - (v as any).Default.includes('/aws-k8s-1.17/'), + (v as any).Default.includes('/aws-k8s-1.18/'), ), 'kubernetesVersion should be in ssm parameters'); test.done(); }, @@ -1264,7 +1382,7 @@ export = { Config: { name: 'my-cluster-name', roleArn: { 'Fn::GetAtt': ['MyClusterRoleBA20FE72', 'Arn'] }, - version: '1.16', + version: '1.18', resourcesVpcConfig: { securityGroupIds: [ { 'Fn::GetAtt': ['MyClusterControlPlaneSecurityGroup6B658F79', 'GroupId'] }, diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index 8f9409331d8a6..c21f81ded307a 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -7,7 +7,7 @@ import { testFixture } from './util'; /* eslint-disable max-len */ -const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_18; export = { 'create nodegroup correctly'(test: Test) { diff --git a/packages/@aws-cdk/aws-elasticache/.npmignore b/packages/@aws-cdk/aws-elasticache/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-elasticache/.npmignore +++ b/packages/@aws-cdk/aws-elasticache/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index ab6e459939236..64b7de26d8033 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElastiCache", diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore b/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore +++ b/packages/@aws-cdk/aws-elasticbeanstalk/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index 1f907f7aea14a..4072f080afd6b 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticBeanstalk", diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore index 1a36d0617073e..4a398e9d7d239 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancing/.npmignore @@ -24,3 +24,5 @@ tsconfig.json **/cdk.out junit.xml jest.config.js + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index a2f49664d154d..21c9fb0106eed 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancing", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore index d7886d9208116..dffe09131a636 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/.npmignore @@ -22,4 +22,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json index 9dd9b82ea2479..ab1cb8cca2023 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json @@ -67,7 +67,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore index a0bf754e3cc79..c28149f1ec41d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/.npmignore @@ -22,4 +22,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index d5148b9709815..82ce69cee6101 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -67,7 +67,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore b/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore index 1a36d0617073e..4a398e9d7d239 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/.npmignore @@ -24,3 +24,5 @@ tsconfig.json **/cdk.out junit.xml jest.config.js + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index f674af8d93d7b..8e8bd7d3a8319 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ElasticLoadBalancingV2", diff --git a/packages/@aws-cdk/aws-elasticsearch/.npmignore b/packages/@aws-cdk/aws-elasticsearch/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-elasticsearch/.npmignore +++ b/packages/@aws-cdk/aws-elasticsearch/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index 69947f1186589..5f42892c18a27 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Elasticsearch", diff --git a/packages/@aws-cdk/aws-emr/.npmignore b/packages/@aws-cdk/aws-emr/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-emr/.npmignore +++ b/packages/@aws-cdk/aws-emr/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-emr/package.json b/packages/@aws-cdk/aws-emr/package.json index da8094218409d..49f0f0e96cfd8 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EMR", diff --git a/packages/@aws-cdk/aws-events-targets/.npmignore b/packages/@aws-cdk/aws-events-targets/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-events-targets/.npmignore +++ b/packages/@aws-cdk/aws-events-targets/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 686c26c270bbc..99bb9364ce7f7 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -23,6 +23,7 @@ Currently supported are: * Queue a Batch job * Make an AWS API call * Put a record to a Kinesis stream +* Put a record to a Kinesis Data Firehose stream See the README of the `@aws-cdk/aws-events` library for more information on EventBridge. diff --git a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts index e38287c0cdc07..55eac4cadd32f 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts @@ -7,6 +7,15 @@ import { singletonEventRole } from './util'; * Customize the CodeBuild Event Target */ export interface CodeBuildProjectProps { + + /** + * The role to assume before invoking the target + * (i.e., the codebuild) when the given rule is triggered. + * + * @default - a new role will be created + */ + readonly eventRole?: iam.IRole; + /** * The event to send to CodeBuild * @@ -33,7 +42,7 @@ export class CodeBuildProject implements events.IRuleTarget { return { id: '', arn: this.project.projectArn, - role: singletonEventRole(this.project, [ + role: this.props.eventRole || singletonEventRole(this.project, [ new iam.PolicyStatement({ actions: ['codebuild:StartBuild'], resources: [this.project.projectArn], diff --git a/packages/@aws-cdk/aws-events-targets/lib/index.ts b/packages/@aws-cdk/aws-events-targets/lib/index.ts index 7031423e6b739..e771a74d8c4eb 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/index.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/index.ts @@ -9,3 +9,4 @@ export * from './ecs-task-properties'; export * from './ecs-task'; export * from './state-machine'; export * from './kinesis-stream'; +export * from './kinesis-firehose-stream'; diff --git a/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts b/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts new file mode 100644 index 0000000000000..d861be96aa7f4 --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts @@ -0,0 +1,47 @@ +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import { singletonEventRole } from './util'; + +/** + * Customize the Firehose Stream Event Target + */ +export interface KinesisFirehoseStreamProps { + /** + * The message to send to the stream. + * + * Must be a valid JSON text passed to the target stream. + * + * @default - the entire Event Bridge event + */ + readonly message?: events.RuleTargetInput; +} + + +/** + * Customize the Firehose Stream Event Target + */ +export class KinesisFirehoseStream implements events.IRuleTarget { + + constructor(private readonly stream: firehose.CfnDeliveryStream, private readonly props: KinesisFirehoseStreamProps = {}) { + } + + /** + * Returns a RuleTarget that can be used to trigger this Firehose Stream as a + * result from a Event Bridge event. + */ + public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { + const policyStatements = [new iam.PolicyStatement({ + actions: ['firehose:PutRecord', 'firehose:PutRecordBatch'], + resources: [this.stream.attrArn], + })]; + + return { + id: '', + arn: this.stream.attrArn, + role: singletonEventRole(this.stream, policyStatements), + input: this.props.message, + targetResource: this.stream, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 14764b6fcb0af..d1574a3c058c2 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -48,12 +48,10 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "node ./build-tools/gen.js" }, "cdk-build": { - "pre": [ - "node ./build-tools/gen.js" - ], "jest": true, "env": { "AWSLINT_BASE_CONSTRUCT": true @@ -76,47 +74,50 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "aws-sdk": "^2.739.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kinesis": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", - "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", - "@aws-cdk/aws-batch": "0.0.0", - "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.4" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-kinesis": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", - "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", + "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", - "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/core": "0.0.0", - "constructs": "^3.0.4", - "@aws-cdk/aws-kinesis": "0.0.0" + "constructs": "^3.0.4" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts index 7cec0549d4e05..c7bb3d59b68e8 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts @@ -1,105 +1,123 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as events from '@aws-cdk/aws-events'; -import { Stack } from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import { CfnElement, Stack } from '@aws-cdk/core'; import * as targets from '../../lib'; -test('use codebuild project as an eventrule target', () => { - // GIVEN - const stack = new Stack(); - const project = new codebuild.PipelineProject(stack, 'MyProject'); - const rule = new events.Rule(stack, 'Rule', { - schedule: events.Schedule.expression('rate(1 min)'), +describe('CodeBuild event target', () => { + let stack: Stack; + let project: codebuild.PipelineProject; + let projectArn: any; + + beforeEach(() => { + stack = new Stack(); + project = new codebuild.PipelineProject(stack, 'MyProject'); + projectArn = { 'Fn::GetAtt': ['MyProject39F7B0AE', 'Arn'] }; }); - // WHEN - rule.addTarget(new targets.CodeBuildProject(project)); + test('use codebuild project as an eventrule target', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); - // THEN - expect(stack).to(haveResource('AWS::Events::Rule', { - Targets: [ - { - Arn: { - 'Fn::GetAtt': [ - 'MyProject39F7B0AE', - 'Arn', - ], - }, - Id: 'Target0', - RoleArn: { - 'Fn::GetAtt': [ - 'MyProjectEventsRole5B7D93F5', - 'Arn', - ], - }, - }, - ], - })); + // WHEN + rule.addTarget(new targets.CodeBuildProject(project)); - expect(stack).to(haveResource('AWS::IAM::Role', { - AssumeRolePolicyDocument: { - Statement: [ + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { Service: 'events.amazonaws.com' }, + Arn: projectArn, + Id: 'Target0', + RoleArn: { 'Fn::GetAtt': ['MyProjectEventsRole5B7D93F5', 'Arn'] }, }, ], - Version: '2012-10-17', - }, - })); + })); + + expect(stack).to(haveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'events.amazonaws.com' }, + }, + ], + Version: '2012-10-17', + }, + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'codebuild:StartBuild', + Effect: 'Allow', + Resource: projectArn, + }, + ], + Version: '2012-10-17', + }, + })); + }); - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ + test('specifying event for codebuild project target', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 hour)'), + }); + + // WHEN + const eventInput = { + buildspecOverride: 'buildspecs/hourly.yml', + }; + + rule.addTarget( + new targets.CodeBuildProject(project, { + event: events.RuleTargetInput.fromObject(eventInput), + }), + ); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ { - Action: 'codebuild:StartBuild', - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': [ - 'MyProject39F7B0AE', - 'Arn', - ], + Arn: projectArn, + Id: 'Target0', + Input: JSON.stringify(eventInput), + RoleArn: { + 'Fn::GetAtt': ['MyProjectEventsRole5B7D93F5', 'Arn'], }, }, ], - Version: '2012-10-17', - }, - })); -}); - -test('specifying event for codebuild project target', () => { - // GIVEN - const stack = new Stack(); - const project = new codebuild.PipelineProject(stack, 'MyProject'); - const rule = new events.Rule(stack, 'Rule', { - schedule: events.Schedule.expression('rate(1 hour)'), + })); }); - // WHEN - const eventInput = { - buildspecOverride: 'buildspecs/hourly.yml', - }; + test('specifying custom role for codebuild project target', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 hour)'), + }); + const role = new iam.Role(stack, 'MyExampleRole', { + assumedBy: new iam.AnyPrincipal(), + }); + const roleResource = role.node.defaultChild as CfnElement; + roleResource.overrideLogicalId('MyRole'); // to make it deterministic in the assertion below - rule.addTarget( - new targets.CodeBuildProject(project, { - event: events.RuleTargetInput.fromObject(eventInput), - }), - ); + // WHEN + rule.addTarget(new targets.CodeBuildProject(project, { eventRole: role })); - // THEN - expect(stack).to(haveResource('AWS::Events::Rule', { - Targets: [ - { - Arn: { - 'Fn::GetAtt': ['MyProject39F7B0AE', 'Arn'], - }, - Id: 'Target0', - Input: JSON.stringify(eventInput), - RoleArn: { - 'Fn::GetAtt': ['MyProjectEventsRole5B7D93F5', 'Arn'], + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: projectArn, + Id: 'Target0', + RoleArn: { 'Fn::GetAtt': ['MyRole', 'Arn'] }, }, - }, - ], - })); + ], + })); + }); }); diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index ed269b2e7b524..472583a9f36ca 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -161,7 +161,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.expected.json b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.expected.json new file mode 100644 index 0000000000000..8cc4f2c5bbe65 --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.expected.json @@ -0,0 +1,164 @@ +{ + "Resources": { + "firehosebucket84C8AE0B": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "firehoseroleDDC4CF0E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "firehoseroleDefaultPolicy3F3F850D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "firehosebucket84C8AE0B", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "firehosebucket84C8AE0B", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "firehoseroleDefaultPolicy3F3F850D", + "Roles": [ + { + "Ref": "firehoseroleDDC4CF0E" + } + ] + } + }, + "MyStream": { + "Type": "AWS::KinesisFirehose::DeliveryStream", + "Properties": { + "ExtendedS3DestinationConfiguration": { + "BucketARN": { + "Fn::GetAtt": [ + "firehosebucket84C8AE0B", + "Arn" + ] + }, + "RoleARN": { + "Fn::GetAtt": [ + "firehoseroleDDC4CF0E", + "Arn" + ] + } + } + } + }, + "MyStreamEventsRole5B6CC6AF": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStreamEventsRoleDefaultPolicy2089B49E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "firehose:PutRecord", + "firehose:PutRecordBatch" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyStream", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyStreamEventsRoleDefaultPolicy2089B49E", + "Roles": [ + { + "Ref": "MyStreamEventsRole5B6CC6AF" + } + ] + } + }, + "EveryMinute2BBCEA8F": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "MyStream", + "Arn" + ] + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "MyStreamEventsRole5B6CC6AF", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.ts b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.ts new file mode 100644 index 0000000000000..70c4ddf050e4f --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/integ.kinesis-firehose-stream.ts @@ -0,0 +1,33 @@ +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as targets from '../../lib'; + +// --------------------------------- +// Define a rule that triggers a put to a Kinesis stream every 1min. + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-firehose-event-target'); + +const bucket = new s3.Bucket(stack, 'firehose-bucket'); +const firehoseRole = new iam.Role(stack, 'firehose-role', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), +}); +const stream = new firehose.CfnDeliveryStream(stack, 'MyStream', { + extendedS3DestinationConfiguration: { + bucketArn: bucket.bucketArn, + roleArn: firehoseRole.roleArn, + }, +}); +bucket.grantReadWrite(firehoseRole); + +const event = new events.Rule(stack, 'EveryMinute', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), +}); + +event.addTarget(new targets.KinesisFirehoseStream(stream, {})); + +app.synth(); diff --git a/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/kinesis-firehose-stream.test.ts b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/kinesis-firehose-stream.test.ts new file mode 100644 index 0000000000000..556816853fee2 --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/test/kinesis-firehose/kinesis-firehose-stream.test.ts @@ -0,0 +1,80 @@ +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import * as events from '@aws-cdk/aws-events'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import { Stack } from '@aws-cdk/core'; +import * as targets from '../../lib'; + +describe('KinesisFirehoseStream event target', () => { + let stack: Stack; + let stream: firehose.CfnDeliveryStream; + let streamArn: any; + + beforeEach(() => { + stack = new Stack(); + stream = new firehose.CfnDeliveryStream(stack, 'MyStream'); + streamArn = { 'Fn::GetAtt': ['MyStream', 'Arn'] }; + }); + + describe('when added to an event rule as a target', () => { + let rule: events.Rule; + + beforeEach(() => { + rule = new events.Rule(stack, 'rule', { + schedule: events.Schedule.expression('rate(1 minute)'), + }); + }); + + describe('with default settings', () => { + beforeEach(() => { + rule.addTarget(new targets.KinesisFirehoseStream(stream)); + }); + + test("adds the stream's ARN and role to the targets of the rule", () => { + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: streamArn, + Id: 'Target0', + RoleArn: { 'Fn::GetAtt': ['MyStreamEventsRole5B6CC6AF', 'Arn'] }, + }, + ], + })); + }); + + test("creates a policy that has PutRecord and PutRecords permissions on the stream's ARN", () => { + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['firehose:PutRecord', 'firehose:PutRecordBatch'], + Effect: 'Allow', + Resource: streamArn, + }, + ], + Version: '2012-10-17', + }, + })); + }); + }); + + describe('with an explicit message', () => { + beforeEach(() => { + rule.addTarget(new targets.KinesisFirehoseStream(stream, { + message: events.RuleTargetInput.fromText('fooBar'), + })); + }); + + test('sets the input', () => { + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + Arn: streamArn, + Id: 'Target0', + Input: '"fooBar"', + }, + ], + })); + }); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-events/.npmignore b/packages/@aws-cdk/aws-events/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-events/.npmignore +++ b/packages/@aws-cdk/aws-events/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index db0bf52f65317..2dc1c35c9cded 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnEventBus } from './events.generated'; @@ -163,7 +163,7 @@ export class EventBus extends Resource implements IEventBus { throw new Error( '\'eventBusName\' and \'eventSourceName\' cannot both be provided', ); - } else if (eventBusName !== undefined) { + } else if (eventBusName !== undefined && !Token.isUnresolved(eventBusName)) { if (eventBusName === 'default') { throw new Error( '\'eventBusName\' must not be \'default\'', diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index fe8af611759f3..10eae38d9c95a 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Events", diff --git a/packages/@aws-cdk/aws-events/test/test.event-bus.ts b/packages/@aws-cdk/aws-events/test/test.event-bus.ts index c32824884c08e..a6c32885ec4a9 100644 --- a/packages/@aws-cdk/aws-events/test/test.event-bus.ts +++ b/packages/@aws-cdk/aws-events/test/test.event-bus.ts @@ -1,6 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; -import { CfnResource, Stack } from '@aws-cdk/core'; +import { Aws, CfnResource, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { EventBus } from '../lib'; @@ -170,6 +170,18 @@ export = { test.done(); }, + 'does not throw if eventBusName is a token'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN / THEN + test.doesNotThrow(() => new EventBus(stack, 'EventBus', { + eventBusName: Aws.STACK_NAME, + })); + + test.done(); + }, + 'event bus source name must follow pattern'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-eventschemas/.npmignore b/packages/@aws-cdk/aws-eventschemas/.npmignore index a7c5b49852b3b..c2827f80c26db 100644 --- a/packages/@aws-cdk/aws-eventschemas/.npmignore +++ b/packages/@aws-cdk/aws-eventschemas/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eventschemas/package.json b/packages/@aws-cdk/aws-eventschemas/package.json index 2ad4983c3be32..8180b30813736 100644 --- a/packages/@aws-cdk/aws-eventschemas/package.json +++ b/packages/@aws-cdk/aws-eventschemas/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::EventSchemas", diff --git a/packages/@aws-cdk/aws-fms/.npmignore b/packages/@aws-cdk/aws-fms/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-fms/.npmignore +++ b/packages/@aws-cdk/aws-fms/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fms/package.json b/packages/@aws-cdk/aws-fms/package.json index 47ffdc69a57ef..f02b4c96d4a7b 100644 --- a/packages/@aws-cdk/aws-fms/package.json +++ b/packages/@aws-cdk/aws-fms/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::FMS", diff --git a/packages/@aws-cdk/aws-fsx/.npmignore b/packages/@aws-cdk/aws-fsx/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-fsx/.npmignore +++ b/packages/@aws-cdk/aws-fsx/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index 21da2047de0df..fc9eb2bb12c83 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::FSx", diff --git a/packages/@aws-cdk/aws-gamelift/.npmignore b/packages/@aws-cdk/aws-gamelift/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-gamelift/.npmignore +++ b/packages/@aws-cdk/aws-gamelift/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 306ffd6f328d3..7e2ab82775f9a 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::GameLift", diff --git a/packages/@aws-cdk/aws-globalaccelerator/.npmignore b/packages/@aws-cdk/aws-globalaccelerator/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/.npmignore +++ b/packages/@aws-cdk/aws-globalaccelerator/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index ea5decf73ff1b..265cc4c542be5 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::GlobalAccelerator", diff --git a/packages/@aws-cdk/aws-glue/.npmignore b/packages/@aws-cdk/aws-glue/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-glue/.npmignore +++ b/packages/@aws-cdk/aws-glue/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index c8ce2ebc7da42..04b84d3c347b5 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Glue", @@ -76,7 +77,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-greengrass/.npmignore b/packages/@aws-cdk/aws-greengrass/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-greengrass/.npmignore +++ b/packages/@aws-cdk/aws-greengrass/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-greengrass/package.json b/packages/@aws-cdk/aws-greengrass/package.json index fa80e4b30ba52..eb00971db47ca 100644 --- a/packages/@aws-cdk/aws-greengrass/package.json +++ b/packages/@aws-cdk/aws-greengrass/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Greengrass", diff --git a/packages/@aws-cdk/aws-guardduty/.npmignore b/packages/@aws-cdk/aws-guardduty/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-guardduty/.npmignore +++ b/packages/@aws-cdk/aws-guardduty/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index 617b4eeec97fe..2ad0431bf4f96 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::GuardDuty", diff --git a/packages/@aws-cdk/aws-iam/.npmignore b/packages/@aws-cdk/aws-iam/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-iam/.npmignore +++ b/packages/@aws-cdk/aws-iam/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index 14c4c31c92d59..efa705aaf669c 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IAM", @@ -75,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "sinon": "^9.1.0" }, diff --git a/packages/@aws-cdk/aws-imagebuilder/.npmignore b/packages/@aws-cdk/aws-imagebuilder/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-imagebuilder/.npmignore +++ b/packages/@aws-cdk/aws-imagebuilder/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-imagebuilder/package.json b/packages/@aws-cdk/aws-imagebuilder/package.json index 256a3b079a760..656f94b3a8c38 100644 --- a/packages/@aws-cdk/aws-imagebuilder/package.json +++ b/packages/@aws-cdk/aws-imagebuilder/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ImageBuilder", diff --git a/packages/@aws-cdk/aws-inspector/.npmignore b/packages/@aws-cdk/aws-inspector/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-inspector/.npmignore +++ b/packages/@aws-cdk/aws-inspector/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index bc46eef38a438..8c6d5fd522c3f 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Inspector", diff --git a/packages/@aws-cdk/aws-iot/.npmignore b/packages/@aws-cdk/aws-iot/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-iot/.npmignore +++ b/packages/@aws-cdk/aws-iot/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index b9d7a26a10987..e0e7c04079287 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoT", diff --git a/packages/@aws-cdk/aws-iot1click/.npmignore b/packages/@aws-cdk/aws-iot1click/.npmignore index 3dffd1ce79a72..2892fc6e99416 100644 --- a/packages/@aws-cdk/aws-iot1click/.npmignore +++ b/packages/@aws-cdk/aws-iot1click/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index 34f6ad2fc8b5c..9760e0c886270 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoT1Click", diff --git a/packages/@aws-cdk/aws-iotanalytics/.npmignore b/packages/@aws-cdk/aws-iotanalytics/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-iotanalytics/.npmignore +++ b/packages/@aws-cdk/aws-iotanalytics/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 02bffc449abc1..6e683f7bb8a5c 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoTAnalytics", diff --git a/packages/@aws-cdk/aws-iotevents/.npmignore b/packages/@aws-cdk/aws-iotevents/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-iotevents/.npmignore +++ b/packages/@aws-cdk/aws-iotevents/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 299bbb8da8861..37d73d2fd1cb4 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoTEvents", diff --git a/packages/@aws-cdk/aws-iotthingsgraph/.npmignore b/packages/@aws-cdk/aws-iotthingsgraph/.npmignore index 917201c845418..f3eaded585e2d 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/.npmignore +++ b/packages/@aws-cdk/aws-iotthingsgraph/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iotthingsgraph/package.json b/packages/@aws-cdk/aws-iotthingsgraph/package.json index 26e80a170a192..62ce3a0fbc627 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/package.json +++ b/packages/@aws-cdk/aws-iotthingsgraph/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::IoTThingsGraph", diff --git a/packages/@aws-cdk/aws-kendra/.npmignore b/packages/@aws-cdk/aws-kendra/.npmignore index 72b7fcab0a22a..7b8fb69082a0f 100644 --- a/packages/@aws-cdk/aws-kendra/.npmignore +++ b/packages/@aws-cdk/aws-kendra/.npmignore @@ -24,3 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kendra/package.json b/packages/@aws-cdk/aws-kendra/package.json index 1e94ce21c0553..17008caafbb38 100644 --- a/packages/@aws-cdk/aws-kendra/package.json +++ b/packages/@aws-cdk/aws-kendra/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Kendra", diff --git a/packages/@aws-cdk/aws-kinesis/.npmignore b/packages/@aws-cdk/aws-kinesis/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-kinesis/.npmignore +++ b/packages/@aws-cdk/aws-kinesis/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index b4e7c21809883..a18995115299e 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Kinesis", diff --git a/packages/@aws-cdk/aws-kinesisanalytics/.npmignore b/packages/@aws-cdk/aws-kinesisanalytics/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/.npmignore +++ b/packages/@aws-cdk/aws-kinesisanalytics/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index 020baefcf1f97..ecca781e5d946 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": [ diff --git a/packages/@aws-cdk/aws-kinesisfirehose/.npmignore b/packages/@aws-cdk/aws-kinesisfirehose/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/.npmignore +++ b/packages/@aws-cdk/aws-kinesisfirehose/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index dac3480ca17bb..1c474c76b1ff8 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::KinesisFirehose", diff --git a/packages/@aws-cdk/aws-kms/.npmignore b/packages/@aws-cdk/aws-kms/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-kms/.npmignore +++ b/packages/@aws-cdk/aws-kms/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 6d59d572727a9..b17ecde0082d9 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::KMS", diff --git a/packages/@aws-cdk/aws-lakeformation/.npmignore b/packages/@aws-cdk/aws-lakeformation/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-lakeformation/.npmignore +++ b/packages/@aws-cdk/aws-lakeformation/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lakeformation/package.json b/packages/@aws-cdk/aws-lakeformation/package.json index 461b0aff0c1b0..756fd59ff172d 100644 --- a/packages/@aws-cdk/aws-lakeformation/package.json +++ b/packages/@aws-cdk/aws-lakeformation/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::LakeFormation", diff --git a/packages/@aws-cdk/aws-lambda-destinations/.npmignore b/packages/@aws-cdk/aws-lambda-destinations/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/.npmignore +++ b/packages/@aws-cdk/aws-lambda-destinations/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-destinations/package.json b/packages/@aws-cdk/aws-lambda-destinations/package.json index c6b1414588d2f..64232e6f367a1 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/package.json +++ b/packages/@aws-cdk/aws-lambda-destinations/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/.npmignore b/packages/@aws-cdk/aws-lambda-event-sources/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/.npmignore +++ b/packages/@aws-cdk/aws-lambda-event-sources/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 452fba4bed43a..b88ce808a9d9f 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -144,7 +144,7 @@ and add it to your Lambda function. The following parameters will impact Amazon * __onFailure__: In the event a record fails after all retries or if the record age has exceeded the configured value, the record will be sent to SQS queue or SNS topic that is specified here * __parallelizationFactor__: The number of batches to concurrently process on each shard. * __retryAttempts__: The maximum number of times a record should be retried in the event of failure. -* __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all reocrds that arrived prior to attaching the event source. +* __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all records that arrived prior to attaching the event source. ```ts import * as dynamodb from '@aws-cdk/aws-dynamodb'; @@ -187,7 +187,7 @@ behavior: * __onFailure__: In the event a record fails and consumes all retries, the record will be sent to SQS queue or SNS topic that is specified here * __parallelizationFactor__: The number of batches to concurrently process on each shard. * __retryAttempts__: The maximum number of times a record should be retried in the event of failure. -* __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all reocrds that arrived prior to attaching the event source. +* __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all records that arrived prior to attaching the event source. ```ts import * as lambda from '@aws-cdk/aws-lambda'; @@ -196,7 +196,7 @@ import { KinesisEventSource } from '@aws-cdk/aws-lambda-event-sources'; const stream = new kinesis.Stream(this, 'MyStream'); -myFunction.addEventSource(new KinesisEventSource(queue, { +myFunction.addEventSource(new KinesisEventSource(stream, { batchSize: 100, // default startingPosition: lambda.StartingPosition.TRIM_HORIZON }); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json index f675e910f6f61..6a16698ea1668 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json @@ -39,20 +39,7 @@ { "Action": "dynamodb:ListStreams", "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TD925BC7E", - "Arn" - ] - }, - "/stream/*" - ] - ] - } + "Resource": "*" }, { "Action": [ diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts index ac29e750cc322..ec8de1664ac26 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts @@ -34,7 +34,7 @@ export = { { 'Action': 'dynamodb:ListStreams', 'Effect': 'Allow', - 'Resource': { 'Fn::Join': ['', [{ 'Fn::GetAtt': ['TD925BC7E', 'Arn'] }, '/stream/*']] }, + 'Resource': '*', }, { 'Action': [ diff --git a/packages/@aws-cdk/aws-lambda-nodejs/.npmignore b/packages/@aws-cdk/aws-lambda-nodejs/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/.npmignore +++ b/packages/@aws-cdk/aws-lambda-nodejs/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index da8d647f1301c..2dff274fd0cbd 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "4.4.0", - "parcel": "2.0.0-beta.1", + "parcel": "2.0.0-nightly.426", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile index 40081228fd554..fe715c72fa8f2 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/parcel/Dockerfile @@ -4,7 +4,7 @@ ARG IMAGE=amazon/aws-sam-cli-build-image-nodejs12.x FROM $IMAGE # Install yarn -RUN npm install --global yarn +RUN npm install --global yarn@1.22.5 # Install parcel 2 (fix the version since it's still in beta) # install at "/" so that node_modules will be in the path for /asset-input @@ -19,4 +19,7 @@ RUN mkdir /tmp/npm-cache && \ # Disable npm update notifications RUN npm config --global set update-notifier false +# create non root user and change allow execute command for non root user +RUN /sbin/useradd -u 1000 user && chmod 711 / + CMD [ "parcel" ] diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts index dfb8d07b0fb4a..7e0ed9db2fb86 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/docker.test.ts @@ -8,7 +8,7 @@ beforeAll(() => { test('parcel is available', async () => { const proc = spawnSync('docker', [ 'run', 'parcel', - 'sh', '-c', + 'bash', '-c', '$(node -p "require.resolve(\'parcel\')") --version', ]); expect(proc.status).toEqual(0); @@ -20,7 +20,7 @@ test('parcel is installed without a package-lock.json file', async () => { // See https://github.com/aws/aws-cdk/pull/10039#issuecomment-682738396 const proc = spawnSync('docker', [ 'run', 'parcel', - 'sh', '-c', + 'bash', '-c', 'test ! -f /package-lock.json', ]); expect(proc.status).toEqual(0); @@ -30,7 +30,7 @@ test('can npm install with non root user', async () => { const proc = spawnSync('docker', [ 'run', '-u', '1000:1000', 'parcel', - 'sh', '-c', [ + 'bash', '-c', [ 'mkdir /tmp/test', 'cd /tmp/test', 'npm i constructs', @@ -43,7 +43,7 @@ test('can yarn install with non root user', async () => { const proc = spawnSync('docker', [ 'run', '-u', '500:500', 'parcel', - 'sh', '-c', [ + 'bash', '-c', [ 'mkdir /tmp/test', 'cd /tmp/test', 'yarn add constructs', diff --git a/packages/@aws-cdk/aws-lambda-python/.npmignore b/packages/@aws-cdk/aws-lambda-python/.npmignore index b70bd617fba2d..1b4217fb1a9f0 100644 --- a/packages/@aws-cdk/aws-lambda-python/.npmignore +++ b/packages/@aws-cdk/aws-lambda-python/.npmignore @@ -24,3 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 8f0387cb2e430..97229dc225fe3 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -32,11 +32,38 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd ### Module Dependencies -If `requirements.txt` exists at the entry path, the construct will handle installing +If `requirements.txt` or `Pipfile` exists at the entry path, the construct will handle installing all required modules in a [Lambda compatible Docker container](https://hub.docker.com/r/amazon/aws-sam-cli-build-image-python3.7) according to the `runtime`. + +**Lambda with a requirements.txt** ``` . ├── lambda_function.py # exports a function named 'handler' ├── requirements.txt # has to be present at the entry path ``` + +**Lambda with a Pipfile** +``` +. +├── lambda_function.py # exports a function named 'handler' +├── Pipfile # has to be present at the entry path +├── Pipfile.lock # your lock file +``` + +**Lambda Layer Support** + +You may create a python-based lambda layer with `PythonLayerVersion`. If `PythonLayerVersion` detects a `requirements.txt` +or `Pipfile` at the entry path, then `PythonLayerVersion` will include the dependencies inline with your code in the +layer. + +```ts +new lambda.PythonFunction(this, 'MyFunction', { + entry: '/path/to/my/function', + layers: [ + new lambda.PythonLayerVersion(this, 'MyLayer', { + entry: '/path/to/my/layer', // point this to your library's directory + }), + ], +}); +``` diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies new file mode 100644 index 0000000000000..4d02d028b187b --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies @@ -0,0 +1,18 @@ +# The correct AWS SAM build image based on the runtime of the function will be +# passed as build arg. The default allows to do `docker build .` when testing. +ARG IMAGE=amazon/aws-sam-cli-build-image-python3.7 +FROM $IMAGE + +# Ensure rsync is installed +RUN yum -q list installed rsync &>/dev/null || yum install -y rsync + +# Install pipenv so we can create a requirements.txt if we detect pipfile +RUN pip install pipenv + +# Install the dependencies in a cacheable layer +WORKDIR /var/dependencies +COPY Pipfile* requirements.tx[t] ./ +RUN [ -f 'Pipfile' ] && pipenv lock -r >requirements.txt; \ + [ -f 'requirements.txt' ] && pip install -r requirements.txt -t .; + +CMD [ "python" ] diff --git a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts index 15d3126c48067..937d420397773 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts @@ -3,6 +3,16 @@ import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; +/** + * Dependency files to exclude from the asset hash. + */ +export const DEPENDENCY_EXCLUDES = ['*.pyc']; + +/** + * The location in the image that the bundler image caches dependencies. + */ +export const BUNDLER_DEPENDENCIES_CACHE = '/var/dependencies'; + /** * Options for bundling */ @@ -16,29 +26,44 @@ export interface BundlingOptions { * The runtime of the lambda function */ readonly runtime: lambda.Runtime; + + /** + * Output path suffix ('python' for a layer, '.' otherwise) + */ + readonly outputPathSuffix: string; } /** * Produce bundled Lambda asset code */ export function bundle(options: BundlingOptions): lambda.AssetCode { - // Bundling image derived from runtime bundling image (AWS SAM docker image) - const image = cdk.BundlingDockerImage.fromAsset(__dirname, { - buildArgs: { - IMAGE: options.runtime.bundlingDockerImage.image, - }, - }); - - let installer = options.runtime === lambda.Runtime.PYTHON_2_7 ? Installer.PIP : Installer.PIP3; + const { entry, runtime, outputPathSuffix } = options; - let hasRequirements = fs.existsSync(path.join(options.entry, 'requirements.txt')); + const hasDeps = hasDependencies(entry); - let depsCommand = chain([ - hasRequirements ? `${installer} install -r requirements.txt -t ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}` : '', - `rsync -r . ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}`, + const depsCommand = chain([ + hasDeps ? `rsync -r ${BUNDLER_DEPENDENCIES_CACHE}/. ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/${outputPathSuffix}` : '', + `rsync -r . ${cdk.AssetStaging.BUNDLING_OUTPUT_DIR}/${outputPathSuffix}`, ]); - return lambda.Code.fromAsset(options.entry, { + // Determine which dockerfile to use. When dependencies are present, we use a + // Dockerfile that can create a cacheable layer. We can't use this Dockerfile + // if there aren't dependencies or the Dockerfile will complain about missing + // sources. + const dockerfile = hasDeps + ? 'Dockerfile.dependencies' + : 'Dockerfile'; + + const image = cdk.BundlingDockerImage.fromAsset(entry, { + buildArgs: { + IMAGE: runtime.bundlingDockerImage.image, + }, + file: path.join(__dirname, dockerfile), + }); + + return lambda.Code.fromAsset(entry, { + assetHashType: cdk.AssetHashType.BUNDLE, + exclude: DEPENDENCY_EXCLUDES, bundling: { image, command: ['bash', '-c', depsCommand], @@ -46,9 +71,20 @@ export function bundle(options: BundlingOptions): lambda.AssetCode { }); } -enum Installer { - PIP = 'pip', - PIP3 = 'pip3', +/** + * Checks to see if the `entry` directory contains a type of dependency that + * we know how to install. + */ +export function hasDependencies(entry: string): boolean { + if (fs.existsSync(path.join(entry, 'Pipfile'))) { + return true; + } + + if (fs.existsSync(path.join(entry, 'requirements.txt'))) { + return true; + } + + return false; } function chain(commands: string[]): string { diff --git a/packages/@aws-cdk/aws-lambda-python/lib/function.ts b/packages/@aws-cdk/aws-lambda-python/lib/function.ts index 7b3fbb1d71fe8..77f794704e967 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/function.ts @@ -64,9 +64,9 @@ export class PythonFunction extends lambda.Function { ...props, runtime, code: bundle({ - ...props, - entry, runtime, + entry, + outputPathSuffix: '.', }), handler: `${index.slice(0, -3)}.${handler}`, }); diff --git a/packages/@aws-cdk/aws-lambda-python/lib/index.ts b/packages/@aws-cdk/aws-lambda-python/lib/index.ts index 2653adb2a89e8..5459e812abb91 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/index.ts @@ -1 +1,2 @@ export * from './function'; +export * from './layer'; diff --git a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts new file mode 100644 index 0000000000000..8b090eed6a989 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts @@ -0,0 +1,54 @@ +import * as path from 'path'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import { bundle } from './bundling'; + +/** + * Properties for PythonLayerVersion + */ +export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { + /** + * The path to the root directory of the lambda layer. + */ + readonly entry: string; + + /** + * The runtimes compatible with the python layer. + * + * @default - All runtimes are supported. + */ + readonly compatibleRuntimes?: lambda.Runtime[]; +} + +/** + * A lambda layer version. + * + * @experimental + */ +export class PythonLayerVersion extends lambda.LayerVersion { + constructor(scope: cdk.Construct, id: string, props: PythonLayerVersionProps) { + const compatibleRuntimes = props.compatibleRuntimes ?? [lambda.Runtime.PYTHON_3_7]; + + // Ensure that all compatible runtimes are python + for (const runtime of compatibleRuntimes) { + if (runtime && runtime.family !== lambda.RuntimeFamily.PYTHON) { + throw new Error('Only `PYTHON` runtimes are supported.'); + } + } + + // Entry and defaults + const entry = path.resolve(props.entry); + // Pick the first compatibleRuntime to use for bundling or PYTHON_3_7 + const runtime = compatibleRuntimes[0] ?? lambda.Runtime.PYTHON_3_7; + + super(scope, id, { + ...props, + compatibleRuntimes, + code: bundle({ + entry, + runtime, + outputPathSuffix: 'python', + }), + }); + } +} diff --git a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts index 492f7b3dbd890..ca415f60e5476 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -1,79 +1,158 @@ import * as fs from 'fs'; +import * as path from 'path'; import { Code, Runtime } from '@aws-cdk/aws-lambda'; -import { bundle } from '../lib/bundling'; +import { hasDependencies, bundle } from '../lib/bundling'; jest.mock('@aws-cdk/aws-lambda'); const existsSyncOriginal = fs.existsSync; const existsSyncMock = jest.spyOn(fs, 'existsSync'); +jest.mock('child_process', () => ({ + spawnSync: jest.fn(() => { + return { + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from('sha256:1234567890abcdef'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }; + }), +})); + beforeEach(() => { jest.clearAllMocks(); }); -test('Bundling', () => { +test('Bundling a function without dependencies', () => { + const entry = path.join(__dirname, 'lambda-handler-nodeps'); bundle({ - entry: '/project/folder', + entry: entry, runtime: Runtime.PYTHON_3_7, + outputPathSuffix: '.', }); // Correctly bundles - expect(Code.fromAsset).toHaveBeenCalledWith('/project/folder', { + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'rsync -r . /asset-output', + 'rsync -r . /asset-output/.', ], }), - }); + })); // Searches for requirements.txt in entry - expect(existsSyncMock).toHaveBeenCalledWith('/project/folder/requirements.txt'); + expect(existsSyncMock).toHaveBeenCalledWith(path.join(entry, 'requirements.txt')); }); -test('Bundling with requirements.txt installed', () => { - existsSyncMock.mockImplementation((p: fs.PathLike) => { - if (/requirements.txt/.test(p.toString())) { - return true; - } - return existsSyncOriginal(p); +test('Bundling a function with requirements.txt installed', () => { + const entry = path.join(__dirname, 'lambda-handler'); + bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + outputPathSuffix: '.', }); + // Correctly bundles + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + 'rsync -r /var/dependencies/. /asset-output/. && rsync -r . /asset-output/.', + ], + }), + })); + + // Searches for requirements.txt in entry + expect(existsSyncMock).toHaveBeenCalledWith(path.join(entry, 'requirements.txt')); +}); + +test('Bundling Python 2.7 with requirements.txt installed', () => { + const entry = path.join(__dirname, 'lambda-handler'); bundle({ - entry: '/project/folder', - runtime: Runtime.PYTHON_3_7, + entry: entry, + runtime: Runtime.PYTHON_2_7, + outputPathSuffix: '.', }); // Correctly bundles with requirements.txt pip installed - expect(Code.fromAsset).toHaveBeenCalledWith('/project/folder', { + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'pip3 install -r requirements.txt -t /asset-output && rsync -r . /asset-output', + 'rsync -r /var/dependencies/. /asset-output/. && rsync -r . /asset-output/.', ], }), - }); + })); + + // Searches for requirements.txt in entry + expect(existsSyncMock).toHaveBeenCalledWith(path.join(entry, 'requirements.txt')); }); -test('Bundling Python 2.7 with requirements.txt installed', () => { - existsSyncMock.mockImplementation((p: fs.PathLike) => { - if (/requirements.txt/.test(p.toString())) { - return true; - } - return existsSyncOriginal(p); +test('Bundling a layer with dependencies', () => { + const entry = path.join(__dirname, 'lambda-handler'); + + bundle({ + entry: entry, + runtime: Runtime.PYTHON_2_7, + outputPathSuffix: 'python', }); + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + 'rsync -r /var/dependencies/. /asset-output/python && rsync -r . /asset-output/python', + ], + }), + })); +}); + +test('Bundling a python code layer', () => { + const entry = path.join(__dirname, 'lambda-handler-nodeps'); + bundle({ - entry: '/project/folder', + entry: path.join(entry, '.'), runtime: Runtime.PYTHON_2_7, + outputPathSuffix: 'python', }); - // Correctly bundles with requirements.txt pip installed - expect(Code.fromAsset).toHaveBeenCalledWith('/project/folder', { + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ bundling: expect.objectContaining({ command: [ 'bash', '-c', - 'pip install -r requirements.txt -t /asset-output && rsync -r . /asset-output', + 'rsync -r . /asset-output/python', ], }), + })); +}); + +describe('Dependency detection', () => { + test('Detects pipenv', () => { + existsSyncMock.mockImplementation((p: fs.PathLike) => { + if (/Pipfile/.test(p.toString())) { + return true; + } + return existsSyncOriginal(p); + }); + + expect(hasDependencies('/asset-input')).toEqual(true); + }); + + test('Detects requirements.txt', () => { + existsSyncMock.mockImplementation((p: fs.PathLike) => { + if (/requirements.txt/.test(p.toString())) { + return true; + } + return existsSyncOriginal(p); + }); + + expect(hasDependencies('/asset-input')).toEqual(true); + }); + + test('No known dependencies', () => { + existsSyncMock.mockImplementation(() => false); + expect(hasDependencies('/asset-input')).toEqual(false); }); }); diff --git a/packages/@aws-cdk/aws-lambda-python/test/function.test.ts b/packages/@aws-cdk/aws-lambda-python/test/function.test.ts index 9bc69a7757946..98bab1cf35be4 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/function.test.ts @@ -12,6 +12,7 @@ jest.mock('../lib/bundling', () => { }, bindToResource: () => { return; }, }), + hasDependencies: jest.fn().mockReturnValue(false), }; }); @@ -28,6 +29,7 @@ test('PythonFunction with defaults', () => { expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringMatching(/@aws-cdk\/aws-lambda-python\/test\/lambda-handler$/), + outputPathSuffix: '.', })); expect(stack).toHaveResource('AWS::Lambda::Function', { @@ -44,6 +46,7 @@ test('PythonFunction with index in a subdirectory', () => { expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ entry: expect.stringMatching(/@aws-cdk\/aws-lambda-python\/test\/lambda-handler-sub$/), + outputPathSuffix: '.', })); expect(stack).toHaveResource('AWS::Lambda::Function', { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json index ba3b9d456ca11..c3adcd34e3e95 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3Bucket5C76F19D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3Bucket5C76F19D": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74": { "Type": "String", - "Description": "S3 bucket for asset \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "S3 bucket for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" }, - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F": { "Type": "String", - "Description": "S3 key for asset version \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "S3 key for asset version \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" }, - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25ArtifactHashB15DA742": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57ArtifactHash70AD5A1E": { "Type": "String", - "Description": "Artifact hash for asset \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "Artifact hash for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.expected.json new file mode 100644 index 0000000000000..19cc6231599e4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.expected.json @@ -0,0 +1,110 @@ +{ + "Resources": { + "myhandlerServiceRole77891068": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Runtime": "python3.6" + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D": { + "Type": "String", + "Description": "S3 bucket for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + }, + "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0": { + "Type": "String", + "Description": "S3 key for asset version \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + }, + "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bArtifactHashEE8E0CE9": { + "Type": "String", + "Description": "Artifact hash for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + } + }, + "Outputs": { + "FunctionName": { + "Value": { + "Ref": "myhandlerD202FA8E" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts new file mode 100644 index 0000000000000..17b17070e56e8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * aws lambda invoke --function-name --invocation-type Event --payload $(base64 <<<'"OK"') response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(__dirname, 'lambda-handler-pipenv'), + runtime: Runtime.PYTHON_3_6, + }); + + new CfnOutput(this, 'FunctionName', { + value: fn.functionName, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-python-inline'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.expected.json new file mode 100644 index 0000000000000..c0cb2b260d146 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.expected.json @@ -0,0 +1,110 @@ +{ + "Resources": { + "myhandlerServiceRole77891068": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Runtime": "python2.7" + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8": { + "Type": "String", + "Description": "S3 bucket for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + }, + "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C": { + "Type": "String", + "Description": "S3 key for asset version \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + }, + "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014ArtifactHash7768674B": { + "Type": "String", + "Description": "Artifact hash for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + } + }, + "Outputs": { + "FunctionName": { + "Value": { + "Ref": "myhandlerD202FA8E" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts new file mode 100644 index 0000000000000..68f2b2f18f4b4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * aws lambda invoke --function-name --invocation-type Event --payload $(base64 <<<'"OK"') response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(__dirname, 'lambda-handler-pipenv'), + runtime: Runtime.PYTHON_2_7, + }); + + new CfnOutput(this, 'FunctionName', { + value: fn.functionName, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-python-pipenv-py27'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.expected.json new file mode 100644 index 0000000000000..07e4ea8cf45f9 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.expected.json @@ -0,0 +1,110 @@ +{ + "Resources": { + "myhandlerServiceRole77891068": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Runtime": "python3.8" + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813": { + "Type": "String", + "Description": "S3 bucket for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + }, + "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383": { + "Type": "String", + "Description": "S3 key for asset version \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + }, + "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aArtifactHash652F614E": { + "Type": "String", + "Description": "Artifact hash for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + } + }, + "Outputs": { + "FunctionName": { + "Value": { + "Ref": "myhandlerD202FA8E" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts new file mode 100644 index 0000000000000..619dd270ec206 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * aws lambda invoke --function-name --invocation-type Event --payload $(base64 <<<'"OK"') response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(__dirname, 'lambda-handler-pipenv'), + runtime: Runtime.PYTHON_3_8, + }); + + new CfnOutput(this, 'FunctionName', { + value: fn.functionName, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-python-pipenv-py38'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json new file mode 100644 index 0000000000000..5bc285e5c5769 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json @@ -0,0 +1,176 @@ +{ + "Resources": { + "SharedDACC02AA": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3Bucket6D2DF2A1" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818" + } + ] + } + ] + } + ] + ] + } + }, + "CompatibleRuntimes": [ + "python3.6" + ] + } + }, + "myhandlerServiceRole77891068": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myhandlerD202FA8E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3Bucket89C9DB12" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3VersionKey435DAD55" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3VersionKey435DAD55" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myhandlerServiceRole77891068", + "Arn" + ] + }, + "Runtime": "python3.6", + "Layers": [ + { + "Ref": "SharedDACC02AA" + } + ] + }, + "DependsOn": [ + "myhandlerServiceRole77891068" + ] + } + }, + "Parameters": { + "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3Bucket6D2DF2A1": { + "Type": "String", + "Description": "S3 bucket for asset \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + }, + "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818": { + "Type": "String", + "Description": "S3 key for asset version \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + }, + "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cArtifactHashF8341E5E": { + "Type": "String", + "Description": "Artifact hash for asset \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + }, + "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3Bucket89C9DB12": { + "Type": "String", + "Description": "S3 bucket for asset \"71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218\"" + }, + "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3VersionKey435DAD55": { + "Type": "String", + "Description": "S3 key for asset version \"71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218\"" + }, + "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218ArtifactHash0EDF3CD0": { + "Type": "String", + "Description": "Artifact hash for asset \"71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218\"" + } + }, + "Outputs": { + "FunctionArn": { + "Value": { + "Fn::GetAtt": [ + "myhandlerD202FA8E", + "Arn" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts new file mode 100644 index 0000000000000..1259ba09bdfe1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts @@ -0,0 +1,36 @@ +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * * aws lambda invoke --function-name --invocation-type Event --payload '"OK"' response.json + */ + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const projectDirectory = path.join(__dirname, 'lambda-handler-project'); + const fn = new lambda.PythonFunction(this, 'my_handler', { + entry: path.join(projectDirectory, 'lambda'), + runtime: Runtime.PYTHON_3_6, + layers: [ + new lambda.PythonLayerVersion(this, 'Shared', { + entry: path.join(projectDirectory, 'shared'), + compatibleRuntimes: [Runtime.PYTHON_3_6], + }), + ], + }); + + new CfnOutput(this, 'FunctionArn', { + value: fn.functionArn, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-lambda-function-project'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json index ff95a0ef74c51..2f0a607ecaecb 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134S3Bucket4FDAE558" + "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3BucketEEA58FD6" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134S3VersionKey09B41633" + "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134S3VersionKey09B41633" + "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134S3Bucket4FDAE558": { + "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3BucketEEA58FD6": { "Type": "String", - "Description": "S3 bucket for asset \"7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134\"" + "Description": "S3 bucket for asset \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" }, - "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134S3VersionKey09B41633": { + "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F": { "Type": "String", - "Description": "S3 key for asset version \"7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134\"" + "Description": "S3 key for asset version \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" }, - "AssetParameters7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134ArtifactHash9ED905F8": { + "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cArtifactHash239A9708": { "Type": "String", - "Description": "Artifact hash for asset \"7173f882be924a92ff6019021219c38f84fdf7f12205fc7558dab5c2fa03d134\"" + "Description": "Artifact hash for asset \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json new file mode 100644 index 0000000000000..ba8fee87727b4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json @@ -0,0 +1,110 @@ +{ + "Resources": { + "functionServiceRoleEF216095": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "functionF19B1A04": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aS3BucketEE202B67" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aS3VersionKey8097C675" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aS3VersionKey8097C675" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "functionServiceRoleEF216095", + "Arn" + ] + }, + "Runtime": "python2.7" + }, + "DependsOn": [ + "functionServiceRoleEF216095" + ] + } + }, + "Parameters": { + "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aS3BucketEE202B67": { + "Type": "String", + "Description": "S3 bucket for asset \"af41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966a\"" + }, + "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aS3VersionKey8097C675": { + "Type": "String", + "Description": "S3 key for asset version \"af41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966a\"" + }, + "AssetParametersaf41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966aArtifactHash3E92B1F8": { + "Type": "String", + "Description": "Artifact hash for asset \"af41a5381eff9302e9acdfeb9c3bcf160b56a97091242b2d599ed5a861af966a\"" + } + }, + "Outputs": { + "Function": { + "Value": { + "Ref": "functionF19B1A04" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts new file mode 100644 index 0000000000000..f9588e313e39e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts @@ -0,0 +1,68 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; +import { Construct, ConstructOrder } from 'constructs'; +import * as lambda from '../lib'; + +/* + * Stack verification steps: + * aws lambda invoke --function-name --invocation-type Event --payload $(base64 <<<'"OK"') response.json + */ + +class TestStack extends Stack { + public readonly dependenciesAssetHash: string; + + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const fn = new lambda.PythonFunction(this, 'function', { + entry: workDir, + runtime: Runtime.PYTHON_2_7, + }); + + new CfnOutput(this, 'Function', { + value: fn.functionName, + }); + + // Find the asset hash of the dependencies + this.dependenciesAssetHash = (fn.node.findAll(ConstructOrder.POSTORDER) + .find(c => c.node.path.endsWith('Code')) as any) + .assetHash; + } +} + +// This is a special integration test that synths twice to show that docker +// picks up a change in requirements.txt + +// Create a working directory for messing around with requirements.txt +const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-integ')); +fs.copyFileSync(path.join(__dirname, 'lambda-handler', 'index.py'), path.join(workDir, 'index.py')); +const requirementsTxtPath = path.join(workDir, 'requirements.txt'); + +// Write a requirements.txt with an extraneous dependency (colorama) +const beforeDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.25.11\nrequests==2.23.0\npillow==6.2.2\ncolorama==0.4.3\n'; +fs.writeFileSync(requirementsTxtPath, beforeDeps); + +// Synth the first time +const app = new App(); +const stack1 = new TestStack(app, 'cdk-integ-lambda-python-requirements-removed1'); +app.synth(); + +// Then, write a requirements.txt without the extraneous dependency and synth again +const afterDeps = 'certifi==2020.6.20\nchardet==3.0.4\nidna==2.10\nurllib3==1.25.11\nrequests==2.23.0\npillow==6.2.2\n'; +fs.writeFileSync(requirementsTxtPath, afterDeps); + +// Synth the same stack a second time with different requirements.txt contents +const app2 = new App(); +const stack2 = new TestStack(app2, 'cdk-integ-lambda-python-requirements-removed2'); +app2.synth(); + +if (!stack1.dependenciesAssetHash || !stack2.dependenciesAssetHash) { + throw new Error('The asset hashes are not both truthy'); +} + +if (stack1.dependenciesAssetHash === stack2.dependenciesAssetHash) { + throw new Error('Removing a dependency did not change the asset hash'); +} diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json index cd0057d2898c2..63e6ba74ccfe9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3Bucket5C76F19D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D" + "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" } ] } @@ -368,17 +368,17 @@ } }, "Parameters": { - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3Bucket5C76F19D": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74": { "Type": "String", - "Description": "S3 bucket for asset \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "S3 bucket for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" }, - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25S3VersionKey374DFF5D": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F": { "Type": "String", - "Description": "S3 key for asset version \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "S3 key for asset version \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" }, - "AssetParameterscc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25ArtifactHashB15DA742": { + "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57ArtifactHash70AD5A1E": { "Type": "String", - "Description": "Artifact hash for asset \"cc7e935d2a5f0ec0cfecbc4c12eabb49f6a3e587f58c4a18cf59383a1d656f25\"" + "Description": "Artifact hash for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py new file mode 100644 index 0000000000000..f118d0551afb8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-nodeps/index.py @@ -0,0 +1,2 @@ +def handler(event, context): + print('No dependencies') diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile new file mode 100644 index 0000000000000..2d72b30bc8be8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile @@ -0,0 +1,8 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[packages] +requests = "==2.23.0" +pillow = "==6.2.2" diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock new file mode 100644 index 0000000000000..0113c74bf7363 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/Pipfile.lock @@ -0,0 +1,94 @@ +{ + "_meta": { + "hash": { + "sha256": "0f782e44e69391c98999b575a7f93228f06d22716a144d955386b9c6abec2040" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + ], + "version": "==2020.6.20" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "pillow": { + "hashes": [ + "sha256:00e0bbe9923adc5cc38a8da7d87d4ce16cde53b8d3bba8886cb928e84522d963", + "sha256:03457e439d073770d88afdd90318382084732a5b98b0eb6f49454746dbaae701", + "sha256:0d5c99f80068f13231ac206bd9b2e80ea357f5cf9ae0fa97fab21e32d5b61065", + "sha256:1a3bc8e1db5af40a81535a62a591fafdb30a8a1b319798ea8052aa65ef8f06d2", + "sha256:2b4a94be53dff02af90760c10a2e3634c3c7703410f38c98154d5ce71fe63d20", + "sha256:3ba7d8f1d962780f86aa747fef0baf3211b80cb13310fff0c375da879c0656d4", + "sha256:3e81485cec47c24f5fb27acb485a4fc97376b2b332ed633867dc68ac3077998c", + "sha256:43ef1cff7ee57f9c8c8e6fa02a62eae9fa23a7e34418c7ce88c0e3fe09d1fb38", + "sha256:4adc3302df4faf77c63ab3a83e1a3e34b94a6a992084f4aa1cb236d1deaf4b39", + "sha256:535e8e0e02c9f1fc2e307256149d6ee8ad3aa9a6e24144b7b6e6fb6126cb0e99", + "sha256:5ccfcb0a34ad9b77ad247c231edb781763198f405a5c8dc1b642449af821fb7f", + "sha256:5dcbbaa3a24d091a64560d3c439a8962866a79a033d40eb1a75f1b3413bfc2bc", + "sha256:6e2a7e74d1a626b817ecb7a28c433b471a395c010b2a1f511f976e9ea4363e64", + "sha256:82859575005408af81b3e9171ae326ff56a69af5439d3fc20e8cb76cd51c8246", + "sha256:834dd023b7f987d6b700ad93dc818098d7eb046bd445e9992b3093c6f9d7a95f", + "sha256:87ef0eca169f7f0bc050b22f05c7e174a65c36d584428431e802c0165c5856ea", + "sha256:900de1fdc93764be13f6b39dc0dd0207d9ff441d87ad7c6e97e49b81987dc0f3", + "sha256:92b83b380f9181cacc994f4c983d95a9c8b00b50bf786c66d235716b526a3332", + "sha256:aa1b0297e352007ec781a33f026afbb062a9a9895bb103c8f49af434b1666880", + "sha256:aa4792ab056f51b49e7d59ce5733155e10a918baf8ce50f64405db23d5627fa2", + "sha256:b72c39585f1837d946bd1a829a4820ccf86e361f28cbf60f5d646f06318b61e2", + "sha256:bb7861e4618a0c06c40a2e509c1bea207eea5fd4320d486e314e00745a402ca5", + "sha256:bc149dab804291a18e1186536519e5e122a2ac1316cb80f506e855a500b1cdd4", + "sha256:c424d35a5259be559b64490d0fd9e03fba81f1ce8e5b66e0a59de97547351d80", + "sha256:cbd5647097dc55e501f459dbac7f1d0402225636deeb9e0a98a8d2df649fc19d", + "sha256:ccf16fe444cc43800eeacd4f4769971200982200a71b1368f49410d0eb769543", + "sha256:d3a98444a00b4643b22b0685dbf9e0ddcaf4ebfd4ea23f84f228adf5a0765bb2", + "sha256:d6b4dc325170bee04ca8292bbd556c6f5398d52c6149ca881e67daf62215426f", + "sha256:db9ff0c251ed066d367f53b64827cc9e18ccea001b986d08c265e53625dab950", + "sha256:e3a797a079ce289e59dbd7eac9ca3bf682d52687f718686857281475b7ca8e6a" + ], + "index": "pypi", + "version": "==6.2.2" + }, + "requests": { + "hashes": [ + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + ], + "index": "pypi", + "version": "==2.23.0" + }, + "urllib3": { + "hashes": [ + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.25.10" + } + }, + "develop": {} +} diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/index.py new file mode 100644 index 0000000000000..c033f37560534 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-pipenv/index.py @@ -0,0 +1,11 @@ +import requests +from PIL import Image + +def handler(event, context): + response = requests.get('https://a0.awsstatic.com/main/images/logos/aws_smile-header-desktop-en-white_59x35.png', stream=True) + img = Image.open(response.raw) + + print(response.status_code) + print(img.size) + + return response.status_code diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/lambda/index.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/lambda/index.py new file mode 100644 index 0000000000000..6ac592242c8fb --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/lambda/index.py @@ -0,0 +1,12 @@ +import requests +from PIL import Image +import shared + +def handler(event, context): + response = requests.get(shared.get_url(), stream=True) + img = Image.open(response.raw) + + print(response.status_code) + print(img.size) + + return response.status_code diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt new file mode 100644 index 0000000000000..10fdedeb59ab2 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt @@ -0,0 +1,9 @@ +# Lock versions of pip packages +certifi==2020.6.20 +chardet==3.0.4 +idna==2.10 +urllib3==1.25.11 +# Requests used by this lambda +requests==2.23.0 +# Pillow 6.x so that python 2.7 and 3.x can both use this fixture +pillow==6.2.2 diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/shared.py b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/shared.py new file mode 100644 index 0000000000000..b17623b83b881 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/shared.py @@ -0,0 +1,2 @@ +def get_url() -> str: + return 'https://a0.awsstatic.com/main/images/logos/aws_smile-header-desktop-en-white_59x35.png' diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt index 288e2971fb79a..10fdedeb59ab2 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt @@ -1,2 +1,9 @@ +# Lock versions of pip packages +certifi==2020.6.20 +chardet==3.0.4 +idna==2.10 +urllib3==1.25.11 +# Requests used by this lambda requests==2.23.0 -pillow==7.2.0 +# Pillow 6.x so that python 2.7 and 3.x can both use this fixture +pillow==6.2.2 diff --git a/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts b/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts new file mode 100644 index 0000000000000..8ace2ec3f7a18 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/test/layer.test.ts @@ -0,0 +1,54 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { Stack } from '@aws-cdk/core'; +import { hasDependencies, bundle } from '../lib/bundling'; +import { PythonLayerVersion } from '../lib/layer'; + +jest.mock('../lib/bundling', () => { + return { + bundle: jest.fn().mockReturnValue({ + bind: () => { + return { + s3Location: { + bucketName: 'bucket', + objectKey: 'key', + }, + }; + }, + bindToResource: () => { return; }, + }), + hasDependencies: jest.fn().mockReturnValue(true), + }; +}); + +const hasDependenciesMock = (hasDependencies as jest.Mock); + +let stack: Stack; +beforeEach(() => { + stack = new Stack(); + jest.clearAllMocks(); +}); + +test('Bundling a layer from files', () => { + hasDependenciesMock.mockReturnValue(false); + + const entry = path.join(__dirname, 'test/lambda-handler-project'); + new PythonLayerVersion(stack, 'layer', { + entry, + }); + + expect(bundle).toHaveBeenCalledWith(expect.objectContaining({ + entry, + outputPathSuffix: 'python', + })); +}); + +test('Fails when bundling a layer for a runtime not supported', () => { + expect(() => { + new PythonLayerVersion(stack, 'layer', { + entry: '/some/path', + compatibleRuntimes: [Runtime.PYTHON_2_7, Runtime.NODEJS], + }); + }).toThrow(/PYTHON.*support/); +}); diff --git a/packages/@aws-cdk/aws-lambda/.gitignore b/packages/@aws-cdk/aws-lambda/.gitignore index d0a956699806b..4af109b196444 100644 --- a/packages/@aws-cdk/aws-lambda/.gitignore +++ b/packages/@aws-cdk/aws-lambda/.gitignore @@ -15,5 +15,6 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js +!jest.config.js junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/.npmignore b/packages/@aws-cdk/aws-lambda/.npmignore index 95a6e5fe5bb87..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-lambda/.npmignore +++ b/packages/@aws-cdk/aws-lambda/.npmignore @@ -19,7 +19,9 @@ dist tsconfig.json .eslintrc.js +jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/jest.config.js b/packages/@aws-cdk/aws-lambda/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 7e6c309a3853e..ab1012b6015bc 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -210,7 +210,7 @@ export abstract class FunctionBase extends Resource implements IFunction { */ public addPermission(id: string, permission: Permission) { if (!this.canCreatePermissions) { - // FIXME: Report metadata + // FIXME: @deprecated(v2) - throw an error if calling `addPermission` on a resource that doesn't support it. return; } @@ -299,7 +299,11 @@ export abstract class FunctionBase extends Resource implements IFunction { action: 'lambda:InvokeFunction', }); - return { statementAdded: true, policyDependable: this._functionNode().findChild(identifier) } as iam.AddToResourcePolicyResult; + const permissionNode = this._functionNode().tryFindChild(identifier); + if (!permissionNode) { + throw new Error('Cannot modify permission to lambda function. Function is either imported or $LATEST version.'); + } + return { statementAdded: true, policyDependable: permissionNode }; }, node: this.node, stack: this.stack, @@ -357,13 +361,13 @@ export abstract class FunctionBase extends Resource implements IFunction { * ..which means that in order to extract the `account-id` component from the ARN, we can * split the ARN using ":" and select the component in index 4. * - * @returns true if account id of function matches this account + * @returns true if account id of function matches this account, or the accounts are unresolved. * * @internal */ protected _isStackAccount(): boolean { if (Token.isUnresolved(this.stack.account) || Token.isUnresolved(this.functionArn)) { - return false; + return true; } return this.stack.parseArn(this.functionArn).account === this.stack.account; } diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 12a92140b511a..4a58d5ce501ef 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -4,21 +4,10 @@ import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; import { Runtime } from './runtime'; -export interface LayerVersionProps { - /** - * The runtimes compatible with this Layer. - * - * @default - All runtimes are supported. - */ - readonly compatibleRuntimes?: Runtime[]; - - /** - * The content of this Layer. - * - * Using `Code.fromInline` is not supported. - */ - readonly code: Code; - +/** + * Non runtime options + */ +export interface LayerVersionOptions { /** * The description the this Lambda Layer. * @@ -41,6 +30,22 @@ export interface LayerVersionProps { readonly layerVersionName?: string; } +export interface LayerVersionProps extends LayerVersionOptions { + /** + * The runtimes compatible with this Layer. + * + * @default - All runtimes are supported. + */ + readonly compatibleRuntimes?: Runtime[]; + + /** + * The content of this Layer. + * + * Using `Code.fromInline` is not supported. + */ + readonly code: Code; +} + export interface ILayerVersion extends IResource { /** * The ARN of the Lambda Layer version that this Layer defines. diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index cd592e5bc3ce6..056ccb67e6d68 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -49,10 +49,12 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Lambda", + "jest": true, "env": { "AWSLINT_BASE_CONSTRUCT": "true" } @@ -77,12 +79,11 @@ "@aws-cdk/assert": "0.0.0", "@types/aws-lambda": "^8.10.63", "@types/lodash": "^4.14.161", - "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", + "jest": "^26.6.0", "lodash": "^4.17.20", - "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda/test/test.alias.ts b/packages/@aws-cdk/aws-lambda/test/alias.test.ts similarity index 73% rename from packages/@aws-cdk/aws-lambda/test/test.alias.ts rename to packages/@aws-cdk/aws-lambda/test/alias.test.ts index 60be6208c48cb..334e40607ed5c 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.alias.ts +++ b/packages/@aws-cdk/aws-lambda/test/alias.test.ts @@ -1,12 +1,12 @@ -import { arrayWith, beASupersetOfTemplate, expect, haveResource, haveResourceLike, objectLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { arrayWith, objectLike } from '@aws-cdk/assert'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import { Lazy, Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; import * as lambda from '../lib'; -export = { - 'version and aliases'(test: Test): void { +describe('alias', () => { + test('version and aliases', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -21,27 +21,18 @@ export = { version, }); - expect(stack).to(beASupersetOfTemplate({ - MyLambdaVersion16CDE3C40: { - Type: 'AWS::Lambda::Version', - Properties: { - FunctionName: { Ref: 'MyLambdaCCE802FB' }, - }, - }, - Alias325C5727: { - Type: 'AWS::Lambda::Alias', - Properties: { - FunctionName: { Ref: 'MyLambdaCCE802FB' }, - FunctionVersion: stack.resolve(version.version), - Name: 'prod', - }, - }, - })); + expect(stack).toHaveResource('AWS::Lambda::Version', { + FunctionName: { Ref: 'MyLambdaCCE802FB' }, + }); - test.done(); - }, + expect(stack).toHaveResource('AWS::Lambda::Alias', { + FunctionName: { Ref: 'MyLambdaCCE802FB' }, + FunctionVersion: stack.resolve(version.version), + Name: 'prod', + }); + }); - 'can create an alias to $LATEST'(test: Test): void { + test('can create an alias to $LATEST', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -54,17 +45,15 @@ export = { version: fn.latestVersion, }); - expect(stack).to(haveResource('AWS::Lambda::Alias', { + expect(stack).toHaveResource('AWS::Lambda::Alias', { FunctionName: { Ref: 'MyLambdaCCE802FB' }, FunctionVersion: '$LATEST', Name: 'latest', - })); - expect(stack).notTo(haveResource('AWS::Lambda::Version')); - - test.done(); - }, + }); + expect(stack).not.toHaveResource('AWS::Lambda::Version'); + }); - 'can use newVersion to create a new Version'(test: Test) { + test('can use newVersion to create a new Version', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -79,19 +68,17 @@ export = { version, }); - expect(stack).to(haveResourceLike('AWS::Lambda::Version', { + expect(stack).toHaveResourceLike('AWS::Lambda::Version', { FunctionName: { Ref: 'MyLambdaCCE802FB' }, - })); + }); - expect(stack).to(haveResourceLike('AWS::Lambda::Alias', { + expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { FunctionName: { Ref: 'MyLambdaCCE802FB' }, Name: 'prod', - })); - - test.done(); - }, + }); + }); - 'can add additional versions to alias'(test: Test) { + test('can add additional versions to alias', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -109,7 +96,7 @@ export = { additionalVersions: [{ version: version2, weight: 0.1 }], }); - expect(stack).to(haveResource('AWS::Lambda::Alias', { + expect(stack).toHaveResource('AWS::Lambda::Alias', { FunctionVersion: stack.resolve(version1.version), RoutingConfig: { AdditionalVersionWeights: [ @@ -119,11 +106,10 @@ export = { }, ], }, - })); + }); + }); - test.done(); - }, - 'version and aliases with provisioned execution'(test: Test): void { + test('version and aliases with provisioned execution', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -140,34 +126,22 @@ export = { provisionedConcurrentExecutions: pce, }); - expect(stack).to(beASupersetOfTemplate({ - MyLambdaVersion16CDE3C40: { - Type: 'AWS::Lambda::Version', - Properties: { - FunctionName: { - Ref: 'MyLambdaCCE802FB', - }, - ProvisionedConcurrencyConfig: { - ProvisionedConcurrentExecutions: 5, - }, - }, + expect(stack).toHaveResource('AWS::Lambda::Version', { + ProvisionedConcurrencyConfig: { + ProvisionedConcurrentExecutions: 5, }, - Alias325C5727: { - Type: 'AWS::Lambda::Alias', - Properties: { - FunctionName: { Ref: 'MyLambdaCCE802FB' }, - FunctionVersion: stack.resolve(version.version), - Name: 'prod', - ProvisionedConcurrencyConfig: { - ProvisionedConcurrentExecutions: 5, - }, - }, + }); + + expect(stack).toHaveResource('AWS::Lambda::Alias', { + FunctionVersion: stack.resolve(version.version), + Name: 'prod', + ProvisionedConcurrencyConfig: { + ProvisionedConcurrentExecutions: 5, }, - })); + }); + }); - test.done(); - }, - 'sanity checks on version weights'(test: Test) { + test('sanity checks on version weights', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -179,27 +153,25 @@ export = { const version = fn.addVersion('1'); // WHEN: Individual weight too high - test.throws(() => { + expect(() => { new lambda.Alias(stack, 'Alias1', { aliasName: 'prod', version, additionalVersions: [{ version, weight: 5 }], }); - }); + }).toThrow(); // WHEN: Sum too high - test.throws(() => { + expect(() => { new lambda.Alias(stack, 'Alias2', { aliasName: 'prod', version, additionalVersions: [{ version, weight: 0.5 }, { version, weight: 0.6 }], }); - }); + }).toThrow(); + }); - test.done(); - }, - - 'metric adds Resource: aliasArn to dimensions'(test: Test) { + test('metric adds Resource: aliasArn to dimensions', () => { const stack = new Stack(); // GIVEN @@ -221,7 +193,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { Dimensions: [{ Name: 'FunctionName', Value: { @@ -239,12 +211,10 @@ export = { ], }, }], - })); - - test.done(); - }, + }); + }); - 'sanity checks provisionedConcurrentExecutions'(test: Test) { + test('sanity checks provisionedConcurrentExecutions', () => { const stack = new Stack(); const pce = -1; @@ -255,33 +225,31 @@ export = { }); // WHEN: Alias provisionedConcurrencyConfig less than 0 - test.throws(() => { + expect(() => { new lambda.Alias(stack, 'Alias1', { aliasName: 'prod', version: fn.addVersion('1'), provisionedConcurrentExecutions: pce, }); - }); + }).toThrow(); // WHEN: Version provisionedConcurrencyConfig less than 0 - test.throws(() => { + expect(() => { new lambda.Version(stack, 'Version 1', { lambda: fn, codeSha256: undefined, description: undefined, provisionedConcurrentExecutions: pce, }); - }); + }).toThrow(); // WHEN: Adding a version provisionedConcurrencyConfig less than 0 - test.throws(() => { + expect(() => { fn.addVersion('1', undefined, undefined, pce); - }); - - test.done(); - }, + }).toThrow(); + }); - 'alias exposes real Lambdas role'(test: Test) { + test('alias exposes real Lambdas role', () => { const stack = new Stack(); // GIVEN @@ -295,12 +263,10 @@ export = { const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // THEN - test.equals(alias.role, fn.role); - - test.done(); - }, + expect(alias.role).toEqual(fn.role); + }); - 'functionName is derived from the aliasArn so that dependencies are sound'(test: Test) { + test('functionName is derived from the aliasArn so that dependencies are sound', () => { const stack = new Stack(); // GIVEN @@ -314,7 +280,7 @@ export = { const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN - test.deepEqual(stack.resolve(alias.functionName), { + expect(stack.resolve(alias.functionName)).toEqual({ 'Fn::Join': [ '', [ @@ -335,11 +301,9 @@ export = { ], ], }); + }); - test.done(); - }, - - 'with event invoke config'(test: Test) { + test('with event invoke config', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'fn', { @@ -361,7 +325,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { FunctionName: { Ref: 'fn5FF616E3', }, @@ -383,12 +347,10 @@ export = { Destination: 'on-success-arn', }, }, - })); - - test.done(); - }, + }); + }); - 'throws when calling configureAsyncInvoke on already configured alias'(test: Test) { + test('throws when calling configureAsyncInvoke on already configured alias', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'fn', { @@ -408,12 +370,10 @@ export = { }); // THEN - test.throws(() => alias.configureAsyncInvoke({ retryAttempts: 0 }), /An EventInvokeConfig has already been configured/); + expect(() => alias.configureAsyncInvoke({ retryAttempts: 0 })).toThrow(/An EventInvokeConfig has already been configured/); + }); - test.done(); - }, - - 'event invoke config on imported alias'(test: Test) { + test('event invoke config on imported alias', () => { // GIVEN const stack = new Stack(); const fn = lambda.Version.fromVersionArn(stack, 'Fn', 'arn:aws:lambda:region:account-id:function:function-name:version'); @@ -425,16 +385,14 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { FunctionName: 'function-name', Qualifier: 'alias-name', MaximumRetryAttempts: 1, - })); - - test.done(); - }, + }); + }); - 'can enable AutoScaling on aliases'(test: Test): void { + test('can enable AutoScaling on aliases', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -454,7 +412,7 @@ export = { alias.addAutoScaling({ maxCapacity: 5 }); // THEN - expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { MinCapacity: 1, MaxCapacity: 5, ResourceId: objectLike({ @@ -471,12 +429,10 @@ export = { ':prod', )), }), - })); - - test.done(); - }, + }); + }); - 'can enable AutoScaling on aliases with Provisioned Concurrency set'(test: Test): void { + test('can enable AutoScaling on aliases with Provisioned Concurrency set', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -497,7 +453,7 @@ export = { alias.addAutoScaling({ maxCapacity: 5 }); // THEN - expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { MinCapacity: 1, MaxCapacity: 5, ResourceId: objectLike({ @@ -514,17 +470,16 @@ export = { ':prod', )), }), - })); + }); - expect(stack).to(haveResourceLike('AWS::Lambda::Alias', { + expect(stack).toHaveResourceLike('AWS::Lambda::Alias', { ProvisionedConcurrencyConfig: { ProvisionedConcurrentExecutions: 10, }, - })); - test.done(); - }, + }); + }); - 'validation for utilizationTarget does not fail when using Tokens'(test: Test) { + test('validation for utilizationTarget does not fail when using Tokens', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -547,19 +502,16 @@ export = { target.scaleOnUtilization({ utilizationTarget: Lazy.numberValue({ produce: () => 0.95 }) }); // THEN: no exception - expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'LambdaProvisionedConcurrencyUtilization' }, TargetValue: 0.95, }, + }); + }); - })); - - test.done(); - }, - - 'cannot enable AutoScaling twice on same property'(test: Test): void { + test('cannot enable AutoScaling twice on same property', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -579,12 +531,10 @@ export = { alias.addAutoScaling({ maxCapacity: 5 }); // THEN - test.throws(() => alias.addAutoScaling({ maxCapacity: 8 }), /AutoScaling already enabled for this alias/); + expect(() => alias.addAutoScaling({ maxCapacity: 8 })).toThrow(/AutoScaling already enabled for this alias/); + }); - test.done(); - }, - - 'error when specifying invalid utilization value when AutoScaling on utilization'(test: Test): void { + test('error when specifying invalid utilization value when AutoScaling on utilization', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -604,11 +554,10 @@ export = { const target = alias.addAutoScaling({ maxCapacity: 5 }); // THEN - test.throws(() => target.scaleOnUtilization({ utilizationTarget: 0.95 }), /Utilization Target should be between 0.1 and 0.9. Found 0.95/); - test.done(); - }, + expect(() => target.scaleOnUtilization({ utilizationTarget: 0.95 })).toThrow(/Utilization Target should be between 0.1 and 0.9. Found 0.95/); + }); - 'can autoscale on a schedule'(test: Test): void { + test('can autoscale on a schedule', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -632,7 +581,7 @@ export = { }); // THEN - expect(stack).to(haveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { ScheduledActions: [ { ScalableTargetAction: { MaxCapacity: 10 }, @@ -640,8 +589,6 @@ export = { ScheduledActionName: 'Scheduling', }, ], - })); - - test.done(); - }, -}; + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts similarity index 58% rename from packages/@aws-cdk/aws-lambda/test/test.code.ts rename to packages/@aws-cdk/aws-lambda/test/code.test.ts index b98653828d3c9..b00cf74562c3e 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -1,37 +1,33 @@ +import '@aws-cdk/assert/jest'; import * as path from 'path'; -import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; +import { ResourcePart } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { Test } from 'nodeunit'; import * as lambda from '../lib'; /* eslint-disable dot-notation */ -export = { - 'lambda.Code.fromInline': { - 'fails if used with unsupported runtimes'(test: Test) { - test.throws(() => defineFunction(lambda.Code.fromInline('boom'), lambda.Runtime.GO_1_X), /Inline source not allowed for go1\.x/); - test.throws(() => defineFunction(lambda.Code.fromInline('boom'), lambda.Runtime.JAVA_8), /Inline source not allowed for java8/); - test.done(); - }, - 'fails if larger than 4096 bytes'(test: Test) { - test.throws( - () => defineFunction(lambda.Code.fromInline(generateRandomString(4097)), lambda.Runtime.NODEJS_10_X), - /Lambda source is too large, must be <= 4096 but is 4097/); - test.done(); - }, - }, - 'lambda.Code.fromAsset': { - 'fails if a non-zip asset is used'(test: Test) { +describe('code', () => { + describe('lambda.Code.fromInline', () => { + test('fails if used with unsupported runtimes', () => { + expect(() => defineFunction(lambda.Code.fromInline('boom'), lambda.Runtime.GO_1_X)).toThrow(/Inline source not allowed for go1\.x/); + expect(() => defineFunction(lambda.Code.fromInline('boom'), lambda.Runtime.JAVA_8)).toThrow(/Inline source not allowed for java8/); + }); + test('fails if larger than 4096 bytes', () => { + expect(() => defineFunction(lambda.Code.fromInline(generateRandomString(4097)), lambda.Runtime.NODEJS_10_X)) + .toThrow(/Lambda source is too large, must be <= 4096 but is 4097/); + }); + }); + describe('lambda.Code.fromAsset', () => { + test('fails if a non-zip asset is used', () => { // GIVEN const fileAsset = lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler', 'index.py')); // THEN - test.throws(() => defineFunction(fileAsset), /Asset must be a \.zip file or a directory/); - test.done(); - }, + expect(() => defineFunction(fileAsset)).toThrow(/Asset must be a \.zip file or a directory/); + }); - 'only one Asset object gets created even if multiple functions use the same AssetCode'(test: Test) { + test('only one Asset object gets created even if multiple functions use the same AssetCode', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'MyStack'); @@ -55,11 +51,10 @@ export = { const synthesized = assembly.stacks[0]; // Func1 has an asset, Func2 does not - test.deepEqual(synthesized.assets.length, 1); - test.done(); - }, + expect(synthesized.assets.length).toEqual(1); + }); - 'adds code asset metadata'(test: Test) { + test('adds code asset metadata', () => { // GIVEN const stack = new cdk.Stack(); stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); @@ -74,18 +69,17 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { Metadata: { [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232', [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code', }, - }, ResourcePart.CompleteDefinition)); - test.done(); - }, - }, + }, ResourcePart.CompleteDefinition); + }); + }); - 'lambda.Code.fromCfnParameters': { - "automatically creates the Bucket and Key parameters when it's used in a Function"(test: Test) { + describe('lambda.Code.fromCfnParameters', () => { + test("automatically creates the Bucket and Key parameters when it's used in a Function", () => { const stack = new cdk.Stack(); const code = new lambda.CfnParametersCode(); new lambda.Function(stack, 'Function', { @@ -94,7 +88,7 @@ export = { handler: 'index.handler', }); - expect(stack).to(haveResourceLike('AWS::Lambda::Function', { + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { Code: { S3Bucket: { Ref: 'FunctionLambdaSourceBucketNameParameter9E9E108F', @@ -103,29 +97,21 @@ export = { Ref: 'FunctionLambdaSourceObjectKeyParameter1C7AED11', }, }, - })); - - test.equal(stack.resolve(code.bucketNameParam), 'FunctionLambdaSourceBucketNameParameter9E9E108F'); - test.equal(stack.resolve(code.objectKeyParam), 'FunctionLambdaSourceObjectKeyParameter1C7AED11'); + }); - test.done(); - }, + expect(stack.resolve(code.bucketNameParam)).toEqual('FunctionLambdaSourceBucketNameParameter9E9E108F'); + expect(stack.resolve(code.objectKeyParam)).toEqual('FunctionLambdaSourceObjectKeyParameter1C7AED11'); + }); - 'does not allow accessing the Parameter properties before being used in a Function'(test: Test) { + test('does not allow accessing the Parameter properties before being used in a Function', () => { const code = new lambda.CfnParametersCode(); - test.throws(() => { - test.notEqual(code.bucketNameParam, undefined); - }, /bucketNameParam/); - - test.throws(() => { - test.notEqual(code.objectKeyParam, undefined); - }, /objectKeyParam/); + expect(() => code.bucketNameParam).toThrow(/bucketNameParam/); - test.done(); - }, + expect(() => code.objectKeyParam).toThrow(/objectKeyParam/); + }); - 'allows passing custom Parameters when creating it'(test: Test) { + test('allows passing custom Parameters when creating it', () => { const stack = new cdk.Stack(); const bucketNameParam = new cdk.CfnParameter(stack, 'BucketNameParam', { type: 'String', @@ -139,8 +125,8 @@ export = { objectKeyParam: bucketKeyParam, }); - test.equal(stack.resolve(code.bucketNameParam), 'BucketNameParam'); - test.equal(stack.resolve(code.objectKeyParam), 'ObjectKeyParam'); + expect(stack.resolve(code.bucketNameParam)).toEqual('BucketNameParam'); + expect(stack.resolve(code.objectKeyParam)).toEqual('ObjectKeyParam'); new lambda.Function(stack, 'Function', { code, @@ -148,7 +134,7 @@ export = { handler: 'index.handler', }); - expect(stack).to(haveResourceLike('AWS::Lambda::Function', { + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { Code: { S3Bucket: { Ref: 'BucketNameParam', @@ -157,12 +143,10 @@ export = { Ref: 'ObjectKeyParam', }, }, - })); - - test.done(); - }, + }); + }); - 'can assign parameters'(test: Test) { + test('can assign parameters', () => { // given const stack = new cdk.Stack(); const code = new lambda.CfnParametersCode({ @@ -181,13 +165,11 @@ export = { })); // then - test.equal(overrides['BucketNameParam'], 'SomeBucketName'); - test.equal(overrides['ObjectKeyParam'], 'SomeObjectKey'); - - test.done(); - }, - }, -}; + expect(overrides['BucketNameParam']).toEqual('SomeBucketName'); + expect(overrides['ObjectKeyParam']).toEqual('SomeObjectKey'); + }); + }); +}); function defineFunction(code: lambda.Code, runtime: lambda.Runtime = lambda.Runtime.NODEJS_10_X) { const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts similarity index 51% rename from packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts rename to packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts index bc9492f1d2f7e..78c11922bf183 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts @@ -1,10 +1,9 @@ -import { expect, haveResourceLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; import { Code, EventSourceMapping, Function, Runtime } from '../lib'; -export = { - 'throws if maxBatchingWindow > 300 seconds'(test: Test) { +describe('event source mapping', () => { + test('throws if maxBatchingWindow > 300 seconds', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -12,19 +11,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - maxBatchingWindow: cdk.Duration.seconds(301), - }), /maxBatchingWindow cannot be over 300 seconds/); - - test.done(); - }, - 'throws if maxRecordAge is below 60 seconds'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + maxBatchingWindow: cdk.Duration.seconds(301), + })).toThrow(/maxBatchingWindow cannot be over 300 seconds/); + }); + + test('throws if maxRecordAge is below 60 seconds', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -32,19 +26,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - maxRecordAge: cdk.Duration.seconds(59), - }), /maxRecordAge must be between 60 seconds and 7 days inclusive/); - - test.done(); - }, - 'throws if maxRecordAge is over 7 days'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + maxRecordAge: cdk.Duration.seconds(59), + })).toThrow(/maxRecordAge must be between 60 seconds and 7 days inclusive/); + }); + + test('throws if maxRecordAge is over 7 days', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -52,19 +41,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - maxRecordAge: cdk.Duration.seconds(604801), - }), /maxRecordAge must be between 60 seconds and 7 days inclusive/); - - test.done(); - }, - 'throws if retryAttempts is negative'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + maxRecordAge: cdk.Duration.seconds(604801), + })).toThrow(/maxRecordAge must be between 60 seconds and 7 days inclusive/); + }); + + test('throws if retryAttempts is negative', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -72,19 +56,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - retryAttempts: -1, - }), /retryAttempts must be between 0 and 10000 inclusive, got -1/); - - test.done(); - }, - 'throws if retryAttempts is over 10000'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + retryAttempts: -1, + })).toThrow(/retryAttempts must be between 0 and 10000 inclusive, got -1/); + }); + + test('throws if retryAttempts is over 10000', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -92,19 +71,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - retryAttempts: 10001, - }), /retryAttempts must be between 0 and 10000 inclusive, got 10001/); - - test.done(); - }, - 'accepts if retryAttempts is a token'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + retryAttempts: 10001, + })).toThrow(/retryAttempts must be between 0 and 10000 inclusive, got 10001/); + }); + + test('accepts if retryAttempts is a token', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -117,10 +91,9 @@ export = { eventSourceArn: '', retryAttempts: cdk.Lazy.numberValue({ produce: () => 100 }), }); + }); - test.done(); - }, - 'throws if parallelizationFactor is below 1'(test: Test) { + test('throws if parallelizationFactor is below 1', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -128,19 +101,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - parallelizationFactor: 0, - }), /parallelizationFactor must be between 1 and 10 inclusive, got 0/); - - test.done(); - }, - 'throws if parallelizationFactor is over 10'(test: Test) { + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + parallelizationFactor: 0, + })).toThrow(/parallelizationFactor must be between 1 and 10 inclusive, got 0/); + }); + + test('throws if parallelizationFactor is over 10', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -148,20 +116,14 @@ export = { runtime: Runtime.NODEJS_10_X, }); - test.throws(() => - new EventSourceMapping( - stack, - 'test', - { - target: fn, - eventSourceArn: '', - parallelizationFactor: 11, - }), /parallelizationFactor must be between 1 and 10 inclusive, got 11/); - - test.done(); - }, + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + parallelizationFactor: 11, + })).toThrow(/parallelizationFactor must be between 1 and 10 inclusive, got 11/); + }); - 'accepts if parallelizationFactor is a token'(test: Test) { + test('accepts if parallelizationFactor is a token', () => { const stack = new cdk.Stack(); const fn = new Function(stack, 'fn', { handler: 'index.handler', @@ -174,20 +136,17 @@ export = { eventSourceArn: '', parallelizationFactor: cdk.Lazy.numberValue({ produce: () => 20 }), }); + }); - test.done(); - }, - - 'import event source mapping'(test: Test) { + test('import event source mapping', () => { const stack = new cdk.Stack(undefined, undefined, { stackName: 'test-stack' }); const imported = EventSourceMapping.fromEventSourceMappingId(stack, 'imported', '14e0db71-5d35-4eb5-b481-8945cf9d10c2'); - test.equals(imported.eventSourceMappingId, '14e0db71-5d35-4eb5-b481-8945cf9d10c2'); - test.equals(imported.stack.stackName, 'test-stack'); - test.done(); - }, + expect(imported.eventSourceMappingId).toEqual('14e0db71-5d35-4eb5-b481-8945cf9d10c2'); + expect(imported.stack.stackName).toEqual('test-stack'); + }); - 'accepts if kafkaTopic is a parameter'(test: Test) { + test('accepts if kafkaTopic is a parameter', () => { const stack = new cdk.Stack(); const topicNameParam = new cdk.CfnParameter(stack, 'TopicNameParam', { type: 'String', @@ -205,12 +164,10 @@ export = { kafkaTopic: topicNameParam.valueAsString, }); - expect(stack).to(haveResourceLike('AWS::Lambda::EventSourceMapping', { + expect(stack).toHaveResourceLike('AWS::Lambda::EventSourceMapping', { Topics: [{ Ref: 'TopicNameParam', }], - })); - - test.done(); - }, -}; + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts similarity index 68% rename from packages/@aws-cdk/aws-lambda/test/test.function-hash.ts rename to packages/@aws-cdk/aws-lambda/test/function-hash.test.ts index b17050d43c8d0..4f80c886e88d2 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts +++ b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts @@ -1,30 +1,27 @@ +import '@aws-cdk/assert/jest'; import * as path from 'path'; import { CfnOutput, Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; import * as lambda from '../lib'; import { calculateFunctionHash, trimFromStart } from '../lib/function-hash'; -export = { - 'trimFromStart': { +describe('function hash', () => { + describe('trimFromStart', () => { - 'trim not needed'(test: Test) { - test.deepEqual(trimFromStart('foo', 100), 'foo'); - test.deepEqual(trimFromStart('foo', 3), 'foo'); - test.deepEqual(trimFromStart('', 3), ''); - test.done(); - }, - - 'trim required'(test: Test) { - test.deepEqual(trimFromStart('hello', 3), 'llo'); - test.deepEqual(trimFromStart('hello', 4), 'ello'); - test.deepEqual(trimFromStart('hello', 1), 'o'); - test.done(); - }, + test('trim not needed', () => { + expect(trimFromStart('foo', 100)).toEqual('foo'); + expect(trimFromStart('foo', 3)).toEqual('foo'); + expect(trimFromStart('', 3)).toEqual(''); + }); - }, + test('trim required', () => { + expect(trimFromStart('hello', 3)).toEqual('llo'); + expect(trimFromStart('hello', 4)).toEqual('ello'); + expect(trimFromStart('hello', 1)).toEqual('o'); + }); + }); - 'calcHash': { - 'same configuration and code yields the same hash'(test: Test) { + describe('calcHash', () => { + test('same configuration and code yields the same hash', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction1', { runtime: lambda.Runtime.NODEJS_12_X, @@ -39,13 +36,12 @@ export = { handler: 'index.handler', }); - test.deepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); - test.deepEqual(calculateFunctionHash(fn1), 'aea5463dba236007afe91d2832b3c836'); - test.done(); - }, - }, + expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); + expect(calculateFunctionHash(fn1)).toEqual('aea5463dba236007afe91d2832b3c836'); + }); + }); - 'code impacts hash'(test: Test) { + test('code impacts hash', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction1', { runtime: lambda.Runtime.NODEJS_12_X, @@ -53,12 +49,11 @@ export = { handler: 'index.handler', }); - test.notDeepEqual(calculateFunctionHash(fn1), 'aea5463dba236007afe91d2832b3c836'); - test.deepEqual(calculateFunctionHash(fn1), '979b4a14c6f174c745cdbcd1036cf844'); - test.done(); - }, + expect(calculateFunctionHash(fn1)).not.toEqual('aea5463dba236007afe91d2832b3c836'); + expect(calculateFunctionHash(fn1)).toEqual('979b4a14c6f174c745cdbcd1036cf844'); + }); - 'environment variables impact hash'(test: Test) { + test('environment variables impact hash', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, @@ -79,12 +74,11 @@ export = { }, }); - test.deepEqual(calculateFunctionHash(fn1), 'd1bc824ac5022b7d62d8b12dbae6580c'); - test.deepEqual(calculateFunctionHash(fn2), '3b683d05465012b0aa9c4ff53b32f014'); - test.done(); - }, + expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); + expect(calculateFunctionHash(fn2)).toEqual('3b683d05465012b0aa9c4ff53b32f014'); + }); - 'runtime impacts hash'(test: Test) { + test('runtime impacts hash', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, @@ -105,12 +99,11 @@ export = { }, }); - test.deepEqual(calculateFunctionHash(fn1), 'd1bc824ac5022b7d62d8b12dbae6580c'); - test.deepEqual(calculateFunctionHash(fn2), '0f168f0772463e8e547bb3800937e54d'); - test.done(); - }, + expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); + expect(calculateFunctionHash(fn2)).toEqual('0f168f0772463e8e547bb3800937e54d'); + }); - 'inline code change impacts the hash'(test: Test) { + test('inline code change impacts the hash', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, @@ -125,14 +118,13 @@ export = { handler: 'index.handler', }); - test.deepEqual(calculateFunctionHash(fn1), 'ebf2e871fc6a3062e8bdcc5ebe16db3f'); - test.deepEqual(calculateFunctionHash(fn2), 'ffedf6424a18a594a513129dc97bf53c'); - test.done(); - }, + expect(calculateFunctionHash(fn1)).toEqual('ebf2e871fc6a3062e8bdcc5ebe16db3f'); + expect(calculateFunctionHash(fn2)).toEqual('ffedf6424a18a594a513129dc97bf53c'); + }); - 'impact of env variables order on hash': { + describe('impact of env variables order on hash', () => { - 'without "currentVersion", we preserve old behavior to avoid unnesesary invalidation of templates'(test: Test) { + test('without "currentVersion", we preserve old behavior to avoid unnesesary invalidation of templates', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, @@ -155,11 +147,10 @@ export = { }, }); - test.notDeepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); - test.done(); - }, + expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); + }); - 'with "currentVersion", we sort env keys so order is consistent'(test: Test) { + test('with "currentVersion", we sort env keys so order is consistent', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, @@ -186,9 +177,7 @@ export = { new CfnOutput(stack2, 'VersionArn', { value: fn2.currentVersion.functionArn }); - test.deepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); - test.done(); - }, - - }, -}; + expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts new file mode 100644 index 0000000000000..761a688762c48 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -0,0 +1,1795 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import { ABSENT, ResourcePart, SynthUtils } from '@aws-cdk/assert'; +import { ProfilingGroup } from '@aws-cdk/aws-codeguruprofiler'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as efs from '@aws-cdk/aws-efs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as cdk from '@aws-cdk/core'; +import * as constructs from 'constructs'; +import * as _ from 'lodash'; +import * as lambda from '../lib'; + +describe('function', () => { + test('default function', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: + { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'lambda.amazonaws.com' }, + }], + Version: '2012-10-17', + }, + ManagedPolicyArns: + [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: + { + Code: { ZipFile: 'foo' }, + Handler: 'index.handler', + Role: { 'Fn::GetAtt': ['MyLambdaServiceRole4539ECB6', 'Arn'] }, + Runtime: 'nodejs10.x', + }, + DependsOn: ['MyLambdaServiceRole4539ECB6'], + }, ResourcePart.CompleteDefinition); + }); + + test('adds policy permissions', () => { + const stack = new cdk.Stack(); + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + initialPolicy: [new iam.PolicyStatement({ actions: ['*'], resources: ['*'] })], + }); + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: + { + Statement: + [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'lambda.amazonaws.com' }, + }], + Version: '2012-10-17', + }, + ManagedPolicyArns: + // eslint-disable-next-line max-len + [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], + }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: '*', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { ZipFile: 'foo' }, + Handler: 'index.handler', + Role: { 'Fn::GetAtt': ['MyLambdaServiceRole4539ECB6', 'Arn'] }, + Runtime: 'nodejs10.x', + }, + DependsOn: ['MyLambdaServiceRoleDefaultPolicy5BBC6F68', 'MyLambdaServiceRole4539ECB6'], + }, ResourcePart.CompleteDefinition); + }); + + test('fails if inline code is used for an invalid runtime', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.DOTNET_CORE_2, + })).toThrow(); + }); + + describe('addToResourcePolicy', () => { + test('can be used to add permissions to the Lambda function', () => { + const stack = new cdk.Stack(); + const fn = newTestLambda(stack); + + fn.addPermission('S3Permission', { + action: 'lambda:*', + principal: new iam.ServicePrincipal('s3.amazonaws.com'), + sourceAccount: stack.account, + sourceArn: 'arn:aws:s3:::my_bucket', + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + ManagedPolicyArns: + // eslint-disable-next-line max-len + [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { + ZipFile: 'foo', + }, + Handler: 'bar', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'python2.7', + }, + DependsOn: [ + 'MyLambdaServiceRole4539ECB6', + ], + }, ResourcePart.CompleteDefinition); + + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:*', + FunctionName: { + 'Fn::GetAtt': [ + 'MyLambdaCCE802FB', + 'Arn', + ], + }, + Principal: 's3.amazonaws.com', + SourceAccount: { + Ref: 'AWS::AccountId', + }, + SourceArn: 'arn:aws:s3:::my_bucket', + }); + }); + + test('fails if the principal is not a service, account or arn principal', () => { + const stack = new cdk.Stack(); + const fn = newTestLambda(stack); + + expect(() => fn.addPermission('F1', { principal: new iam.OrganizationPrincipal('org') })) + .toThrow(/Invalid principal type for Lambda permission statement/); + + fn.addPermission('S1', { principal: new iam.ServicePrincipal('my-service') }); + fn.addPermission('S2', { principal: new iam.AccountPrincipal('account') }); + fn.addPermission('S3', { principal: new iam.ArnPrincipal('my:arn') }); + }); + + test('BYORole', () => { + // GIVEN + const stack = new cdk.Stack(); + const role = new iam.Role(stack, 'SomeRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + role.addToPolicy(new iam.PolicyStatement({ actions: ['confirm:itsthesame'], resources: ['*'] })); + + // WHEN + const fn = new lambda.Function(stack, 'Function', { + code: new lambda.InlineCode('test'), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.test', + role, + initialPolicy: [ + new iam.PolicyStatement({ actions: ['inline:inline'], resources: ['*'] }), + ], + }); + + fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['explicit:explicit'], resources: ['*'] })); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { Action: 'confirm:itsthesame', Effect: 'Allow', Resource: '*' }, + { Action: 'inline:inline', Effect: 'Allow', Resource: '*' }, + { Action: 'explicit:explicit', Effect: 'Allow', Resource: '*' }, + ], + }, + }); + }); + }); + + test('fromFunctionArn', () => { + // GIVEN + const stack2 = new cdk.Stack(); + + // WHEN + const imported = lambda.Function.fromFunctionArn(stack2, 'Imported', 'arn:aws:lambda:us-east-1:123456789012:function:ProcessKinesisRecords'); + + // THEN + expect(imported.functionArn).toEqual('arn:aws:lambda:us-east-1:123456789012:function:ProcessKinesisRecords'); + expect(imported.functionName).toEqual('ProcessKinesisRecords'); + }); + + describe('addPermissions', () => { + test('imported Function w/ resolved account and function arn', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Imports', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + + // WHEN + const iFunc = lambda.Function.fromFunctionAttributes(stack, 'iFunc', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction', + }); + iFunc.addPermission('iFunc', { + principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission'); + }); + + test('imported Function w/ unresolved account', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Imports'); + + // WHEN + const iFunc = lambda.Function.fromFunctionAttributes(stack, 'iFunc', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction', + }); + iFunc.addPermission('iFunc', { + principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission'); + }); + + test('imported Function w/different account', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Base', { + env: { account: '111111111111' }, + }); + + // WHEN + const iFunc = lambda.Function.fromFunctionAttributes(stack, 'iFunc', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction', + }); + iFunc.addPermission('iFunc', { + principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), + }); + + // THEN + expect(stack).not.toHaveResource('AWS::Lambda::Permission'); + }); + }); + + test('Lambda code can be read from a local directory via an asset', () => { + // GIVEN + const stack = new cdk.Stack(); + new lambda.Function(stack, 'MyLambda', { + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_6, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + S3Bucket: { + Ref: 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3Bucket1354C645', + }, + S3Key: { + 'Fn::Join': ['', [ + { 'Fn::Select': [0, { 'Fn::Split': ['||', { Ref: 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3VersionKey5D873FAC' }] }] }, + { 'Fn::Select': [1, { 'Fn::Split': ['||', { Ref: 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3VersionKey5D873FAC' }] }] }, + ]], + }, + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'python3.6', + }); + }); + + test('default function with SQS DLQ when client sets deadLetterQueueEnabled to true and functionName defined by client', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + functionName: 'OneFunctionToRuleThemAll', + deadLetterQueueEnabled: true, + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'MyLambdaDeadLetterQueue399EEA2D', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { + ZipFile: 'foo', + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': [ + 'MyLambdaDeadLetterQueue399EEA2D', + 'Arn', + ], + }, + }, + FunctionName: 'OneFunctionToRuleThemAll', + }, + DependsOn: [ + 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + 'MyLambdaServiceRole4539ECB6', + ], + }, ResourcePart.CompleteDefinition); + }); + + test('default function with SQS DLQ when client sets deadLetterQueueEnabled to true and functionName not defined by client', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + deadLetterQueueEnabled: true, + }); + + expect(stack).toHaveResource('AWS::SQS::Queue', { + MessageRetentionPeriod: 1209600, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': [ + 'MyLambdaDeadLetterQueue399EEA2D', + 'Arn', + ], + }, + }, + }); + }); + + test('default function with SQS DLQ when client sets deadLetterQueueEnabled to false', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + deadLetterQueueEnabled: false, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + ZipFile: 'foo', + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + }); + }); + + test('default function with SQS DLQ when client provides Queue to be used as DLQ', () => { + const stack = new cdk.Stack(); + + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { + queueName: 'MyLambda_DLQ', + retentionPeriod: cdk.Duration.days(14), + }); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + deadLetterQueue: dlQueue, + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + }, + }); + }); + + test('default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to true', () => { + const stack = new cdk.Stack(); + + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { + queueName: 'MyLambda_DLQ', + retentionPeriod: cdk.Duration.days(14), + }); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + deadLetterQueueEnabled: true, + deadLetterQueue: dlQueue, + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + }, + }); + }); + + test('error when default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to false', () => { + const stack = new cdk.Stack(); + + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { + queueName: 'MyLambda_DLQ', + retentionPeriod: cdk.Duration.days(14), + }); + + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + deadLetterQueueEnabled: false, + deadLetterQueue: dlQueue, + })).toThrow(/deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false/); + }); + + test('default function with Active tracing', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + tracing: lambda.Tracing.ACTIVE, + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { + ZipFile: 'foo', + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + TracingConfig: { + Mode: 'Active', + }, + }, + DependsOn: [ + 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + 'MyLambdaServiceRole4539ECB6', + ], + }, ResourcePart.CompleteDefinition); + }); + + test('default function with PassThrough tracing', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + tracing: lambda.Tracing.PASS_THROUGH, + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { + ZipFile: 'foo', + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + TracingConfig: { + Mode: 'PassThrough', + }, + }, + DependsOn: [ + 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + 'MyLambdaServiceRole4539ECB6', + ], + }, ResourcePart.CompleteDefinition); + }); + + test('default function with Disabled tracing', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + tracing: lambda.Tracing.DISABLED, + }); + + expect(stack).not.toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Properties: { + Code: { + ZipFile: 'foo', + }, + Handler: 'index.handler', + Role: { + 'Fn::GetAtt': [ + 'MyLambdaServiceRole4539ECB6', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + }, + DependsOn: [ + 'MyLambdaServiceRole4539ECB6', + ], + }, ResourcePart.CompleteDefinition); + }); + + describe('grantInvoke', () => { + + test('adds iam:InvokeFunction', () => { + // GIVEN + const stack = new cdk.Stack(); + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.AccountPrincipal('1234'), + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.grantInvoke(role); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Function76856677', 'Arn'] }, + }, + ], + }, + }); + }); + + test('with a service principal', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + const service = new iam.ServicePrincipal('apigateway.amazonaws.com'); + + // WHEN + fn.grantInvoke(service); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: 'apigateway.amazonaws.com', + }); + }); + + test('with an account principal', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + const account = new iam.AccountPrincipal('123456789012'); + + // WHEN + fn.grantInvoke(account); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: '123456789012', + }); + }); + + test('with an arn principal', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + const account = new iam.ArnPrincipal('arn:aws:iam::123456789012:role/someRole'); + + // WHEN + fn.grantInvoke(account); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: 'arn:aws:iam::123456789012:role/someRole', + }); + }); + + test('can be called twice for the same service principal', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + const service = new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com'); + + // WHEN + fn.grantInvoke(service); + fn.grantInvoke(service); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: 'elasticloadbalancing.amazonaws.com', + }); + }); + + test('with an imported role (in the same account)', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '123456789012' }, + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); + + // THEN + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Function76856677', 'Arn'] }, + }, + ], + }, + Roles: ['someRole'], + }); + }); + + test('with an imported role (from a different account)', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '3333' }, + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: 'arn:aws:iam::123456789012:role/someRole', + }); + }); + + test('on an imported function (same account)', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '123456789012' }, + }); + const fn = lambda.Function.fromFunctionArn(stack, 'Function', 'arn:aws:lambda:us-east-1:123456789012:function:MyFn'); + + // WHEN + fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', + Principal: 'elasticloadbalancing.amazonaws.com', + }); + }); + + test('on an imported function (unresolved account)', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = lambda.Function.fromFunctionArn(stack, 'Function', 'arn:aws:lambda:us-east-1:123456789012:function:MyFn'); + + // WHEN + fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', + Principal: 'elasticloadbalancing.amazonaws.com', + }); + }); + + test('on an imported function (different account)', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '111111111111' }, // Different account + }); + const fn = lambda.Function.fromFunctionArn(stack, 'Function', 'arn:aws:lambda:us-east-1:123456789012:function:MyFn'); + + // THEN + expect(() => { fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); }) + .toThrow(/Cannot modify permission to lambda function/); + }); + }); + + test('Can use metricErrors on a lambda Function', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // THEN + expect(stack.resolve(fn.metricErrors())).toEqual({ + dimensions: { FunctionName: { Ref: 'Function76856677' } }, + namespace: 'AWS/Lambda', + metricName: 'Errors', + period: cdk.Duration.minutes(5), + statistic: 'Sum', + }); + }); + + test('addEventSource calls bind', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + let bindTarget; + + class EventSourceMock implements lambda.IEventSource { + public bind(target: lambda.IFunction) { + bindTarget = target; + } + } + + // WHEN + fn.addEventSource(new EventSourceMock()); + + // THEN + expect(bindTarget).toEqual(fn); + }); + + test('using an incompatible layer', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const layer = lambda.LayerVersion.fromLayerVersionAttributes(stack, 'TestLayer', { + layerVersionArn: 'arn:aws:...', + compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], + }); + + // THEN + expect(() => new lambda.Function(stack, 'Function', { + layers: [layer], + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), + handler: 'index.main', + })).toThrow(/nodejs10.x is not in \[nodejs12.x\]/); + }); + + test('using more than 5 layers', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const layers = new Array(6).fill(lambda.LayerVersion.fromLayerVersionAttributes(stack, 'TestLayer', { + layerVersionArn: 'arn:aws:...', + compatibleRuntimes: [lambda.Runtime.NODEJS_10_X], + })); + + // THEN + expect(() => new lambda.Function(stack, 'Function', { + layers, + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), + handler: 'index.main', + })).toThrow(/Unable to add layer:/); + }); + + test('environment variables work in China', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'cn-north-1' } }); + + // WHEN + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS, + environment: { + SOME: 'Variable', + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + SOME: 'Variable', + }, + }, + }); + }); + + test('environment variables work in an unspecified region', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS, + environment: { + SOME: 'Variable', + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + SOME: 'Variable', + }, + }, + }); + }); + + test('support reserved concurrent executions', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS, + reservedConcurrentExecutions: 10, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + ReservedConcurrentExecutions: 10, + }); + }); + + test('its possible to specify event sources upon creation', () => { + // GIVEN + const stack = new cdk.Stack(); + + let bindCount = 0; + + class EventSource implements lambda.IEventSource { + public bind(_fn: lambda.IFunction): void { + bindCount++; + } + } + + // WHEN + new lambda.Function(stack, 'fn', { + code: lambda.Code.fromInline('boom'), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.bam', + events: [ + new EventSource(), + new EventSource(), + ], + }); + + // THEN + expect(bindCount).toEqual(2); + }); + + test('Provided Runtime returns the right values', () => { + const rt = lambda.Runtime.PROVIDED; + + expect(rt.name).toEqual('provided'); + expect(rt.supportsInlineCode).toEqual(false); + }); + + test('specify log retention', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS, + logRetention: logs.RetentionDays.ONE_MONTH, + }); + + // THEN + expect(stack).toHaveResource('Custom::LogRetention', { + LogGroupName: { + 'Fn::Join': [ + '', + [ + '/aws/lambda/', + { + Ref: 'MyLambdaCCE802FB', + }, + ], + ], + }, + RetentionInDays: 30, + }); + }); + + test('imported lambda with imported security group and allowAllOutbound set to false', () => { + // GIVEN + const stack = new cdk.Stack(); + + const fn = lambda.Function.fromFunctionAttributes(stack, 'fn', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', { + allowAllOutbound: false, + }), + }); + + // WHEN + fn.connections.allowToAnyIpv4(ec2.Port.tcp(443)); + + // THEN + expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: 'sg-123456789', + }); + }); + + test('with event invoke config', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + onFailure: { + bind: () => ({ destination: 'on-failure-arn' }), + }, + onSuccess: { + bind: () => ({ destination: 'on-success-arn' }), + }, + maxEventAge: cdk.Duration.hours(1), + retryAttempts: 0, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { + FunctionName: { + Ref: 'fn5FF616E3', + }, + Qualifier: '$LATEST', + DestinationConfig: { + OnFailure: { + Destination: 'on-failure-arn', + }, + OnSuccess: { + Destination: 'on-success-arn', + }, + }, + MaximumEventAgeInSeconds: 3600, + MaximumRetryAttempts: 0, + }); + }); + + test('throws when calling configureAsyncInvoke on already configured function', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + maxEventAge: cdk.Duration.hours(1), + }); + + // THEN + expect(() => fn.configureAsyncInvoke({ retryAttempts: 0 })).toThrow(/An EventInvokeConfig has already been configured/); + }); + + test('event invoke config on imported lambda', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = lambda.Function.fromFunctionAttributes(stack, 'fn', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function', + }); + + // WHEN + fn.configureAsyncInvoke({ + retryAttempts: 1, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { + FunctionName: 'my-function', + Qualifier: '$LATEST', + MaximumRetryAttempts: 1, + }); + }); + + test('add a version with event invoke config', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.addVersion('1', 'sha256', 'desc', undefined, { + retryAttempts: 0, + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { + FunctionName: { + Ref: 'fn5FF616E3', + }, + Qualifier: { + 'Fn::GetAtt': [ + 'fnVersion197FA813F', + 'Version', + ], + }, + MaximumRetryAttempts: 0, + }); + }); + + test('check edge compatibility with env vars that can be removed', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + fn.addEnvironment('KEY', 'value', { removeInEdge: true }); + + // WHEN + fn._checkEdgeCompatibility(); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: ABSENT, + }); + }); + + test('check edge compatibility with env vars that cannot be removed', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + environment: { + KEY: 'value', + }, + }); + fn.addEnvironment('OTHER_KEY', 'other_value', { removeInEdge: true }); + + // THEN + expect(() => fn._checkEdgeCompatibility()).toThrow(/The function Default\/fn contains environment variables \[KEY\] and is not compatible with Lambda@Edge/); + }); + + test('add incompatible layer', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + + const func = new lambda.Function(stack, 'myFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code, + }); + const layer = new lambda.LayerVersion(stack, 'myLayer', { + code, + compatibleRuntimes: [lambda.Runtime.NODEJS], + }); + + // THEN + expect(() => func.addLayers(layer)).toThrow( + /This lambda function uses a runtime that is incompatible with this layer/); + }); + + test('add compatible layer', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + + const func = new lambda.Function(stack, 'myFunc', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code, + }); + const layer = new lambda.LayerVersion(stack, 'myLayer', { + code, + compatibleRuntimes: [lambda.Runtime.PYTHON_3_7], + }); + + // THEN + // should not throw + expect(() => func.addLayers(layer)).not.toThrow(); + }); + + test('add compatible layer for deep clone', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + + const runtime = lambda.Runtime.PYTHON_3_7; + const func = new lambda.Function(stack, 'myFunc', { + runtime, + handler: 'index.handler', + code, + }); + const clone = _.cloneDeep(runtime); + const layer = new lambda.LayerVersion(stack, 'myLayer', { + code, + compatibleRuntimes: [clone], + }); + + // THEN + // should not throw + expect(() => func.addLayers(layer)).not.toThrow(); + }); + + test('empty inline code is not allowed', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN/THEN + expect(() => new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline(''), + })).toThrow(/Lambda inline code cannot be empty/); + }); + + test('logGroup is correctly returned', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + }); + const logGroup = fn.logGroup; + expect(logGroup.logGroupName).toBeDefined(); + expect(logGroup.logGroupArn).toBeDefined(); + }); + + test('dlq is returned when provided by user', () => { + const stack = new cdk.Stack(); + + const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { + queueName: 'MyLambda_DLQ', + retentionPeriod: cdk.Duration.days(14), + }); + + const fn = new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + deadLetterQueue: dlQueue, + }); + const deadLetterQueue = fn.deadLetterQueue; + expect(deadLetterQueue?.queueArn).toBeDefined(); + expect(deadLetterQueue?.queueName).toBeDefined(); + expect(deadLetterQueue?.queueUrl).toBeDefined(); + }); + + test('dlq is returned when setup by cdk', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + deadLetterQueueEnabled: true, + }); + const deadLetterQueue = fn.deadLetterQueue; + expect(deadLetterQueue?.queueArn).toBeDefined(); + expect(deadLetterQueue?.queueName).toBeDefined(); + expect(deadLetterQueue?.queueUrl).toBeDefined(); + }); + + test('dlq is undefined when not setup', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + }); + const deadLetterQueue = fn.deadLetterQueue; + expect(deadLetterQueue).toBeUndefined(); + }); + + test('one and only one child LogRetention construct will be created', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo'), + logRetention: logs.RetentionDays.FIVE_DAYS, + }); + + // Call logGroup a few times. If more than one instance of LogRetention was created, + // the second call will fail on duplicate constructs. + fn.logGroup; + fn.logGroup; + fn.logGroup; + }); + + test('fails when inline code is specified on an incompatible runtime', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'fn', { + handler: 'foo', + runtime: lambda.Runtime.PROVIDED, + code: lambda.Code.fromInline('foo'), + })).toThrow(/Inline source not allowed for/); + }); + + test('multiple calls to latestVersion returns the same version', () => { + const stack = new cdk.Stack(); + + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('hello()'), + handler: 'index.hello', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + const version1 = fn.latestVersion; + const version2 = fn.latestVersion; + + const expectedArn = { + 'Fn::Join': ['', [ + { 'Fn::GetAtt': ['MyLambdaCCE802FB', 'Arn'] }, + ':$LATEST', + ]], + }; + expect(version1).toEqual(version2); + expect(stack.resolve(version1.functionArn)).toEqual(expectedArn); + expect(stack.resolve(version2.functionArn)).toEqual(expectedArn); + }); + + describe('profiling group', () => { + test('default function with CDK created Profiling Group', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: true, + }); + + expect(stack).toHaveResource('AWS::CodeGuruProfiler::ProfilingGroup', { + ProfilingGroupName: 'MyLambdaProfilingGroupC5B6CCD8', + ComputePlatform: 'AWSLambda', + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'] }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + }); + }); + + test('default function with client provided Profiling Group', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + }); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': ['ProfilingGroup26979FD7', 'Arn'], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', + Roles: [ + { + Ref: 'MyLambdaServiceRole4539ECB6', + }, + ], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { + 'Fn::Join': [ + '', + [ + 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, + ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, + ], + ], + }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + }); + }); + + test('default function with client provided Profiling Group but profiling set to false', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: false, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + }); + + expect(stack).not.toHaveResource('AWS::IAM::Policy'); + + expect(stack).not.toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + AWS_CODEGURU_PROFILER_GROUP_ARN: { + 'Fn::Join': [ + '', + [ + 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, + ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, + ], + ], + }, + AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', + }, + }, + }); + }); + + test('default function with profiling enabled and client provided env vars', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profiling: true, + environment: { + AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', + AWS_CODEGURU_PROFILER_ENABLED: 'yes', + }, + })).toThrow(/AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); + }); + + test('default function with client provided Profiling Group and client provided env vars', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + environment: { + AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', + AWS_CODEGURU_PROFILER_ENABLED: 'yes', + }, + })).toThrow(/AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); + }); + }); + + describe('currentVersion', () => { + // see test.function-hash.ts for more coverage for this + test('logical id of version is based on the function hash', () => { + // GIVEN + const stack1 = new cdk.Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + environment: { + FOO: 'bar', + }, + }); + const stack2 = new cdk.Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + environment: { + FOO: 'bear', + }, + }); + + // WHEN + new cdk.CfnOutput(stack1, 'CurrentVersionArn', { + value: fn1.currentVersion.functionArn, + }); + new cdk.CfnOutput(stack2, 'CurrentVersionArn', { + value: fn2.currentVersion.functionArn, + }); + + // THEN + const template1 = SynthUtils.synthesize(stack1).template; + const template2 = SynthUtils.synthesize(stack2).template; + + // these functions are different in their configuration but the original + // logical ID of the version would be the same unless the logical ID + // includes the hash of function's configuration. + expect(template1.Outputs.CurrentVersionArn.Value).not.toEqual(template2.Outputs.CurrentVersionArn.Value); + }); + }); + + describe('filesystem', () => { + + test('mount efs filesystem', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + + const fs = new efs.FileSystem(stack, 'Efs', { + vpc, + }); + const accessPoint = fs.addAccessPoint('AccessPoint'); + // WHEN + new lambda.Function(stack, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), + }); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + FileSystemConfigs: [ + { + Arn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':elasticfilesystem:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':access-point/', + { + Ref: 'EfsAccessPointE419FED9', + }, + ], + ], + }, + LocalMountPath: '/mnt/msg', + }, + ], + }); + }); + }); +}); + +function newTestLambda(scope: constructs.Construct) { + return new lambda.Function(scope, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.PYTHON_2_7, + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts similarity index 71% rename from packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts rename to packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts index e06ce385522ea..20b040a135a7b 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts @@ -1,12 +1,9 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; import * as lambda from '../lib'; -/* eslint-disable quote-props */ - -export = { - 'can import a Lambda version by ARN'(test: Test) { +describe('lambda version', () => { + test('can import a Lambda version by ARN', () => { // GIVEN const stack = new cdk.Stack(); @@ -17,7 +14,7 @@ export = { new cdk.CfnOutput(stack, 'Name', { value: version.functionName }); // THEN - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ Outputs: { ARN: { Value: 'arn:aws:lambda:region:account-id:function:function-name:version', @@ -27,11 +24,9 @@ export = { }, }, }); + }); - test.done(); - }, - - 'create a version with event invoke config'(test: Test) { + test('create a version with event invoke config', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Fn', { @@ -48,7 +43,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { FunctionName: { Ref: 'Fn9270CBC0', }, @@ -60,12 +55,10 @@ export = { }, MaximumEventAgeInSeconds: 3600, MaximumRetryAttempts: 0, - })); - - test.done(); - }, + }); + }); - 'throws when calling configureAsyncInvoke on already configured version'(test: Test) { + test('throws when calling configureAsyncInvoke on already configured version', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Fn', { @@ -80,12 +73,10 @@ export = { }); // THEN - test.throws(() => version.configureAsyncInvoke({ retryAttempts: 1 }), /An EventInvokeConfig has already been configured/); + expect(() => version.configureAsyncInvoke({ retryAttempts: 1 })).toThrow(/An EventInvokeConfig has already been configured/); + }); - test.done(); - }, - - 'event invoke config on imported versions'(test: Test) { + test('event invoke config on imported versions', () => { // GIVEN const stack = new cdk.Stack(); const version1 = lambda.Version.fromVersionArn(stack, 'Version1', 'arn:aws:lambda:region:account-id:function:function-name:version1'); @@ -100,21 +91,19 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { FunctionName: 'function-name', Qualifier: 'version1', MaximumRetryAttempts: 1, - })); - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { + }); + expect(stack).toHaveResource('AWS::Lambda::EventInvokeConfig', { FunctionName: 'function-name', Qualifier: 'version2', MaximumRetryAttempts: 0, - })); - - test.done(); - }, + }); + }); - 'addAlias can be used to add an alias that points to a version'(test: Test) { + test('addAlias can be used to add an alias that points to a version', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Fn', { @@ -128,22 +117,21 @@ export = { version.addAlias('foo'); // THEN - expect(stack).to(haveResource('AWS::Lambda::Alias', { - 'FunctionName': { - 'Ref': 'Fn9270CBC0', + expect(stack).toHaveResource('AWS::Lambda::Alias', { + FunctionName: { + Ref: 'Fn9270CBC0', }, - 'FunctionVersion': { + FunctionVersion: { 'Fn::GetAtt': [ 'FnCurrentVersion17A89ABBab5c765f3c55e4e61583b51b00a95742', 'Version', ], }, - 'Name': 'foo', - })); - test.done(); - }, + Name: 'foo', + }); + }); - 'edgeArn'(test: Test) { + test('edgeArn', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Fn', { @@ -154,23 +142,19 @@ export = { const version = fn.currentVersion; // THEN - test.deepEqual(stack.resolve(version.edgeArn), { Ref: 'FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005' }); + expect(stack.resolve(version.edgeArn)).toEqual({ Ref: 'FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005' }); + }); - test.done(); - }, - - 'edgeArn throws with $LATEST'(test: Test) { + test('edgeArn throws with $LATEST', () => { // GIVEN const stack = new cdk.Stack(); const version = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:region:account-id:function:function-name:$LATEST'); // THEN - test.throws(() => version.edgeArn, /\$LATEST function version cannot be used for Lambda@Edge/); - - test.done(); - }, + expect(() => version.edgeArn).toThrow(/\$LATEST function version cannot be used for Lambda@Edge/); + }); - 'edgeArn throws at synthesis if underlying function is not edge compatible'(test: Test) { + test('edgeArn throws at synthesis if underlying function is not edge compatible', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); @@ -195,8 +179,6 @@ export = { fn.addEnvironment('KEY2', 'value2'); // THEN - test.throws(() => app.synth(), /KEY1,KEY2/); - - test.done(); - }, -}; + expect(() => app.synth()).toThrow(/KEY1,KEY2/); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/layers.test.ts similarity index 69% rename from packages/@aws-cdk/aws-lambda/test/test.layers.ts rename to packages/@aws-cdk/aws-lambda/test/layers.test.ts index 2afc60b26ae3d..0806f6d823dba 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/layers.test.ts @@ -1,13 +1,13 @@ +import '@aws-cdk/assert/jest'; import * as path from 'path'; -import { canonicalizeTemplate, expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; +import { canonicalizeTemplate, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { Test, testCase } from 'nodeunit'; import * as lambda from '../lib'; -export = testCase({ - 'creating a layer'(test: Test) { +describe('layers', () => { + test('creating a layer', () => { // GIVEN const stack = new cdk.Stack(undefined, 'TestStack'); const bucket = new s3.Bucket(stack, 'Bucket'); @@ -20,18 +20,16 @@ export = testCase({ }); // THEN - expect(stack).to(haveResource('AWS::Lambda::LayerVersion', { + expect(stack).toHaveResource('AWS::Lambda::LayerVersion', { Content: { S3Bucket: stack.resolve(bucket.bucketName), S3Key: 'ObjectKey', }, CompatibleRuntimes: ['nodejs10.x'], - })); - - test.done(); - }, + }); + }); - 'granting access to a layer'(test: Test) { + test('granting access to a layer', () => { // GIVEN const stack = new cdk.Stack(undefined, 'TestStack'); const bucket = new s3.Bucket(stack, 'Bucket'); @@ -46,35 +44,31 @@ export = testCase({ layer.addPermission('GrantUsage-o-123456', { accountId: '*', organizationId: 'o-123456' }); // THEN - expect(stack).to(haveResource('AWS::Lambda::LayerVersionPermission', { + expect(stack).toHaveResource('AWS::Lambda::LayerVersionPermission', { Action: 'lambda:GetLayerVersion', LayerVersionArn: stack.resolve(layer.layerVersionArn), Principal: '123456789012', - })); - expect(stack).to(haveResource('AWS::Lambda::LayerVersionPermission', { + }); + expect(stack).toHaveResource('AWS::Lambda::LayerVersionPermission', { Action: 'lambda:GetLayerVersion', LayerVersionArn: stack.resolve(layer.layerVersionArn), Principal: '*', OrganizationId: 'o-123456', - })); - - test.done(); - }, + }); + }); - 'creating a layer with no runtimes compatible'(test: Test) { + test('creating a layer with no runtimes compatible', () => { // GIVEN const stack = new cdk.Stack(undefined, 'TestStack'); const bucket = new s3.Bucket(stack, 'Bucket'); const code = new lambda.S3Code(bucket, 'ObjectKey'); // THEN - test.throws(() => new lambda.LayerVersion(stack, 'LayerVersion', { code, compatibleRuntimes: [] }), - /supports no runtime/); - - test.done(); - }, + expect(() => new lambda.LayerVersion(stack, 'LayerVersion', { code, compatibleRuntimes: [] })) + .toThrow(/supports no runtime/); + }); - 'asset metadata is added to the cloudformation resource'(test: Test) { + test('asset metadata is added to the cloudformation resource', () => { // GIVEN const stack = new cdk.Stack(); stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); @@ -85,12 +79,11 @@ export = testCase({ }); // THEN - expect(canonicalizeTemplate(SynthUtils.toCloudFormation(stack))).to(haveResource('AWS::Lambda::LayerVersion', { + expect(canonicalizeTemplate(SynthUtils.toCloudFormation(stack))).toHaveResource('AWS::Lambda::LayerVersion', { Metadata: { 'aws:asset:path': 'asset.Asset1Hash', 'aws:asset:property': 'Content', }, - }, ResourcePart.CompleteDefinition)); - test.done(); - }, + }, ResourcePart.CompleteDefinition); + }); }); diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.test.ts b/packages/@aws-cdk/aws-lambda/test/runtime.test.ts new file mode 100644 index 0000000000000..e90d7535d8045 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/runtime.test.ts @@ -0,0 +1,64 @@ +import '@aws-cdk/assert/jest'; +import * as lambda from '../lib'; + +describe('runtime', () => { + test('runtimes are equal for different instances', () => { + // GIVEN + const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + + // WHEN + expect(runtime1.runtimeEquals(runtime2)).toBe(true); + }); + + test('runtimes are equal for same instance', () => { + const runtime = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + + expect(runtime.runtimeEquals(runtime)).toBe(true); + }); + + test('unequal when name changes', () => { + const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + const runtime2 = new lambda.Runtime('python3.6', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + + expect(runtime1.runtimeEquals(runtime2)).toBe(false); + }); + + test('unequal when family changes', () => { + const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.JAVA, { supportsInlineCode: true }); + + expect(runtime1.runtimeEquals(runtime2)).toBe(false); + }); + + test('unequal when supportsInlineCode changes', () => { + const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); + const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: false }); + + expect(runtime1.runtimeEquals(runtime2)).toBe(false); + }); + + test('bundlingDockerImage points to AWS SAM build image', () => { + // GIVEN + const runtime = new lambda.Runtime('my-runtime-name'); + + // THEN + expect(runtime.bundlingDockerImage.image).toEqual('amazon/aws-sam-cli-build-image-my-runtime-name'); + }); + + test('overridde to bundlingDockerImage points to the correct image', () => { + // GIVEN + const runtime = new lambda.Runtime('my-runtime-name', undefined, { + bundlingDockerImage: 'my-docker-image', + }); + + // THEN + expect(runtime.bundlingDockerImage.image).toEqual('my-docker-image'); + }); + + test('dotnetcore and go have overridden images', () => { + expect(lambda.Runtime.DOTNET_CORE_3_1.bundlingDockerImage.image).toEqual('lambci/lambda:build-dotnetcore3.1'); + expect(lambda.Runtime.DOTNET_CORE_2_1.bundlingDockerImage.image).toEqual('lambci/lambda:build-dotnetcore2.1'); + expect(lambda.Runtime.GO_1_X.bundlingDockerImage.image).toEqual('lambci/lambda:build-go1.x'); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts similarity index 76% rename from packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts rename to packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts index 0183e78badd27..28eebb1a0c284 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts @@ -1,11 +1,11 @@ -import { expect, haveResource, matchTemplate, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { ResourcePart } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; import * as lambda from '../lib'; -export = { - 'can add same singleton Lambda multiple times, only instantiated once in template'(test: Test) { +describe('singleton lambda', () => { + test('can add same singleton Lambda multiple times, only instantiated once in template', () => { // GIVEN const stack = new cdk.Stack(); @@ -21,7 +21,7 @@ export = { } // THEN - expect(stack).to(matchTemplate({ + expect(stack).toMatchTemplate({ Resources: { SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235: { Type: 'AWS::IAM::Role', @@ -57,12 +57,10 @@ export = { DependsOn: ['SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235'], }, }, - })); - - test.done(); - }, + }); + }); - 'dependencies are correctly added'(test: Test) { + test('dependencies are correctly added', () => { // GIVEN const stack = new cdk.Stack(); const singleton = new lambda.SingletonFunction(stack, 'Singleton', { @@ -78,17 +76,15 @@ export = { singleton.addDependency(dependency); // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { DependsOn: [ 'dependencyUser1B9CB07E', 'SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235', ], - }, ResourcePart.CompleteDefinition)); + }, ResourcePart.CompleteDefinition); + }); - test.done(); - }, - - 'dependsOn are correctly added'(test: Test) { + test('dependsOn are correctly added', () => { // GIVEN const stack = new cdk.Stack(); const singleton = new lambda.SingletonFunction(stack, 'Singleton', { @@ -104,17 +100,15 @@ export = { singleton.dependOn(user); // THEN - expect(stack).to(haveResource('AWS::IAM::User', { + expect(stack).toHaveResource('AWS::IAM::User', { DependsOn: [ 'SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38', 'SingletonLambda84c0de93353f42179b0b45b6c993251aServiceRole26D59235', ], - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, + }, ResourcePart.CompleteDefinition); + }); - 'grantInvoke works correctly'(test: Test) { + test('grantInvoke works correctly', () => { // GIVEN const stack = new cdk.Stack(); const singleton = new lambda.SingletonFunction(stack, 'Singleton', { @@ -129,20 +123,19 @@ export = { const statement = stack.resolve(invokeResult.resourceStatement); // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { + expect(stack).toHaveResource('AWS::Lambda::Permission', { Action: 'lambda:InvokeFunction', Principal: 'events.amazonaws.com', - })); - test.deepEqual(statement.action, ['lambda:InvokeFunction']); - test.deepEqual(statement.principal, { Service: ['events.amazonaws.com'] }); - test.deepEqual(statement.effect, 'Allow'); - test.deepEqual(statement.resource, [{ + }); + expect(statement.action).toEqual(['lambda:InvokeFunction']); + expect(statement.principal).toEqual({ Service: ['events.amazonaws.com'] }); + expect(statement.effect).toEqual('Allow'); + expect(statement.resource).toEqual([{ 'Fn::GetAtt': ['SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38', 'Arn'], }]); - test.done(); - }, + }); - 'check edge compatibility'(test: Test) { + test('check edge compatibility', () => { // GIVEN const stack = new cdk.Stack(); const singleton = new lambda.SingletonFunction(stack, 'Singleton', { @@ -156,12 +149,11 @@ export = { }); // THEN - test.throws(() => singleton._checkEdgeCompatibility(), /The function Default\/SingletonLambda84c0de93353f42179b0b45b6c993251a contains environment variables \[KEY\] and is not compatible with Lambda@Edge/); - - test.done(); - }, + expect(() => singleton._checkEdgeCompatibility()) + .toThrow(/contains environment variables .* and is not compatible with Lambda@Edge/); + }); - 'current version of a singleton function'(test: Test) { + test('current version of a singleton function', () => { // GIVEN const stack = new cdk.Stack(); const singleton = new lambda.SingletonFunction(stack, 'Singleton', { @@ -176,12 +168,10 @@ export = { version.addAlias('foo'); // THEN - expect(stack).to(haveResource('AWS::Lambda::Version', { + expect(stack).toHaveResource('AWS::Lambda::Version', { FunctionName: { Ref: 'SingletonLambda84c0de93353f42179b0b45b6c993251a840BCC38', }, - })); - - test.done(); - }, -}; + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.function.ts b/packages/@aws-cdk/aws-lambda/test/test.function.ts deleted file mode 100644 index 94c878161221b..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/test.function.ts +++ /dev/null @@ -1,486 +0,0 @@ -import * as path from 'path'; -import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; -import { ProfilingGroup } from '@aws-cdk/aws-codeguruprofiler'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as efs from '@aws-cdk/aws-efs'; -import * as logs from '@aws-cdk/aws-logs'; -import * as s3 from '@aws-cdk/aws-s3'; -import * as sqs from '@aws-cdk/aws-sqs'; -import * as cdk from '@aws-cdk/core'; -import * as _ from 'lodash'; -import { Test, testCase } from 'nodeunit'; -import * as lambda from '../lib'; - -/* eslint-disable quote-props */ - -export = testCase({ - 'add incompatible layer'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack'); - const bucket = new s3.Bucket(stack, 'Bucket'); - const code = new lambda.S3Code(bucket, 'ObjectKey'); - - const func = new lambda.Function(stack, 'myFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code, - }); - const layer = new lambda.LayerVersion(stack, 'myLayer', { - code, - compatibleRuntimes: [lambda.Runtime.NODEJS], - }); - - // THEN - test.throws(() => func.addLayers(layer), - /This lambda function uses a runtime that is incompatible with this layer/); - - test.done(); - }, - 'add compatible layer'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack'); - const bucket = new s3.Bucket(stack, 'Bucket'); - const code = new lambda.S3Code(bucket, 'ObjectKey'); - - const func = new lambda.Function(stack, 'myFunc', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code, - }); - const layer = new lambda.LayerVersion(stack, 'myLayer', { - code, - compatibleRuntimes: [lambda.Runtime.PYTHON_3_7], - }); - - // THEN - // should not throw - func.addLayers(layer); - - test.done(); - }, - 'add compatible layer for deep clone'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack'); - const bucket = new s3.Bucket(stack, 'Bucket'); - const code = new lambda.S3Code(bucket, 'ObjectKey'); - - const runtime = lambda.Runtime.PYTHON_3_7; - const func = new lambda.Function(stack, 'myFunc', { - runtime, - handler: 'index.handler', - code, - }); - const clone = _.cloneDeep(runtime); - const layer = new lambda.LayerVersion(stack, 'myLayer', { - code, - compatibleRuntimes: [clone], - }); - - // THEN - // should not throw - func.addLayers(layer); - - test.done(); - }, - - 'empty inline code is not allowed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN/THEN - test.throws(() => new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline(''), - }), /Lambda inline code cannot be empty/); - test.done(); - }, - - 'logGroup is correctly returned'(test: Test) { - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('foo'), - }); - const logGroup = fn.logGroup; - test.ok(logGroup.logGroupName); - test.ok(logGroup.logGroupArn); - test.done(); - }, - - 'dlq is returned when provided by user'(test: Test) { - const stack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriod: cdk.Duration.days(14), - }); - - const fn = new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('foo'), - deadLetterQueue: dlQueue, - }); - const deadLetterQueue = fn.deadLetterQueue; - test.ok(deadLetterQueue?.queueArn); - test.ok(deadLetterQueue?.queueName); - test.ok(deadLetterQueue?.queueUrl); - test.done(); - }, - - 'dlq is returned when setup by cdk'(test: Test) { - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('foo'), - deadLetterQueueEnabled: true, - }); - const deadLetterQueue = fn.deadLetterQueue; - test.ok(deadLetterQueue?.queueArn); - test.ok(deadLetterQueue?.queueName); - test.ok(deadLetterQueue?.queueUrl); - test.done(); - }, - - 'dlq is undefined when not setup'(test: Test) { - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('foo'), - }); - const deadLetterQueue = fn.deadLetterQueue; - test.ok(deadLetterQueue === undefined); - test.done(); - }, - - 'one and only one child LogRetention construct will be created'(test: Test) { - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('foo'), - logRetention: logs.RetentionDays.FIVE_DAYS, - }); - - // Call logGroup a few times. If more than one instance of LogRetention was created, - // the second call will fail on duplicate constructs. - fn.logGroup; - fn.logGroup; - fn.logGroup; - - test.done(); - }, - - 'fails when inline code is specified on an incompatible runtime'(test: Test) { - const stack = new cdk.Stack(); - test.throws(() => new lambda.Function(stack, 'fn', { - handler: 'foo', - runtime: lambda.Runtime.PROVIDED, - code: lambda.Code.fromInline('foo'), - }), /Inline source not allowed for/); - test.done(); - }, - - 'default function with CDK created Profiling Group'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - profiling: true, - }); - - expect(stack).to(haveResource('AWS::CodeGuruProfiler::ProfilingGroup', { - ProfilingGroupName: 'MyLambdaProfilingGroupC5B6CCD8', - ComputePlatform: 'AWSLambda', - })); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: [ - 'codeguru-profiler:ConfigureAgent', - 'codeguru-profiler:PostAgentProfile', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'], - }, - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - Roles: [ - { - Ref: 'MyLambdaServiceRole4539ECB6', - }, - ], - })); - - expect(stack).to(haveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - AWS_CODEGURU_PROFILER_GROUP_ARN: { 'Fn::GetAtt': ['MyLambdaProfilingGroupEC6DE32F', 'Arn'] }, - AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', - }, - }, - })); - - test.done(); - }, - - 'default function with client provided Profiling Group'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), - }); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: [ - 'codeguru-profiler:ConfigureAgent', - 'codeguru-profiler:PostAgentProfile', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['ProfilingGroup26979FD7', 'Arn'], - }, - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - Roles: [ - { - Ref: 'MyLambdaServiceRole4539ECB6', - }, - ], - })); - - expect(stack).to(haveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - AWS_CODEGURU_PROFILER_GROUP_ARN: { - 'Fn::Join': [ - '', - [ - 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, - ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, - ], - ], - }, - AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', - }, - }, - })); - - test.done(); - }, - - 'default function with client provided Profiling Group but profiling set to false'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - profiling: false, - profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), - }); - - expect(stack).notTo(haveResource('AWS::IAM::Policy')); - - expect(stack).notTo(haveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - AWS_CODEGURU_PROFILER_GROUP_ARN: { - 'Fn::Join': [ - '', - [ - 'arn:', { Ref: 'AWS::Partition' }, ':codeguru-profiler:', { Ref: 'AWS::Region' }, - ':', { Ref: 'AWS::AccountId' }, ':profilingGroup/', { Ref: 'ProfilingGroup26979FD7' }, - ], - ], - }, - AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', - }, - }, - })); - - test.done(); - }, - - 'default function with profiling enabled and client provided env vars'(test: Test) { - const stack = new cdk.Stack(); - - test.throws(() => new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - profiling: true, - environment: { - AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', - AWS_CODEGURU_PROFILER_ENABLED: 'yes', - }, - }), - /AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); - - test.done(); - }, - - 'default function with client provided Profiling Group and client provided env vars'(test: Test) { - const stack = new cdk.Stack(); - - test.throws(() => new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), - environment: { - AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', - AWS_CODEGURU_PROFILER_ENABLED: 'yes', - }, - }), - /AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); - - test.done(); - }, - - 'multiple calls to latestVersion returns the same version'(test: Test) { - const stack = new cdk.Stack(); - - const fn = new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('hello()'), - handler: 'index.hello', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - const version1 = fn.latestVersion; - const version2 = fn.latestVersion; - - const expectedArn = { - 'Fn::Join': ['', [ - { 'Fn::GetAtt': ['MyLambdaCCE802FB', 'Arn'] }, - ':$LATEST', - ]], - }; - test.equal(version1, version2); - test.deepEqual(stack.resolve(version1.functionArn), expectedArn); - test.deepEqual(stack.resolve(version2.functionArn), expectedArn); - - test.done(); - }, - - 'currentVersion': { - // see test.function-hash.ts for more coverage for this - 'logical id of version is based on the function hash'(test: Test) { - // GIVEN - const stack1 = new cdk.Stack(); - const fn1 = new lambda.Function(stack1, 'MyFunction', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_12_X, - code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), - environment: { - FOO: 'bar', - }, - }); - const stack2 = new cdk.Stack(); - const fn2 = new lambda.Function(stack2, 'MyFunction', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_12_X, - code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), - environment: { - FOO: 'bear', - }, - }); - - // WHEN - new cdk.CfnOutput(stack1, 'CurrentVersionArn', { - value: fn1.currentVersion.functionArn, - }); - new cdk.CfnOutput(stack2, 'CurrentVersionArn', { - value: fn2.currentVersion.functionArn, - }); - - // THEN - const template1 = SynthUtils.synthesize(stack1).template; - const template2 = SynthUtils.synthesize(stack2).template; - - // these functions are different in their configuration but the original - // logical ID of the version would be the same unless the logical ID - // includes the hash of function's configuration. - test.notDeepEqual(template1.Outputs.CurrentVersionArn.Value, template2.Outputs.CurrentVersionArn.Value); - test.done(); - }, - }, - - 'filesystem': { - - 'mount efs filesystem'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { - maxAzs: 3, - natGateways: 1, - }); - - const fs = new efs.FileSystem(stack, 'Efs', { - vpc, - }); - const accessPoint = fs.addAccessPoint('AccessPoint'); - // WHEN - new lambda.Function(stack, 'MyFunction', { - handler: 'foo', - runtime: lambda.Runtime.NODEJS_12_X, - code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), - filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { - FileSystemConfigs: [ - { - Arn: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':elasticfilesystem:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':access-point/', - { - Ref: 'EfsAccessPointE419FED9', - }, - ], - ], - }, - LocalMountPath: '/mnt/msg', - }, - ], - })); - test.done(); - }, - }, -}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts deleted file mode 100644 index 3678ba37069e0..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ /dev/null @@ -1,1722 +0,0 @@ -import * as path from 'path'; -import { ABSENT, expect, haveResource, MatchStyle, ResourcePart, arrayWith, objectLike } from '@aws-cdk/assert'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as iam from '@aws-cdk/aws-iam'; -import * as logs from '@aws-cdk/aws-logs'; -import * as sqs from '@aws-cdk/aws-sqs'; -import * as cdk from '@aws-cdk/core'; -import * as constructs from 'constructs'; -import { Test } from 'nodeunit'; -import * as lambda from '../lib'; - -/* eslint-disable quote-props */ - -export = { - 'default function'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - expect(stack).toMatch({ - Resources: - { - MyLambdaServiceRole4539ECB6: - { - Type: 'AWS::IAM::Role', - Properties: - { - AssumeRolePolicyDocument: - { - Statement: - [{ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { Service: 'lambda.amazonaws.com' }, - }], - Version: '2012-10-17', - }, - ManagedPolicyArns: - // arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - // eslint-disable-next-line max-len - [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], - }, - }, - MyLambdaCCE802FB: - { - Type: 'AWS::Lambda::Function', - Properties: - { - Code: { ZipFile: 'foo' }, - Handler: 'index.handler', - Role: { 'Fn::GetAtt': ['MyLambdaServiceRole4539ECB6', 'Arn'] }, - Runtime: 'nodejs10.x', - }, - DependsOn: ['MyLambdaServiceRole4539ECB6'], - }, - }, - }); - test.done(); - }, - - 'adds policy permissions'(test: Test) { - const stack = new cdk.Stack(); - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - initialPolicy: [new iam.PolicyStatement({ actions: ['*'], resources: ['*'] })], - }); - expect(stack).toMatch({ - Resources: - { - MyLambdaServiceRole4539ECB6: - { - Type: 'AWS::IAM::Role', - Properties: - { - AssumeRolePolicyDocument: - { - Statement: - [{ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { Service: 'lambda.amazonaws.com' }, - }], - Version: '2012-10-17', - }, - ManagedPolicyArns: - // eslint-disable-next-line max-len - [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], - }, - }, - MyLambdaServiceRoleDefaultPolicy5BBC6F68: { - Type: 'AWS::IAM::Policy', - Properties: { - PolicyDocument: { - Statement: [ - { - Action: '*', - Effect: 'Allow', - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - Roles: [ - { - Ref: 'MyLambdaServiceRole4539ECB6', - }, - ], - }, - }, - MyLambdaCCE802FB: - { - Type: 'AWS::Lambda::Function', - Properties: - { - Code: { ZipFile: 'foo' }, - Handler: 'index.handler', - Role: { 'Fn::GetAtt': ['MyLambdaServiceRole4539ECB6', 'Arn'] }, - Runtime: 'nodejs10.x', - }, - DependsOn: ['MyLambdaServiceRoleDefaultPolicy5BBC6F68', 'MyLambdaServiceRole4539ECB6'], - }, - }, - }); - test.done(); - - }, - - 'fails if inline code is used for an invalid runtime'(test: Test) { - const stack = new cdk.Stack(); - test.throws(() => new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'bar', - runtime: lambda.Runtime.DOTNET_CORE_2, - })); - test.done(); - }, - - 'addToResourcePolicy': { - 'can be used to add permissions to the Lambda function'(test: Test) { - const stack = new cdk.Stack(); - const fn = newTestLambda(stack); - - fn.addPermission('S3Permission', { - action: 'lambda:*', - principal: new iam.ServicePrincipal('s3.amazonaws.com'), - sourceAccount: stack.account, - sourceArn: 'arn:aws:s3:::my_bucket', - }); - - expect(stack).toMatch({ - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': - // eslint-disable-next-line max-len - [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'bar', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'python2.7', - }, - 'DependsOn': [ - 'MyLambdaServiceRole4539ECB6', - ], - }, - 'MyLambdaS3Permission99D0EA08': { - 'Type': 'AWS::Lambda::Permission', - 'Properties': { - 'Action': 'lambda:*', - 'FunctionName': { - 'Fn::GetAtt': [ - 'MyLambdaCCE802FB', - 'Arn', - ], - }, - 'Principal': 's3.amazonaws.com', - 'SourceAccount': { - 'Ref': 'AWS::AccountId', - }, - 'SourceArn': 'arn:aws:s3:::my_bucket', - }, - }, - }, - }); - - test.done(); - }, - - 'fails if the principal is not a service, account or arn principal'(test: Test) { - const stack = new cdk.Stack(); - const fn = newTestLambda(stack); - - test.throws(() => fn.addPermission('F1', { principal: new iam.OrganizationPrincipal('org') }), - /Invalid principal type for Lambda permission statement/); - - fn.addPermission('S1', { principal: new iam.ServicePrincipal('my-service') }); - fn.addPermission('S2', { principal: new iam.AccountPrincipal('account') }); - fn.addPermission('S3', { principal: new iam.ArnPrincipal('my:arn') }); - - test.done(); - }, - - 'BYORole'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - role.addToPolicy(new iam.PolicyStatement({ actions: ['confirm:itsthesame'], resources: ['*'] })); - - // WHEN - const fn = new lambda.Function(stack, 'Function', { - code: new lambda.InlineCode('test'), - runtime: lambda.Runtime.PYTHON_3_6, - handler: 'index.test', - role, - initialPolicy: [ - new iam.PolicyStatement({ actions: ['inline:inline'], resources: ['*'] }), - ], - }); - - fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['explicit:explicit'], resources: ['*'] })); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Version': '2012-10-17', - 'Statement': [ - { 'Action': 'confirm:itsthesame', 'Effect': 'Allow', 'Resource': '*' }, - { 'Action': 'inline:inline', 'Effect': 'Allow', 'Resource': '*' }, - { 'Action': 'explicit:explicit', 'Effect': 'Allow', 'Resource': '*' }, - ], - }, - })); - - test.done(); - }, - }, - - 'fromFunctionArn'(test: Test) { - // GIVEN - const stack2 = new cdk.Stack(); - - // WHEN - const imported = lambda.Function.fromFunctionArn(stack2, 'Imported', 'arn:aws:lambda:us-east-1:123456789012:function:ProcessKinesisRecords'); - - // Can call addPermission() but it won't do anything - imported.addPermission('Hello', { - principal: new iam.ServicePrincipal('harry'), - }); - - // THEN - test.deepEqual(imported.functionArn, 'arn:aws:lambda:us-east-1:123456789012:function:ProcessKinesisRecords'); - test.deepEqual(imported.functionName, 'ProcessKinesisRecords'); - expect(stack2).notTo(haveResource('AWS::Lambda::Permission')); - test.done(); - }, - - 'imported Function w/ resolved account and function arn can addPermissions'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Imports', { - env: { account: '123456789012', region: 'us-east-1' }, - }); - const stack2 = new cdk.Stack(app, 'imported', { - env: { account: '123456789012', region: 'us-east-1' }, - }); - new lambda.Function(stack, 'BaseFunction', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - const iFunc = lambda.Function.fromFunctionAttributes(stack2, 'iFunc', { - functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction', - }); - iFunc.addPermission('iFunc', { - principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), - }); - - // THEN - expect(stack2).to(haveResource('AWS::Lambda::Permission')); - test.done(); - }, - - 'imported Function w/o account cannot addPermissions'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Base'); - const importedStack = new cdk.Stack(app, 'Imported'); - new lambda.Function(stack, 'BaseFunction', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - const iFunc = lambda.Function.fromFunctionAttributes(importedStack, 'iFunc', { - functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction', - }); - iFunc.addPermission('iFunc', { - principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), - }); - - // THEN - expect(importedStack).notTo(haveResource('AWS::Lambda::Permission')); - test.done(); - }, - - 'Lambda code can be read from a local directory via an asset'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - new lambda.Function(stack, 'MyLambda', { - code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), - handler: 'index.handler', - runtime: lambda.Runtime.PYTHON_3_6, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { - 'Code': { - 'S3Bucket': { - 'Ref': 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3Bucket1354C645', - }, - 'S3Key': { - 'Fn::Join': ['', [ - { 'Fn::Select': [0, { 'Fn::Split': ['||', { 'Ref': 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3VersionKey5D873FAC' }] }] }, - { 'Fn::Select': [1, { 'Fn::Split': ['||', { 'Ref': 'AssetParameters9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232S3VersionKey5D873FAC' }] }] }, - ]], - }, - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'python3.6', - })); - - test.done(); - }, - - 'default function with SQS DLQ when client sets deadLetterQueueEnabled to true and functionName defined by client'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - functionName: 'OneFunctionToRuleThemAll', - deadLetterQueueEnabled: true, - }); - - expect(stack).toMatch( - { - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - 'Ref': 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - ], - }, - }, - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': 'sqs:SendMessage', - 'Effect': 'Allow', - 'Resource': { - 'Fn::GetAtt': [ - 'MyLambdaDeadLetterQueue399EEA2D', - 'Arn', - ], - }, - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - }, - }, - 'MyLambdaDeadLetterQueue399EEA2D': { - 'Type': 'AWS::SQS::Queue', - 'Properties': { - 'MessageRetentionPeriod': 1209600, - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'DeadLetterConfig': { - 'TargetArn': { - 'Fn::GetAtt': [ - 'MyLambdaDeadLetterQueue399EEA2D', - 'Arn', - ], - }, - }, - 'FunctionName': 'OneFunctionToRuleThemAll', - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, - }, - }, - ); - test.done(); - }, - - 'default function with SQS DLQ when client sets deadLetterQueueEnabled to true and functionName not defined by client'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - deadLetterQueueEnabled: true, - }); - - expect(stack).toMatch( - { - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - 'Ref': 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - ], - }, - }, - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': 'sqs:SendMessage', - 'Effect': 'Allow', - 'Resource': { - 'Fn::GetAtt': [ - 'MyLambdaDeadLetterQueue399EEA2D', - 'Arn', - ], - }, - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - }, - }, - 'MyLambdaDeadLetterQueue399EEA2D': { - 'Type': 'AWS::SQS::Queue', - 'Properties': { - 'MessageRetentionPeriod': 1209600, - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'DeadLetterConfig': { - 'TargetArn': { - 'Fn::GetAtt': [ - 'MyLambdaDeadLetterQueue399EEA2D', - 'Arn', - ], - }, - }, - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, - }, - }, - ); - test.done(); - }, - - 'default function with SQS DLQ when client sets deadLetterQueueEnabled to false'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - deadLetterQueueEnabled: false, - }); - - expect(stack).toMatch( - { - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - 'Ref': 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - ], - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - }, - 'DependsOn': [ - 'MyLambdaServiceRole4539ECB6', - ], - }, - }, - }, - ); - test.done(); - }, - - 'default function with SQS DLQ when client provides Queue to be used as DLQ'(test: Test) { - const stack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriod: cdk.Duration.days(14), - }); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - deadLetterQueue: dlQueue, - }); - - expect(stack).toMatch( - { - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - 'Ref': 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - ], - }, - }, - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': 'sqs:SendMessage', - 'Effect': 'Allow', - 'Resource': { - 'Fn::GetAtt': [ - 'DeadLetterQueue9F481546', - 'Arn', - ], - }, - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'DeadLetterConfig': { - 'TargetArn': { - 'Fn::GetAtt': [ - 'DeadLetterQueue9F481546', - 'Arn', - ], - }, - }, - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, - }, - } - , MatchStyle.SUPERSET); - test.done(); - }, - - 'default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to true'(test: Test) { - const stack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriod: cdk.Duration.days(14), - }); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - deadLetterQueueEnabled: true, - deadLetterQueue: dlQueue, - }); - - expect(stack).toMatch( - { - 'Resources': { - 'MyLambdaServiceRole4539ECB6': { - 'Type': 'AWS::IAM::Role', - 'Properties': { - 'AssumeRolePolicyDocument': { - 'Statement': [ - { - 'Action': 'sts:AssumeRole', - 'Effect': 'Allow', - 'Principal': { - 'Service': 'lambda.amazonaws.com', - }, - }, - ], - 'Version': '2012-10-17', - }, - 'ManagedPolicyArns': [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - 'Ref': 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - ], - }, - }, - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': 'sqs:SendMessage', - 'Effect': 'Allow', - 'Resource': { - 'Fn::GetAtt': [ - 'DeadLetterQueue9F481546', - 'Arn', - ], - }, - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - }, - }, - 'MyLambdaCCE802FB': { - 'Type': 'AWS::Lambda::Function', - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'DeadLetterConfig': { - 'TargetArn': { - 'Fn::GetAtt': [ - 'DeadLetterQueue9F481546', - 'Arn', - ], - }, - }, - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, - }, - } - , MatchStyle.SUPERSET); - test.done(); - }, - - 'error when default function with SQS DLQ when client provides Queue to be used as DLQ and deadLetterQueueEnabled set to false'(test: Test) { - const stack = new cdk.Stack(); - - const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriod: cdk.Duration.days(14), - }); - - test.throws(() => new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - deadLetterQueueEnabled: false, - deadLetterQueue: dlQueue, - }), /deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false/); - - test.done(); - }, - - 'default function with Active tracing'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - tracing: lambda.Tracing.ACTIVE, - }); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': [ - 'xray:PutTraceSegments', - 'xray:PutTelemetryRecords', - ], - 'Effect': 'Allow', - 'Resource': '*', - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - })); - - expect(stack).to(haveResource('AWS::Lambda::Function', { - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'TracingConfig': { - 'Mode': 'Active', - }, - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, - - 'default function with PassThrough tracing'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - tracing: lambda.Tracing.PASS_THROUGH, - }); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': [ - 'xray:PutTraceSegments', - 'xray:PutTelemetryRecords', - ], - 'Effect': 'Allow', - 'Resource': '*', - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - })); - - expect(stack).to(haveResource('AWS::Lambda::Function', { - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - 'TracingConfig': { - 'Mode': 'PassThrough', - }, - }, - 'DependsOn': [ - 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'MyLambdaServiceRole4539ECB6', - ], - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, - - 'default function with Disabled tracing'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - tracing: lambda.Tracing.DISABLED, - }); - - expect(stack).notTo(haveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': [ - 'xray:PutTraceSegments', - 'xray:PutTelemetryRecords', - ], - 'Effect': 'Allow', - 'Resource': '*', - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'MyLambdaServiceRoleDefaultPolicy5BBC6F68', - 'Roles': [ - { - 'Ref': 'MyLambdaServiceRole4539ECB6', - }, - ], - })); - - expect(stack).to(haveResource('AWS::Lambda::Function', { - 'Properties': { - 'Code': { - 'ZipFile': 'foo', - }, - 'Handler': 'index.handler', - 'Role': { - 'Fn::GetAtt': [ - 'MyLambdaServiceRole4539ECB6', - 'Arn', - ], - }, - 'Runtime': 'nodejs10.x', - }, - 'DependsOn': [ - 'MyLambdaServiceRole4539ECB6', - ], - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, - - 'grantInvoke': { - - 'adds iam:InvokeFunction'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'Role', { - assumedBy: new iam.AccountPrincipal('1234'), - }); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - fn.grantInvoke(role); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Action: 'lambda:InvokeFunction', - Effect: 'Allow', - Resource: { 'Fn::GetAtt': ['Function76856677', 'Arn'] }, - }, - ], - }, - })); - - test.done(); - }, - - 'with a service principal'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - const service = new iam.ServicePrincipal('apigateway.amazonaws.com'); - - // WHEN - fn.grantInvoke(service); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'Function76856677', - 'Arn', - ], - }, - Principal: 'apigateway.amazonaws.com', - })); - - test.done(); - }, - - 'with an account principal'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - const account = new iam.AccountPrincipal('123456789012'); - - // WHEN - fn.grantInvoke(account); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'Function76856677', - 'Arn', - ], - }, - Principal: '123456789012', - })); - - test.done(); - }, - - 'with an arn principal'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - const account = new iam.ArnPrincipal('arn:aws:iam::123456789012:role/someRole'); - - // WHEN - fn.grantInvoke(account); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'Function76856677', - 'Arn', - ], - }, - Principal: 'arn:aws:iam::123456789012:role/someRole', - })); - - test.done(); - }, - - 'can be called twice for the same service principal'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - const service = new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com'); - - // WHEN - fn.grantInvoke(service); - fn.grantInvoke(service); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'Function76856677', - 'Arn', - ], - }, - Principal: 'elasticloadbalancing.amazonaws.com', - })); - - test.done(); - }, - }, - - 'grantInvoke with an imported role (in the same account)'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, undefined, { - env: { account: '123456789012' }, - }); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: objectLike({ - Statement: arrayWith( - { - Action: 'lambda:InvokeFunction', - Effect: 'Allow', - Resource: { 'Fn::GetAtt': ['Function76856677', 'Arn'] }, - }, - ), - }), - Roles: ['someRole'], - })); - - test.done(); - }, - - 'grantInvoke with an imported role (from a different account)'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, undefined, { - env: { account: '3333' }, - }); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'Function76856677', - 'Arn', - ], - }, - Principal: 'arn:aws:iam::123456789012:role/someRole', - })); - - test.done(); - }, - - 'Can use metricErrors on a lambda Function'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // THEN - test.deepEqual(stack.resolve(fn.metricErrors()), { - dimensions: { FunctionName: { Ref: 'Function76856677' } }, - namespace: 'AWS/Lambda', - metricName: 'Errors', - period: cdk.Duration.minutes(5), - statistic: 'Sum', - }); - - test.done(); - }, - - 'addEventSource calls bind'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'Function', { - code: lambda.Code.fromInline('xxx'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - let bindTarget; - - class EventSourceMock implements lambda.IEventSource { - public bind(target: lambda.IFunction) { - bindTarget = target; - } - } - - // WHEN - fn.addEventSource(new EventSourceMock()); - - // THEN - test.same(bindTarget, fn); - test.done(); - }, - - 'using an incompatible layer'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack'); - const layer = lambda.LayerVersion.fromLayerVersionAttributes(stack, 'TestLayer', { - layerVersionArn: 'arn:aws:...', - compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], - }); - - // THEN - test.throws(() => new lambda.Function(stack, 'Function', { - layers: [layer], - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), - handler: 'index.main', - }), - /nodejs10.x is not in \[nodejs12.x\]/); - - test.done(); - }, - - 'using more than 5 layers'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack'); - const layers = new Array(6).fill(lambda.LayerVersion.fromLayerVersionAttributes(stack, 'TestLayer', { - layerVersionArn: 'arn:aws:...', - compatibleRuntimes: [lambda.Runtime.NODEJS_10_X], - })); - - // THEN - test.throws(() => new lambda.Function(stack, 'Function', { - layers, - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), - handler: 'index.main', - }), - /Unable to add layer:/); - - test.done(); - }, - - 'environment variables work in China'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, undefined, { env: { region: 'cn-north-1' } }); - - // WHEN - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS, - environment: { - SOME: 'Variable', - }, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - SOME: 'Variable', - }, - }, - })); - - test.done(); - }, - - 'environment variables work in an unspecified region'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS, - environment: { - SOME: 'Variable', - }, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { - Environment: { - Variables: { - SOME: 'Variable', - }, - }, - })); - - test.done(); - - }, - - 'support reserved concurrent executions'(test: Test) { - const stack = new cdk.Stack(); - - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS, - reservedConcurrentExecutions: 10, - }); - - expect(stack).toMatch({ - Resources: - { - MyLambdaServiceRole4539ECB6: - { - Type: 'AWS::IAM::Role', - Properties: - { - AssumeRolePolicyDocument: - { - Statement: - [{ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { Service: 'lambda.amazonaws.com' }, - }], - Version: '2012-10-17', - }, - ManagedPolicyArns: - // arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - // eslint-disable-next-line max-len - [{ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole']] }], - }, - }, - MyLambdaCCE802FB: - { - Type: 'AWS::Lambda::Function', - Properties: - { - Code: { ZipFile: 'foo' }, - Handler: 'index.handler', - ReservedConcurrentExecutions: 10, - Role: { 'Fn::GetAtt': ['MyLambdaServiceRole4539ECB6', 'Arn'] }, - Runtime: 'nodejs', - }, - DependsOn: ['MyLambdaServiceRole4539ECB6'], - }, - }, - }); - test.done(); - }, - - 'its possible to specify event sources upon creation'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - let bindCount = 0; - - class EventSource implements lambda.IEventSource { - public bind(_: lambda.IFunction): void { - bindCount++; - } - } - - // WHEN - new lambda.Function(stack, 'fn', { - code: lambda.Code.fromInline('boom'), - runtime: lambda.Runtime.NODEJS_10_X, - handler: 'index.bam', - events: [ - new EventSource(), - new EventSource(), - ], - }); - - // THEN - test.deepEqual(bindCount, 2); - test.done(); - }, - - 'Provided Runtime returns the right values'(test: Test) { - const rt = lambda.Runtime.PROVIDED; - - test.equal(rt.name, 'provided'); - test.equal(rt.supportsInlineCode, false); - - test.done(); - }, - - 'specify log retention'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new lambda.Function(stack, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS, - logRetention: logs.RetentionDays.ONE_MONTH, - }); - - // THEN - expect(stack).to(haveResource('Custom::LogRetention', { - 'LogGroupName': { - 'Fn::Join': [ - '', - [ - '/aws/lambda/', - { - Ref: 'MyLambdaCCE802FB', - }, - ], - ], - }, - 'RetentionInDays': 30, - })); - - test.done(); - }, - - 'imported lambda with imported security group and allowAllOutbound set to false'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - const fn = lambda.Function.fromFunctionAttributes(stack, 'fn', { - functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function', - securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', { - allowAllOutbound: false, - }), - }); - - // WHEN - fn.connections.allowToAnyIpv4(ec2.Port.tcp(443)); - - // THEN - expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { - GroupId: 'sg-123456789', - })); - - test.done(); - }, - - 'with event invoke config'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new lambda.Function(stack, 'fn', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - onFailure: { - bind: () => ({ destination: 'on-failure-arn' }), - }, - onSuccess: { - bind: () => ({ destination: 'on-success-arn' }), - }, - maxEventAge: cdk.Duration.hours(1), - retryAttempts: 0, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { - FunctionName: { - Ref: 'fn5FF616E3', - }, - Qualifier: '$LATEST', - DestinationConfig: { - OnFailure: { - Destination: 'on-failure-arn', - }, - OnSuccess: { - Destination: 'on-success-arn', - }, - }, - MaximumEventAgeInSeconds: 3600, - MaximumRetryAttempts: 0, - })); - - test.done(); - }, - - 'throws when calling configureAsyncInvoke on already configured function'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - maxEventAge: cdk.Duration.hours(1), - }); - - // THEN - test.throws(() => fn.configureAsyncInvoke({ retryAttempts: 0 }), /An EventInvokeConfig has already been configured/); - - test.done(); - }, - - 'event invoke config on imported lambda'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = lambda.Function.fromFunctionAttributes(stack, 'fn', { - functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function', - }); - - // WHEN - fn.configureAsyncInvoke({ - retryAttempts: 1, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { - FunctionName: 'my-function', - Qualifier: '$LATEST', - MaximumRetryAttempts: 1, - })); - - test.done(); - }, - - 'add a version with event invoke config'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, - }); - - // WHEN - fn.addVersion('1', 'sha256', 'desc', undefined, { - retryAttempts: 0, - }); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::EventInvokeConfig', { - FunctionName: { - Ref: 'fn5FF616E3', - }, - Qualifier: { - 'Fn::GetAtt': [ - 'fnVersion197FA813F', - 'Version', - ], - }, - MaximumRetryAttempts: 0, - })); - - test.done(); - }, - - 'check edge compatibility with env vars that can be removed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_12_X, - }); - fn.addEnvironment('KEY', 'value', { removeInEdge: true }); - - // WHEN - fn._checkEdgeCompatibility(); - - // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { - Environment: ABSENT, - })); - - test.done(); - }, - - 'check edge compatibility with env vars that cannot be removed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = new lambda.Function(stack, 'fn', { - code: new lambda.InlineCode('foo'), - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_12_X, - environment: { - KEY: 'value', - }, - }); - fn.addEnvironment('OTHER_KEY', 'other_value', { removeInEdge: true }); - - // THEN - test.throws(() => fn._checkEdgeCompatibility(), /The function Default\/fn contains environment variables \[KEY\] and is not compatible with Lambda@Edge/); - - test.done(); - }, -}; - -function newTestLambda(scope: constructs.Construct) { - return new lambda.Function(scope, 'MyLambda', { - code: new lambda.InlineCode('foo'), - handler: 'bar', - runtime: lambda.Runtime.PYTHON_2_7, - }); -} diff --git a/packages/@aws-cdk/aws-lambda/test/test.runtime.ts b/packages/@aws-cdk/aws-lambda/test/test.runtime.ts deleted file mode 100644 index e46686669da1d..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/test.runtime.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Test, testCase } from 'nodeunit'; -import * as lambda from '../lib'; - -export = testCase({ - 'runtimes are equal for different instances'(test: Test) { - // GIVEN - const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - - // WHEN - const result = runtime1.runtimeEquals(runtime2); - - // THEN - test.strictEqual(result, true, 'Runtimes should be equal'); - - test.done(); - }, - 'runtimes are equal for same instance'(test: Test) { - // GIVEN - const runtime = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - - // WHEN - const result = runtime.runtimeEquals(runtime); - - // THEN - test.strictEqual(result, true, 'Runtimes should be equal'); - - test.done(); - }, - 'unequal when name changes'(test: Test) { - // GIVEN - const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - const runtime2 = new lambda.Runtime('python3.6', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - - // WHEN - const result = runtime1.runtimeEquals(runtime2); - - // THEN - test.strictEqual(result, false, 'Runtimes should be unequal when name changes'); - - test.done(); - }, - 'unequal when family changes'(test: Test) { - // GIVEN - const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.JAVA, { supportsInlineCode: true }); - - // WHEN - const result = runtime1.runtimeEquals(runtime2); - - // THEN - test.strictEqual(result, false, 'Runtimes should be unequal when family changes'); - - test.done(); - }, - 'unequal when supportsInlineCode changes'(test: Test) { - // GIVEN - const runtime1 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: true }); - const runtime2 = new lambda.Runtime('python3.7', lambda.RuntimeFamily.PYTHON, { supportsInlineCode: false }); - - // WHEN - const result = runtime1.runtimeEquals(runtime2); - - // THEN - test.strictEqual(result, false, 'Runtimes should be unequal when supportsInlineCode changes'); - - test.done(); - }, - 'bundlingDockerImage points to AWS SAM build image'(test: Test) { - // GIVEN - const runtime = new lambda.Runtime('my-runtime-name'); - - // THEN - test.equal(runtime.bundlingDockerImage.image, 'amazon/aws-sam-cli-build-image-my-runtime-name'); - - test.done(); - }, - 'overridde to bundlingDockerImage points to the correct image'(test: Test) { - // GIVEN - const runtime = new lambda.Runtime('my-runtime-name', undefined, { - bundlingDockerImage: 'my-docker-image', - }); - - // THEN - test.equal(runtime.bundlingDockerImage.image, 'my-docker-image'); - - test.done(); - }, - 'dotnetcore and go have overridden images'(test: Test) { - test.equal(lambda.Runtime.DOTNET_CORE_3_1.bundlingDockerImage.image, 'lambci/lambda:build-dotnetcore3.1'); - test.equal(lambda.Runtime.DOTNET_CORE_2_1.bundlingDockerImage.image, 'lambci/lambda:build-dotnetcore2.1'); - test.equal(lambda.Runtime.GO_1_X.bundlingDockerImage.image, 'lambci/lambda:build-go1.x'); - test.done(); - }, -}); diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts similarity index 61% rename from packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts rename to packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index 1f560f151b01d..8c3dde47306b3 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -1,35 +1,34 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { ICallbackFunction, Test } from 'nodeunit'; import * as lambda from '../lib'; -export = { - 'lambda in a VPC': classFixture(class Henk { - private readonly app: cdk.App; - private readonly stack: cdk.Stack; - private readonly vpc: ec2.Vpc; - private readonly lambda: lambda.Function; +describe('lambda + vpc', () => { + describe('lambda in vpc', () => { + let app: cdk.App; + let stack: cdk.Stack; + let vpc: ec2.Vpc; + let fn: lambda.Function; - constructor() { + beforeEach(() => { // GIVEN - this.app = new cdk.App(); - this.stack = new cdk.Stack(this.app, 'stack'); - this.vpc = new ec2.Vpc(this.stack, 'VPC'); + app = new cdk.App(); + stack = new cdk.Stack(app, 'stack'); + vpc = new ec2.Vpc(stack, 'VPC'); // WHEN - this.lambda = new lambda.Function(this.stack, 'Lambda', { + fn = new lambda.Function(stack, 'Lambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, - vpc: this.vpc, + vpc: vpc, allowAllOutbound: false, }); - } + }); - public 'has subnet and securitygroup'(test: Test) { + test('has subnet and securitygroup', () => { // THEN - expect(this.stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['LambdaSecurityGroupE74659A1', 'GroupId'] }, @@ -39,22 +38,20 @@ export = { { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }, - })); - - test.done(); - } + }); + }); - public 'has securitygroup that is passed in props'(test: Test) { + test('has securitygroup that is passed in props', () => { // WHEN - new lambda.Function(this.stack, 'LambdaWithCustomSG', { + new lambda.Function(stack, 'LambdaWithCustomSG', { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, - vpc: this.vpc, - securityGroup: new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupX', { vpc: this.vpc }), + vpc, + securityGroup: new ec2.SecurityGroup(stack, 'CustomSecurityGroupX', { vpc }), }); // THEN - expect(this.stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['CustomSecurityGroupX6C7F3A78', 'GroupId'] }, @@ -64,25 +61,23 @@ export = { { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }, - })); - - test.done(); - } + }); + }); - public 'has all the securitygroups that are passed as a list of SG in props'(test: Test) { + test('has all the securitygroups that are passed as a list of SG in props', () => { // WHEN - new lambda.Function(this.stack, 'LambdaWithCustomSGList', { + new lambda.Function(stack, 'LambdaWithCustomSGList', { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, - vpc: this.vpc, + vpc, securityGroups: [ - new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupA', { vpc: this.vpc }), - new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupB', { vpc: this.vpc }), + new ec2.SecurityGroup(stack, 'CustomSecurityGroupA', { vpc }), + new ec2.SecurityGroup(stack, 'CustomSecurityGroupB', { vpc }), ], }); // THEN - expect(this.stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['CustomSecurityGroupA267F62DE', 'GroupId'] }, @@ -93,72 +88,66 @@ export = { { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }, - })); - - test.done(); - } + }); + }); - public 'fails if both of securityGroup and securityGroups are passed in props at once'(test: Test) { + test('fails if both of securityGroup and securityGroups are passed in props at once', () => { // THEN - test.throws(() => { - new lambda.Function(this.stack, 'LambdaWithWrongProps', { + expect(() => { + new lambda.Function(stack, 'LambdaWithWrongProps', { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, - vpc: this.vpc, - securityGroup: new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupB', { vpc: this.vpc }), + vpc, + securityGroup: new ec2.SecurityGroup(stack, 'CustomSecurityGroupB', { vpc }), securityGroups: [ - new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupC', { vpc: this.vpc }), - new ec2.SecurityGroup(this.stack, 'CustomSecurityGroupD', { vpc: this.vpc }), + new ec2.SecurityGroup(stack, 'CustomSecurityGroupC', { vpc }), + new ec2.SecurityGroup(stack, 'CustomSecurityGroupD', { vpc }), ], }); - }, /Only one of the function props, securityGroup or securityGroups, is allowed/); - - test.done(); - } + }).toThrow(/Only one of the function props, securityGroup or securityGroups, is allowed/); + }); - public 'participates in Connections objects'(test: Test) { + test('participates in Connections objects', () => { // GIVEN - const securityGroup = new ec2.SecurityGroup(this.stack, 'SomeSecurityGroup', { vpc: this.vpc }); + const securityGroup = new ec2.SecurityGroup(stack, 'SomeSecurityGroup', { vpc }); const somethingConnectable = new SomethingConnectable(new ec2.Connections({ securityGroups: [securityGroup] })); // WHEN - this.lambda.connections.allowTo(somethingConnectable, ec2.Port.allTcp(), 'Lambda can call connectable'); + fn.connections.allowTo(somethingConnectable, ec2.Port.allTcp(), 'Lambda can call connectable'); // THEN: Lambda can connect to SomeSecurityGroup - expect(this.stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { GroupId: { 'Fn::GetAtt': ['LambdaSecurityGroupE74659A1', 'GroupId'] }, IpProtocol: 'tcp', Description: 'Lambda can call connectable', DestinationSecurityGroupId: { 'Fn::GetAtt': ['SomeSecurityGroupEF219AD6', 'GroupId'] }, FromPort: 0, ToPort: 65535, - })); + }); // THEN: SomeSecurityGroup accepts connections from Lambda - expect(this.stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', Description: 'Lambda can call connectable', FromPort: 0, GroupId: { 'Fn::GetAtt': ['SomeSecurityGroupEF219AD6', 'GroupId'] }, SourceSecurityGroupId: { 'Fn::GetAtt': ['LambdaSecurityGroupE74659A1', 'GroupId'] }, ToPort: 65535, - })); - - test.done(); - } + }); + }); - public 'can still make Connections after export/import'(test: Test) { + test('can still make Connections after export/import', () => { // GIVEN - const stack2 = new cdk.Stack(this.app, 'stack2'); - const securityGroup = new ec2.SecurityGroup(stack2, 'SomeSecurityGroup', { vpc: this.vpc }); + const stack2 = new cdk.Stack(app, 'stack2'); + const securityGroup = new ec2.SecurityGroup(stack2, 'SomeSecurityGroup', { vpc }); const somethingConnectable = new SomethingConnectable(new ec2.Connections({ securityGroups: [securityGroup] })); // WHEN - somethingConnectable.connections.allowFrom(this.lambda.connections, ec2.Port.allTcp(), 'Lambda can call connectable'); + somethingConnectable.connections.allowFrom(fn.connections, ec2.Port.allTcp(), 'Lambda can call connectable'); // THEN: SomeSecurityGroup accepts connections from Lambda - expect(stack2).to(haveResource('AWS::EC2::SecurityGroupEgress', { + expect(stack2).toHaveResource('AWS::EC2::SecurityGroupEgress', { GroupId: { 'Fn::ImportValue': 'stack:ExportsOutputFnGetAttLambdaSecurityGroupE74659A1GroupId8F3EC6F1', }, @@ -172,10 +161,10 @@ export = { }, FromPort: 0, ToPort: 65535, - })); + }); // THEN: Lambda can connect to SomeSecurityGroup - expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { + expect(stack2).toHaveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', Description: 'Lambda can call connectable', FromPort: 0, @@ -189,13 +178,11 @@ export = { 'Fn::ImportValue': 'stack:ExportsOutputFnGetAttLambdaSecurityGroupE74659A1GroupId8F3EC6F1', }, ToPort: 65535, - })); - - test.done(); - } - }), + }); + }); + }); - 'lambda without VPC throws Error upon accessing connections'(test: Test) { + test('lambda without VPC throws Error upon accessing connections', () => { // GIVEN const stack = new cdk.Stack(); const lambdaFn = new lambda.Function(stack, 'Lambda', { @@ -205,14 +192,12 @@ export = { }); // WHEN - test.throws(() => { + expect(() => { lambdaFn.connections.allowToAnyIpv4(ec2.Port.allTcp(), 'Reach for the world Lambda!'); - }); - - test.done(); - }, + }).toThrow(); + }); - 'can pick public subnet for Lambda'(test: Test) { + test('can pick public subnet for Lambda', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -228,7 +213,7 @@ export = { }); // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['PublicLambdaSecurityGroup61D896FD', 'GroupId'] }, @@ -238,11 +223,10 @@ export = { { Ref: 'VPCPublicSubnet2Subnet74179F39' }, ], }, - })); - test.done(); - }, + }); + }); - 'can pick private subnet for Lambda'(test: Test) { + test('can pick private subnet for Lambda', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -258,7 +242,7 @@ export = { // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['PrivateLambdaSecurityGroupF53C8342', 'GroupId'] }, @@ -268,11 +252,10 @@ export = { { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }, - })); - test.done(); - }, + }); + }); - 'can pick isolated subnet for Lambda'(test: Test) { + test('can pick isolated subnet for Lambda', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC', { @@ -295,7 +278,7 @@ export = { // THEN - expect(stack).to(haveResource('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['IsolatedLambdaSecurityGroupCE25B6A9', 'GroupId'] }, @@ -305,11 +288,10 @@ export = { { Ref: 'VPCIsolatedSubnet2Subnet4B1C8CAA' }, ], }, - })); - test.done(); - }, + }); + }); - 'picking public subnet type is not allowed if not overriding allowPublicSubnet'(test: Test) { + test('picking public subnet type is not allowed if not overriding allowPublicSubnet', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC', { @@ -330,7 +312,7 @@ export = { }); // WHEN - test.throws(() => { + expect(() => { new lambda.Function(stack, 'PublicLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', @@ -338,41 +320,9 @@ export = { vpc, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); - }, /Lambda Functions in a public subnet/); - test.done(); - }, -}; - -/** - * Use a class as test fixture - * - * setUp() will be mapped to the (synchronous) constructor. tearDown(cb) will be called if available. - */ -function classFixture(klass: any) { - let fixture: any; - - const ret: any = { - setUp(cb: ICallbackFunction) { - fixture = new klass(); - cb(); - }, - - tearDown(cb: ICallbackFunction) { - if (fixture.tearDown) { - fixture.tearDown(cb); - } else { - cb(); - } - }, - }; - - const testNames = Reflect.ownKeys(klass.prototype).filter(m => m !== 'tearDown' && m !== 'constructor'); - for (const testName of testNames) { - ret[testName] = (test: Test) => fixture[testName](test); - } - - return ret; -} + }).toThrow(/Lambda Functions in a public subnet/); + }); +}); class SomethingConnectable implements ec2.IConnectable { constructor(public readonly connections: ec2.Connections) { diff --git a/packages/@aws-cdk/aws-logs-destinations/.npmignore b/packages/@aws-cdk/aws-logs-destinations/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-logs-destinations/.npmignore +++ b/packages/@aws-cdk/aws-logs-destinations/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs-destinations/package.json b/packages/@aws-cdk/aws-logs-destinations/package.json index 8f7f06ef3d4b0..cb20a511c902e 100644 --- a/packages/@aws-cdk/aws-logs-destinations/package.json +++ b/packages/@aws-cdk/aws-logs-destinations/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-logs/.npmignore b/packages/@aws-cdk/aws-logs/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-logs/.npmignore +++ b/packages/@aws-cdk/aws-logs/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 43f2520c4280b..d92212dd10a68 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Logs", diff --git a/packages/@aws-cdk/aws-macie/.npmignore b/packages/@aws-cdk/aws-macie/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-macie/.npmignore +++ b/packages/@aws-cdk/aws-macie/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-macie/package.json b/packages/@aws-cdk/aws-macie/package.json index 88f4023ea790e..31cadb759a266 100644 --- a/packages/@aws-cdk/aws-macie/package.json +++ b/packages/@aws-cdk/aws-macie/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Macie", diff --git a/packages/@aws-cdk/aws-managedblockchain/.npmignore b/packages/@aws-cdk/aws-managedblockchain/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-managedblockchain/.npmignore +++ b/packages/@aws-cdk/aws-managedblockchain/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-managedblockchain/package.json b/packages/@aws-cdk/aws-managedblockchain/package.json index 11963c04905d8..5172350e42045 100644 --- a/packages/@aws-cdk/aws-managedblockchain/package.json +++ b/packages/@aws-cdk/aws-managedblockchain/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ManagedBlockchain", diff --git a/packages/@aws-cdk/aws-mediaconvert/.npmignore b/packages/@aws-cdk/aws-mediaconvert/.npmignore index a7c5b49852b3b..c2827f80c26db 100644 --- a/packages/@aws-cdk/aws-mediaconvert/.npmignore +++ b/packages/@aws-cdk/aws-mediaconvert/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediaconvert/package.json b/packages/@aws-cdk/aws-mediaconvert/package.json index 90371cff4644f..9b3653dce2e4c 100644 --- a/packages/@aws-cdk/aws-mediaconvert/package.json +++ b/packages/@aws-cdk/aws-mediaconvert/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::MediaConvert", diff --git a/packages/@aws-cdk/aws-medialive/.npmignore b/packages/@aws-cdk/aws-medialive/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-medialive/.npmignore +++ b/packages/@aws-cdk/aws-medialive/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-medialive/package.json b/packages/@aws-cdk/aws-medialive/package.json index ce03aa8f8559d..d6039025f23d4 100644 --- a/packages/@aws-cdk/aws-medialive/package.json +++ b/packages/@aws-cdk/aws-medialive/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::MediaLive", diff --git a/packages/@aws-cdk/aws-mediastore/.npmignore b/packages/@aws-cdk/aws-mediastore/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-mediastore/.npmignore +++ b/packages/@aws-cdk/aws-mediastore/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-mediastore/package.json b/packages/@aws-cdk/aws-mediastore/package.json index 1d0e9516ac62f..5beb69fd781e2 100644 --- a/packages/@aws-cdk/aws-mediastore/package.json +++ b/packages/@aws-cdk/aws-mediastore/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::MediaStore", diff --git a/packages/@aws-cdk/aws-msk/.npmignore b/packages/@aws-cdk/aws-msk/.npmignore index 917201c845418..f3eaded585e2d 100644 --- a/packages/@aws-cdk/aws-msk/.npmignore +++ b/packages/@aws-cdk/aws-msk/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-msk/package.json b/packages/@aws-cdk/aws-msk/package.json index c2b82ed9b62d0..d3304bd2d3d99 100644 --- a/packages/@aws-cdk/aws-msk/package.json +++ b/packages/@aws-cdk/aws-msk/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::MSK", diff --git a/packages/@aws-cdk/aws-neptune/.npmignore b/packages/@aws-cdk/aws-neptune/.npmignore index 3dffd1ce79a72..2892fc6e99416 100644 --- a/packages/@aws-cdk/aws-neptune/.npmignore +++ b/packages/@aws-cdk/aws-neptune/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index 4d99679cc9705..e5acaee45a5a0 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Neptune", diff --git a/packages/@aws-cdk/aws-networkmanager/.npmignore b/packages/@aws-cdk/aws-networkmanager/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-networkmanager/.npmignore +++ b/packages/@aws-cdk/aws-networkmanager/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-networkmanager/package.json b/packages/@aws-cdk/aws-networkmanager/package.json index 472734536bacb..c37cae4a92c20 100644 --- a/packages/@aws-cdk/aws-networkmanager/package.json +++ b/packages/@aws-cdk/aws-networkmanager/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::NetworkManager", diff --git a/packages/@aws-cdk/aws-opsworks/.npmignore b/packages/@aws-cdk/aws-opsworks/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-opsworks/.npmignore +++ b/packages/@aws-cdk/aws-opsworks/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworks/package.json b/packages/@aws-cdk/aws-opsworks/package.json index b33783b40cb45..34dece81bd77f 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::OpsWorks", diff --git a/packages/@aws-cdk/aws-opsworkscm/.npmignore b/packages/@aws-cdk/aws-opsworkscm/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-opsworkscm/.npmignore +++ b/packages/@aws-cdk/aws-opsworkscm/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opsworkscm/package.json b/packages/@aws-cdk/aws-opsworkscm/package.json index 107aa2d9a4f47..a43d46ae04d3f 100644 --- a/packages/@aws-cdk/aws-opsworkscm/package.json +++ b/packages/@aws-cdk/aws-opsworkscm/package.json @@ -50,7 +50,8 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::OpsWorksCM", diff --git a/packages/@aws-cdk/aws-pinpoint/.npmignore b/packages/@aws-cdk/aws-pinpoint/.npmignore index 917201c845418..f3eaded585e2d 100644 --- a/packages/@aws-cdk/aws-pinpoint/.npmignore +++ b/packages/@aws-cdk/aws-pinpoint/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpoint/package.json b/packages/@aws-cdk/aws-pinpoint/package.json index fd660c7d8e04c..f4c5c8a97c063 100644 --- a/packages/@aws-cdk/aws-pinpoint/package.json +++ b/packages/@aws-cdk/aws-pinpoint/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Pinpoint", diff --git a/packages/@aws-cdk/aws-pinpointemail/.npmignore b/packages/@aws-cdk/aws-pinpointemail/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-pinpointemail/.npmignore +++ b/packages/@aws-cdk/aws-pinpointemail/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-pinpointemail/package.json b/packages/@aws-cdk/aws-pinpointemail/package.json index ec43537f13f82..70add6d8405eb 100644 --- a/packages/@aws-cdk/aws-pinpointemail/package.json +++ b/packages/@aws-cdk/aws-pinpointemail/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::PinpointEmail", diff --git a/packages/@aws-cdk/aws-qldb/.npmignore b/packages/@aws-cdk/aws-qldb/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-qldb/.npmignore +++ b/packages/@aws-cdk/aws-qldb/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-qldb/package.json b/packages/@aws-cdk/aws-qldb/package.json index 158598e00ac11..2c12734c3bf7a 100644 --- a/packages/@aws-cdk/aws-qldb/package.json +++ b/packages/@aws-cdk/aws-qldb/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::QLDB", diff --git a/packages/@aws-cdk/aws-ram/.npmignore b/packages/@aws-cdk/aws-ram/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-ram/.npmignore +++ b/packages/@aws-cdk/aws-ram/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ram/package.json b/packages/@aws-cdk/aws-ram/package.json index aee469902f46c..a38b60a12404b 100644 --- a/packages/@aws-cdk/aws-ram/package.json +++ b/packages/@aws-cdk/aws-ram/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::RAM", diff --git a/packages/@aws-cdk/aws-rds/.npmignore b/packages/@aws-cdk/aws-rds/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-rds/.npmignore +++ b/packages/@aws-cdk/aws-rds/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index 032c76101798a..5ae25357ed2ab 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -438,6 +438,8 @@ export class AuroraPostgresEngineVersion { public static readonly VER_11_6 = AuroraPostgresEngineVersion.of('11.6', '11', { s3Import: true, s3Export: true }); /** Version "11.7". */ public static readonly VER_11_7 = AuroraPostgresEngineVersion.of('11.7', '11', { s3Import: true, s3Export: true }); + /** Version "11.8". */ + public static readonly VER_11_8 = AuroraPostgresEngineVersion.of('11.8', '11', { s3Import: true, s3Export: true }); /** * Create a new AuroraPostgresEngineVersion with an arbitrary version. diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index 570e7b81a0479..bca23a9dc8ac5 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -349,6 +349,8 @@ export class MysqlEngineVersion { public static readonly VER_8_0_17 = MysqlEngineVersion.of('8.0.17', '8.0'); /** Version "8.0.19". */ public static readonly VER_8_0_19 = MysqlEngineVersion.of('8.0.19', '8.0'); + /** Version "8.0.20 ". */ + public static readonly VER_8_0_20 = MysqlEngineVersion.of('8.0.20', '8.0'); /** * Create a new MysqlEngineVersion with an arbitrary version. @@ -508,6 +510,8 @@ export class PostgresEngineVersion { public static readonly VER_10_12 = PostgresEngineVersion.of('10.12', '10', { s3Import: true }); /** Version "10.13". */ public static readonly VER_10_13 = PostgresEngineVersion.of('10.13', '10', { s3Import: true }); + /** Version "10.14". */ + public static readonly VER_10_14 = PostgresEngineVersion.of('10.14', '10', { s3Import: true }); /** Version "11" (only a major version, without a specific minor version). */ public static readonly VER_11 = PostgresEngineVersion.of('11', '11', { s3Import: true }); @@ -525,6 +529,8 @@ export class PostgresEngineVersion { public static readonly VER_11_7 = PostgresEngineVersion.of('11.7', '11', { s3Import: true }); /** Version "11.8". */ public static readonly VER_11_8 = PostgresEngineVersion.of('11.8', '11', { s3Import: true }); + /** Version "11.9". */ + public static readonly VER_11_9 = PostgresEngineVersion.of('11.9', '11', { s3Import: true }); /** Version "12" (only a major version, without a specific minor version). */ public static readonly VER_12 = PostgresEngineVersion.of('12', '12', { s3Import: true }); @@ -532,6 +538,8 @@ export class PostgresEngineVersion { public static readonly VER_12_2 = PostgresEngineVersion.of('12.2', '12', { s3Import: true }); /** Version "12.3". */ public static readonly VER_12_3 = PostgresEngineVersion.of('12.3', '12', { s3Import: true }); + /** Version "12.4". */ + public static readonly VER_12_4 = PostgresEngineVersion.of('12.4', '12', { s3Import: true }); /** * Create a new PostgresEngineVersion with an arbitrary version. diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index a41acb74fb265..fa3ecac082fab 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -365,8 +365,10 @@ export interface DatabaseInstanceNewProps { readonly iamAuthentication?: boolean; /** - * The number of days during which automatic DB snapshots are retained. Set - * to zero to disable backups. + * The number of days during which automatic DB snapshots are retained. + * Set to zero to disable backups. + * When creating a read replica, you must enable automatic backups on the source + * database instance by setting the backup retention to a value other than zero. * * @default Duration.days(1) */ diff --git a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts index 3f2dbfef37c7d..43305ac2d6e26 100644 --- a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource } from '@aws-cdk/core'; +import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IClusterEngine } from './cluster-engine'; import { DatabaseSecret } from './database-secret'; @@ -23,6 +23,11 @@ export interface IServerlessCluster extends IResource, ec2.IConnectable, secrets */ readonly clusterIdentifier: string; + /** + * The ARN of the cluster + */ + readonly clusterArn: string; + /** * The endpoint to use for read/write operations * @attribute EndpointAddress,EndpointPort @@ -61,8 +66,9 @@ export interface ServerlessClusterProps { readonly clusterIdentifier?: string; /** - * The number of days during which automatic DB snapshots are retained. Set - * to zero to disable backups. + * The number of days during which automatic DB snapshots are retained. + * Automatic backup retention cannot be disabled on serverless clusters. + * Must be a value from 1 day to 35 days. * * @default Duration.days(1) */ @@ -282,6 +288,18 @@ abstract class ServerlessClusterBase extends Resource implements IServerlessClus */ public abstract readonly connections: ec2.Connections; + /** + * The ARN of the cluster + */ + public get clusterArn(): string { + return Stack.of(this).formatArn({ + service: 'rds', + resource: 'cluster', + sep: ':', + resourceName: this.clusterIdentifier, + }); + } + /** * Renders the secret attachment target specifications. */ @@ -351,6 +369,13 @@ export class ServerlessCluster extends ServerlessClusterBase { removalPolicy: props.removalPolicy === RemovalPolicy.RETAIN ? props.removalPolicy : undefined, }); + if (props.backupRetention) { + const backupRetentionDays = props.backupRetention.toDays(); + if (backupRetentionDays < 1 || backupRetentionDays > 35) { + throw new Error(`backup retention period must be between 1 and 35 days. received: ${backupRetentionDays}`); + } + } + let credentials = props.credentials ?? Credentials.fromUsername(props.engine.defaultUsername ?? 'admin'); if (!credentials.secret && !credentials.password) { credentials = Credentials.fromSecret(new DatabaseSecret(this, 'Secret', { diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 3169954e99ffa..5cac89b6e38f4 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::RDS", diff --git a/packages/@aws-cdk/aws-rds/test/test.serverless-cluster.ts b/packages/@aws-cdk/aws-rds/test/test.serverless-cluster.ts index dbd93d4d7fd38..e3bc737ba9963 100644 --- a/packages/@aws-cdk/aws-rds/test/test.serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.serverless-cluster.ts @@ -599,6 +599,29 @@ export = { test.done(); }, + 'throws when invalid backup retention period is specified'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); + + // WHEN + test.throws(() => + new ServerlessCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + vpc, + backupRetention: cdk.Duration.days(0), + }), /backup retention period must be between 1 and 35 days. received: 0/); + + test.throws(() => + new ServerlessCluster(stack, 'Another Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + vpc, + backupRetention: cdk.Duration.days(36), + }), /backup retention period must be between 1 and 35 days. received: 36/); + + test.done(); + }, + 'throws error when min capacity is greater than max capacity'(test: Test) { // GIVEN const stack = testStack(); @@ -617,6 +640,37 @@ export = { test.done(); }, + + 'check that clusterArn property works'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); + const cluster = new ServerlessCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + vpc, + }); + const exportName = 'DbCluterArn'; + + // WHEN + new cdk.CfnOutput(stack, exportName, { + exportName, + value: cluster.clusterArn, + }); + + // THEN + test.deepEqual(stack.resolve(cluster.clusterArn), { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':rds:us-test-1:12345:cluster:', + { Ref: 'DatabaseB269D8BB' }, + ], + ], + }); + test.done(); + }, }; function testStack() { diff --git a/packages/@aws-cdk/aws-redshift/.npmignore b/packages/@aws-cdk/aws-redshift/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-redshift/.npmignore +++ b/packages/@aws-cdk/aws-redshift/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-redshift/README.md b/packages/@aws-cdk/aws-redshift/README.md index 05736c4c15c2c..11fe7a8fd363d 100644 --- a/packages/@aws-cdk/aws-redshift/README.md +++ b/packages/@aws-cdk/aws-redshift/README.md @@ -1,4 +1,5 @@ ## Amazon Redshift Construct Library + --- @@ -6,6 +7,10 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- @@ -24,6 +29,7 @@ const cluster = new redshift.Cluster(this, 'Redshift', { 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. @@ -46,11 +52,13 @@ 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 diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index 0856766b99fb9..3b0667364772a 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -416,7 +416,6 @@ export class Cluster extends ClusterBase { props.securityGroups : [new ec2.SecurityGroup(this, 'SecurityGroup', { description: 'Redshift security group', vpc: this.vpc, - securityGroupName: 'redshift SG', })]; const securityGroupIds = securityGroups.map(sg => sg.securityGroupId); diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 77deed8ac0c8e..6df900877d373 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Redshift", @@ -74,7 +75,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { @@ -110,7 +111,7 @@ ] }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-resourcegroups/.npmignore b/packages/@aws-cdk/aws-resourcegroups/.npmignore index c8874799485a3..5bd978c36b820 100644 --- a/packages/@aws-cdk/aws-resourcegroups/.npmignore +++ b/packages/@aws-cdk/aws-resourcegroups/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-resourcegroups/package.json b/packages/@aws-cdk/aws-resourcegroups/package.json index bc9d79393aeaf..1e0965bc9bbd3 100644 --- a/packages/@aws-cdk/aws-resourcegroups/package.json +++ b/packages/@aws-cdk/aws-resourcegroups/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ResourceGroups", diff --git a/packages/@aws-cdk/aws-robomaker/.npmignore b/packages/@aws-cdk/aws-robomaker/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-robomaker/.npmignore +++ b/packages/@aws-cdk/aws-robomaker/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-robomaker/package.json b/packages/@aws-cdk/aws-robomaker/package.json index 5baeb85f6582e..f0479c1ad5f73 100644 --- a/packages/@aws-cdk/aws-robomaker/package.json +++ b/packages/@aws-cdk/aws-robomaker/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::RoboMaker", diff --git a/packages/@aws-cdk/aws-route53-patterns/.npmignore b/packages/@aws-cdk/aws-route53-patterns/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-route53-patterns/.npmignore +++ b/packages/@aws-cdk/aws-route53-patterns/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-patterns/package.json b/packages/@aws-cdk/aws-route53-patterns/package.json index 0ea5727474b23..f85057928fb12 100644 --- a/packages/@aws-cdk/aws-route53-patterns/package.json +++ b/packages/@aws-cdk/aws-route53-patterns/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53-targets/.npmignore b/packages/@aws-cdk/aws-route53-targets/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-route53-targets/.npmignore +++ b/packages/@aws-cdk/aws-route53-targets/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index 1d069b61487a8..078ff6f6acb50 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -69,7 +69,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/.npmignore b/packages/@aws-cdk/aws-route53/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-route53/.npmignore +++ b/packages/@aws-cdk/aws-route53/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 4aa7c9084b782..43d57731f2c9e 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Route53", diff --git a/packages/@aws-cdk/aws-route53resolver/.npmignore b/packages/@aws-cdk/aws-route53resolver/.npmignore index ab90672b1d91e..207e92300ba4a 100644 --- a/packages/@aws-cdk/aws-route53resolver/.npmignore +++ b/packages/@aws-cdk/aws-route53resolver/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index e0003ac9de3fc..f191269f0f42b 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Route53Resolver", diff --git a/packages/@aws-cdk/aws-s3-assets/.npmignore b/packages/@aws-cdk/aws-s3-assets/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-s3-assets/.npmignore +++ b/packages/@aws-cdk/aws-s3-assets/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/.npmignore b/packages/@aws-cdk/aws-s3-deployment/.npmignore index f1b35d10f16cf..6ed30427bcfa6 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.npmignore +++ b/packages/@aws-cdk/aws-s3-deployment/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py b/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py index 474eb6d69226c..300e8d0772bf0 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py @@ -168,7 +168,7 @@ def create_metadata_args(raw_user_metadata, raw_system_metadata): return [] format_system_metadata_key = lambda k: k.lower() - format_user_metadata_key = lambda k: k.lower() if k.lower().startswith("x-amzn-meta-") else f"x-amzn-meta-{k.lower()}" + format_user_metadata_key = lambda k: k.lower() if k.lower().startswith("x-amz-meta-") else f"x-amz-meta-{k.lower()}" system_metadata = { format_system_metadata_key(k): v for k, v in raw_system_metadata.items() } user_metadata = { format_user_metadata_key(k): v for k, v in raw_user_metadata.items() } diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py b/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py index 24552c98d45ea..df48d6cdf38ff 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py @@ -119,7 +119,7 @@ def test_create_update_with_metadata(self): self.assertAwsCommands( ["s3", "cp", "s3:///", "archive.zip"], - ["s3", "sync", "--delete", "contents.zip", "s3:///", "--content-type", "text/html", "--content-language", "en", "--metadata", "{\"x-amzn-meta-best\":\"game\"}", "--metadata-directive", "REPLACE"] + ["s3", "sync", "--delete", "contents.zip", "s3:///", "--content-type", "text/html", "--content-language", "en", "--metadata", "{\"x-amz-meta-best\":\"game\"}", "--metadata-directive", "REPLACE"] ) def test_delete_no_retain(self): diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 429991c20e751..92d6ab6fbcd2f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -48,12 +48,10 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "/bin/bash lambda/build.sh" }, "cdk-build": { - "pre": [ - "/bin/bash lambda/build.sh" - ], "test": [ "/bin/bash lambda/test.sh" ], @@ -89,7 +87,7 @@ "@types/jest": "^26.0.14", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json index 7563384f9b9f0..3f025bb6bf6a9 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json @@ -251,7 +251,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3Bucket77147E20" }, "S3Key": { "Fn::Join": [ @@ -264,7 +264,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F" } ] } @@ -277,7 +277,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F" } ] } @@ -304,17 +304,17 @@ } }, "Parameters": { - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3Bucket77147E20": { "Type": "String", - "Description": "S3 bucket for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "S3 bucket for asset \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F": { "Type": "String", - "Description": "S3 key for asset version \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "S3 key for asset version \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50ArtifactHash846130E4": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176ArtifactHash4E343C6C": { "Type": "String", - "Description": "Artifact hash for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "Artifact hash for asset \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json index a3b8a6d1c81c9..ff9a3162fbae8 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json @@ -260,7 +260,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3Bucket77147E20" }, "S3Key": { "Fn::Join": [ @@ -273,7 +273,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F" } ] } @@ -286,7 +286,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" + "Ref": "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F" } ] } @@ -507,17 +507,17 @@ } }, "Parameters": { - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3Bucket77147E20": { "Type": "String", - "Description": "S3 bucket for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "S3 bucket for asset \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176S3VersionKey4253216F": { "Type": "String", - "Description": "S3 key for asset version \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "S3 key for asset version \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, - "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50ArtifactHash846130E4": { + "AssetParametersc9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176ArtifactHash4E343C6C": { "Type": "String", - "Description": "Artifact hash for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" + "Description": "Artifact hash for asset \"c9ac4b3b65f3510a2088b7fd003de23d2aefac424025eb168725ce6769e3c176\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-notifications/.npmignore b/packages/@aws-cdk/aws-s3-notifications/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-s3-notifications/.npmignore +++ b/packages/@aws-cdk/aws-s3-notifications/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index bc818c945ef07..ab017d60d912f 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -66,7 +66,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts index 10de49b776d02..6bd225d17fd0c 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/lambda.test.ts @@ -102,6 +102,22 @@ test('lambda in a different stack as notification target', () => { }); }); +test('imported lambda in a different account as notification target', () => { + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { account: '111111111111' }, + }); + + // Lambda account and stack account differ; no permissions should be created. + const lambdaFunction = lambda.Function.fromFunctionArn(stack, 'lambdaFunction', 'arn:aws:lambda:us-east-1:123456789012:function:BaseFunction'); + const bucket = new s3.Bucket(stack, 'bucket'); + + bucket.addObjectCreatedNotification(new s3n.LambdaDestination(lambdaFunction)); + + // no permissions created + expect(stack).not.toHaveResourceLike('AWS::Lambda::Permission'); +}); + test('lambda as notification target', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-s3/.gitignore b/packages/@aws-cdk/aws-s3/.gitignore index dcc1dc41e477f..17a41566f0002 100644 --- a/packages/@aws-cdk/aws-s3/.gitignore +++ b/packages/@aws-cdk/aws-s3/.gitignore @@ -15,4 +15,5 @@ nyc.config.js *.snk !.eslintrc.js -junit.xml \ No newline at end of file +junit.xml +!jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/.npmignore b/packages/@aws-cdk/aws-s3/.npmignore index 95a6e5fe5bb87..9e88226921c33 100644 --- a/packages/@aws-cdk/aws-s3/.npmignore +++ b/packages/@aws-cdk/aws-s3/.npmignore @@ -22,4 +22,6 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ +jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 335cdfd871f8a..b3eb2e1a4707d 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -31,6 +31,8 @@ new Bucket(this, 'MyFirstBucket'); `arn:aws:s3:::bucket_name/Development/*`) * `urlForObject(key)` - the HTTP URL of an object within the bucket (i.e. `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey`) + * `virtualHostedUrlForObject(key)` - the virtual-hosted style HTTP URL of an object + within the bucket (i.e. `https://china-bucket-s3.cn-north-1.amazonaws.com.cn/mykey`) * `s3UrlForObject(key)` - the S3 URL of an object within the bucket (i.e. `s3://bucket/mykey`) @@ -323,3 +325,19 @@ const bucket = new Bucket(this, 'MyRedirectedBucket', { To put files into a bucket as part of a deployment (for example, to host a website), see the `@aws-cdk/aws-s3-deployment` package, which provides a resource that can do just that. + +### The URL for objects + +S3 provides two types of URLs for accessing objects via HTTP(S). Path-Style and +[Virtual Hosted-Style](https://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html) +URL. Path-Style is a classic way and will be +[deprecated](https://aws.amazon.com/jp/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story). +We recommend to use Virtual Hosted-Style URL for newly made bucket. + +You can generate both of them. + +```ts +bucket.urlForObject('objectname'); // Path-Style URL +bucket.virtualHostedUrlForObject('objectname'); // Virtual Hosted-Style URL +bucket.virtualHostedUrlForObject('objectname', { regional: false }); // Virtual Hosted-Style URL but non-regional +``` diff --git a/packages/@aws-cdk/aws-s3/jest.config.js b/packages/@aws-cdk/aws-s3/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-s3/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 5142995ca0937..f1752cc402b5e 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -92,6 +92,20 @@ export interface IBucket extends IResource { */ urlForObject(key?: string): string; + /** + * The virtual hosted-style URL of an S3 object. Specify `regional: false` at + * the options for non-regional URL. For example: + * @example https://only-bucket.s3.us-west-1.amazonaws.com + * @example https://bucket.s3.us-west-1.amazonaws.com/key + * @example https://bucket.s3.amazonaws.com/key + * @example https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey + * @param key The S3 key of the object. If not specified, the URL of the + * bucket is returned. + * @param options Options for generating URL. + * @returns an ObjectS3Url token + */ + virtualHostedUrlForObject(key?: string, options?: VirtualHostedStyleUrlOptions): string; + /** * The S3 URL of an S3 object. For example: * @example s3://onlybucket @@ -470,7 +484,8 @@ abstract class BucketBase extends Resource implements IBucket { } /** - * The https URL of an S3 object. For example: + * The https URL of an S3 object. Specify `regional: false` at the options + * for non-regional URLs. For example: * @example https://s3.us-west-1.amazonaws.com/onlybucket * @example https://s3.us-west-1.amazonaws.com/bucket/key * @example https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey @@ -481,7 +496,31 @@ abstract class BucketBase extends Resource implements IBucket { public urlForObject(key?: string): string { const stack = Stack.of(this); const prefix = `https://s3.${stack.region}.${stack.urlSuffix}/`; - return this.buildUrl(prefix, key); + if (typeof key !== 'string') { + return this.urlJoin(prefix, this.bucketName); + } + return this.urlJoin(prefix, this.bucketName, key); + } + + /** + * The virtual hosted-style URL of an S3 object. Specify `regional: false` at + * the options for non-regional URL. For example: + * @example https://only-bucket.s3.us-west-1.amazonaws.com + * @example https://bucket.s3.us-west-1.amazonaws.com/key + * @example https://bucket.s3.amazonaws.com/key + * @example https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey + * @param key The S3 key of the object. If not specified, the URL of the + * bucket is returned. + * @param options Options for generating URL. + * @returns an ObjectS3Url token + */ + public virtualHostedUrlForObject(key?: string, options?: VirtualHostedStyleUrlOptions): string { + const domainName = options?.regional ?? true ? this.bucketRegionalDomainName : this.bucketDomainName; + const prefix = `https://${domainName}`; + if (typeof key !== 'string') { + return prefix; + } + return this.urlJoin(prefix, key); } /** @@ -493,7 +532,11 @@ abstract class BucketBase extends Resource implements IBucket { * @returns an ObjectS3Url token */ public s3UrlForObject(key?: string): string { - return this.buildUrl('s3://', key); + const prefix = 's3://'; + if (typeof key !== 'string') { + return this.urlJoin(prefix, this.bucketName); + } + return this.urlJoin(prefix, this.bucketName, key); } /** @@ -577,7 +620,8 @@ abstract class BucketBase extends Resource implements IBucket { */ public grantReadWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { const bucketActions = perms.BUCKET_READ_ACTIONS.concat(perms.BUCKET_WRITE_ACTIONS); - const keyActions = perms.KEY_READ_ACTIONS.concat(perms.KEY_WRITE_ACTIONS); + // we need unique permissions because some permissions are common between read and write key actions + const keyActions = [...new Set([...perms.KEY_READ_ACTIONS, ...perms.KEY_WRITE_ACTIONS])]; return this.grant(identity, bucketActions, @@ -623,22 +667,16 @@ abstract class BucketBase extends Resource implements IBucket { }); } - private buildUrl(prefix: string, key?: string): string { - const components = [ - prefix, - this.bucketName, - ]; - - if (key) { - // trim prepending '/' - if (typeof key === 'string' && key.startsWith('/')) { - key = key.substr(1); + private urlJoin(...components: string[]): string { + return components.reduce((result, component) => { + if (result.endsWith('/')) { + result = result.slice(0, -1); } - components.push('/'); - components.push(key); - } - - return components.join(''); + if (component.startsWith('/')) { + component = component.slice(1); + } + return `${result}/${component}`; + }); } private grant( @@ -1995,6 +2033,18 @@ export interface RoutingRule { readonly condition?: RoutingRuleCondition; } +/** + * Options for creating Virtual-Hosted style URL. + */ +export interface VirtualHostedStyleUrlOptions { + /** + * Specifies the URL includes the region. + * + * @default - true + */ + readonly regional?: boolean; +} + function mapOrUndefined(list: T[] | undefined, callback: (element: T) => U): U[] | undefined { if (!list || list.length === 0) { return undefined; diff --git a/packages/@aws-cdk/aws-s3/lib/perms.ts b/packages/@aws-cdk/aws-s3/lib/perms.ts index b9994420f590f..544bdda936da9 100644 --- a/packages/@aws-cdk/aws-s3/lib/perms.ts +++ b/packages/@aws-cdk/aws-s3/lib/perms.ts @@ -27,4 +27,5 @@ export const KEY_WRITE_ACTIONS = [ 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', + 'kms:Decrypt', // required for multipart uploads. Refer https://aws.amazon.com/premiumsupport/knowledge-center/s3-multipart-kms-decrypt/ ]; diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index fec39ed3100c9..b763221c8cfb1 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -49,13 +49,15 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::S3", "env": { "AWSLINT_BASE_CONSTRUCT": "true" - } + }, + "jest": true }, "keywords": [ "aws", @@ -71,12 +73,11 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", - "pkglint": "0.0.0" + "pkglint": "0.0.0", + "nodeunit-shim": "0.0.0" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3/test/test.aspect.ts b/packages/@aws-cdk/aws-s3/test/aspect.test.ts similarity index 95% rename from packages/@aws-cdk/aws-s3/test/test.aspect.ts rename to packages/@aws-cdk/aws-s3/test/aspect.test.ts index df020a5ca4698..fb981c8f3e27a 100644 --- a/packages/@aws-cdk/aws-s3/test/test.aspect.ts +++ b/packages/@aws-cdk/aws-s3/test/aspect.test.ts @@ -2,10 +2,10 @@ import { SynthUtils } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { IConstruct } from 'constructs'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as s3 from '../lib'; -export = { +nodeunitShim({ 'bucket must have versioning: failure'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -38,7 +38,7 @@ export = { test.done(); }, -}; +}); class BucketVersioningChecker implements cdk.IAspect { public visit(node: IConstruct): void { diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts b/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts similarity index 98% rename from packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts rename to packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts index d99ddbc1cac84..bb3a5fd6de135 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket-policy.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts @@ -1,13 +1,13 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { AnyPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; import { RemovalPolicy, Stack, App } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: /* eslint-disable quote-props */ -export = { +nodeunitShim({ 'default properties'(test: Test) { const stack = new Stack(); @@ -161,4 +161,4 @@ export = { test.done(); }, -}; \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts similarity index 92% rename from packages/@aws-cdk/aws-s3/test/test.bucket.ts rename to packages/@aws-cdk/aws-s3/test/bucket.test.ts index 2c9e13c798e96..275f4bc6aa822 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -3,13 +3,13 @@ import { expect, haveResource, haveResourceLike, SynthUtils, arrayWith, objectLi import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: /* eslint-disable quote-props */ -export = { +nodeunitShim({ 'default bucket'(test: Test) { const stack = new cdk.Stack(); @@ -127,9 +127,7 @@ export = { test.throws(() => new s3.Bucket(stack, 'MyBucket', { bucketName: bucket, - }), function(err: Error) { - return expectedErrors === err.message; - }); + }), expectedErrors); test.done(); }, @@ -1089,6 +1087,176 @@ export = { }, }, + 'grantWrite with KMS key has appropriate permissions for multipart uploads'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); + const user = new iam.User(stack, 'MyUser'); + bucket.grantWrite(user); + + expect(stack).toMatch({ + 'Resources': { + 'MyBucketKeyC17130CF': { + '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', + 'kms:TagResource', + 'kms:UntagResource', + ], + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::', + { + 'Ref': 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + 'Resource': '*', + }, + { + 'Action': [ + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Decrypt', + ], + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::GetAtt': [ + 'MyUserDC45028B', + 'Arn', + ], + }, + }, + 'Resource': '*', + }, + ], + 'Version': '2012-10-17', + }, + 'Description': 'Created by Default/MyBucket', + }, + 'UpdateReplacePolicy': 'Retain', + 'DeletionPolicy': 'Retain', + }, + 'MyBucketF68F3FF0': { + 'Type': 'AWS::S3::Bucket', + 'Properties': { + 'BucketEncryption': { + 'ServerSideEncryptionConfiguration': [ + { + 'ServerSideEncryptionByDefault': { + 'KMSMasterKeyID': { + 'Fn::GetAtt': [ + 'MyBucketKeyC17130CF', + 'Arn', + ], + }, + 'SSEAlgorithm': 'aws:kms', + }, + }, + ], + }, + }, + 'UpdateReplacePolicy': 'Retain', + 'DeletionPolicy': 'Retain', + }, + 'MyUserDC45028B': { + 'Type': 'AWS::IAM::User', + }, + 'MyUserDefaultPolicy7B897426': { + 'Type': 'AWS::IAM::Policy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 's3:DeleteObject*', + 's3:PutObject*', + 's3:Abort*', + ], + 'Effect': 'Allow', + 'Resource': [ + { + 'Fn::GetAtt': [ + 'MyBucketF68F3FF0', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'MyBucketF68F3FF0', + 'Arn', + ], + }, + '/*', + ], + ], + }, + ], + }, + { + 'Action': [ + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Decrypt', + ], + 'Effect': 'Allow', + 'Resource': { + 'Fn::GetAtt': [ + 'MyBucketKeyC17130CF', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'MyUserDefaultPolicy7B897426', + 'Users': [ + { + 'Ref': 'MyUserDC45028B', + }, + ], + }, + }, + }, + }); + + test.done(); + }, + 'more grants'(test: Test) { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); @@ -2080,4 +2248,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-s3/test/test.cors.ts b/packages/@aws-cdk/aws-s3/test/cors.test.ts similarity index 97% rename from packages/@aws-cdk/aws-s3/test/test.cors.ts rename to packages/@aws-cdk/aws-s3/test/cors.test.ts index f06819cc5dcd6..45daf74c75742 100644 --- a/packages/@aws-cdk/aws-s3/test/test.cors.ts +++ b/packages/@aws-cdk/aws-s3/test/cors.test.ts @@ -1,9 +1,9 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { Bucket, HttpMethods } from '../lib'; -export = { +nodeunitShim({ 'Can use addCors() to add a CORS configuration'(test: Test) { // GIVEN const stack = new Stack(); @@ -118,4 +118,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.expected.json b/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.expected.json index 74f803ae04cb0..37a7d24a40029 100644 --- a/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.expected.json +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.expected.json @@ -37,6 +37,34 @@ ] } }, + "VirtualHostedObjectURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Fn::GetAtt": ["MyBucketF68F3FF0", "RegionalDomainName"] + }, + "/myfolder/myfile.txt" + ] + ] + } + }, + "VirtualHostedObjectURLNonRegional": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Fn::GetAtt": ["MyBucketF68F3FF0", "DomainName"] + }, + "/myfolder/myfile.txt" + ] + ] + } + }, "S3ObjectURL": { "Value": { "Fn::Join": [ @@ -52,4 +80,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.ts b/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.ts index 34a282ac5dfb3..e3655bf6e1f94 100644 --- a/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.ts +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket.url.lit.ts @@ -12,6 +12,8 @@ class TestStack extends cdk.Stack { new cdk.CfnOutput(this, 'BucketURL', { value: bucket.bucketWebsiteUrl }); new cdk.CfnOutput(this, 'ObjectURL', { value: bucket.urlForObject('myfolder/myfile.txt') }); + new cdk.CfnOutput(this, 'VirtualHostedObjectURL', { value: bucket.virtualHostedUrlForObject('myfolder/myfile.txt') }); + new cdk.CfnOutput(this, 'VirtualHostedObjectURLNonRegional', { value: bucket.virtualHostedUrlForObject('myfolder/myfile.txt', { regional: false }) }); new cdk.CfnOutput(this, 'S3ObjectURL', { value: bucket.s3UrlForObject('myfolder/myfile.txt') }); /// !hide } diff --git a/packages/@aws-cdk/aws-s3/test/test.metrics.ts b/packages/@aws-cdk/aws-s3/test/metrics.test.ts similarity index 96% rename from packages/@aws-cdk/aws-s3/test/test.metrics.ts rename to packages/@aws-cdk/aws-s3/test/metrics.test.ts index da6a5b1c977ef..c82267afcadd1 100644 --- a/packages/@aws-cdk/aws-s3/test/test.metrics.ts +++ b/packages/@aws-cdk/aws-s3/test/metrics.test.ts @@ -1,9 +1,9 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { Bucket } from '../lib'; -export = { +nodeunitShim({ 'Can use addMetrics() to add a metric configuration'(test: Test) { // GIVEN const stack = new Stack(); @@ -109,4 +109,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-s3/test/test.notification.ts b/packages/@aws-cdk/aws-s3/test/notification.test.ts similarity index 98% rename from packages/@aws-cdk/aws-s3/test/test.notification.ts rename to packages/@aws-cdk/aws-s3/test/notification.test.ts index 1eb3708dc10c5..a96290643770d 100644 --- a/packages/@aws-cdk/aws-s3/test/test.notification.ts +++ b/packages/@aws-cdk/aws-s3/test/notification.test.ts @@ -1,9 +1,9 @@ import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as s3 from '../lib'; -export = { +nodeunitShim({ 'when notification is added a custom s3 bucket notification resource is provisioned'(test: Test) { const stack = new cdk.Stack(); @@ -133,4 +133,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-s3/test/test.rules.ts b/packages/@aws-cdk/aws-s3/test/rules.test.ts similarity index 97% rename from packages/@aws-cdk/aws-s3/test/test.rules.ts rename to packages/@aws-cdk/aws-s3/test/rules.test.ts index f8de0f1fa82f4..8e3a5ea5c3530 100644 --- a/packages/@aws-cdk/aws-s3/test/test.rules.ts +++ b/packages/@aws-cdk/aws-s3/test/rules.test.ts @@ -1,9 +1,9 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Duration, Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { Bucket, StorageClass } from '../lib'; -export = { +nodeunitShim({ 'Bucket with expiration days'(test: Test) { // GIVEN const stack = new Stack(); @@ -128,4 +128,4 @@ export = { test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-s3/test/test.util.ts b/packages/@aws-cdk/aws-s3/test/util.test.ts similarity index 96% rename from packages/@aws-cdk/aws-s3/test/test.util.ts rename to packages/@aws-cdk/aws-s3/test/util.test.ts index 96276f681d499..13a6376847ee9 100644 --- a/packages/@aws-cdk/aws-s3/test/test.util.ts +++ b/packages/@aws-cdk/aws-s3/test/util.test.ts @@ -1,8 +1,8 @@ import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { parseBucketArn, parseBucketName } from '../lib/util'; -export = { +nodeunitShim({ parseBucketArn: { 'explicit arn'(test: Test) { const stack = new cdk.Stack(); @@ -65,4 +65,4 @@ export = { test.done(); }, }, -}; +}); diff --git a/packages/@aws-cdk/aws-sagemaker/.npmignore b/packages/@aws-cdk/aws-sagemaker/.npmignore index 3ac7fc56cea02..142da412995b7 100644 --- a/packages/@aws-cdk/aws-sagemaker/.npmignore +++ b/packages/@aws-cdk/aws-sagemaker/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index efca2898af564..5919c20b0be5e 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SageMaker", diff --git a/packages/@aws-cdk/aws-sam/.npmignore b/packages/@aws-cdk/aws-sam/.npmignore index 3dffd1ce79a72..2892fc6e99416 100644 --- a/packages/@aws-cdk/aws-sam/.npmignore +++ b/packages/@aws-cdk/aws-sam/.npmignore @@ -27,4 +27,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index e5a2c9f67d57a..840669a6c7c50 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Serverless", @@ -76,7 +77,7 @@ "@types/jest": "^26.0.14", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "ts-jest": "^26.4.1" }, diff --git a/packages/@aws-cdk/aws-sdb/.npmignore b/packages/@aws-cdk/aws-sdb/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-sdb/.npmignore +++ b/packages/@aws-cdk/aws-sdb/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index da9bc2cd9ab13..b6b5c9328fb1e 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SDB", diff --git a/packages/@aws-cdk/aws-secretsmanager/.gitignore b/packages/@aws-cdk/aws-secretsmanager/.gitignore index 65d2efc186f05..1e0f697de30e5 100644 --- a/packages/@aws-cdk/aws-secretsmanager/.gitignore +++ b/packages/@aws-cdk/aws-secretsmanager/.gitignore @@ -12,5 +12,6 @@ coverage dist tsconfig.json !.eslintrc.js +!jest.config.js -junit.xml \ No newline at end of file +junit.xml diff --git a/packages/@aws-cdk/aws-secretsmanager/.npmignore b/packages/@aws-cdk/aws-secretsmanager/.npmignore index f937500da09a6..ff4c0cd3c03a2 100644 --- a/packages/@aws-cdk/aws-secretsmanager/.npmignore +++ b/packages/@aws-cdk/aws-secretsmanager/.npmignore @@ -22,7 +22,9 @@ dist tsconfig.json .eslintrc.js +jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index 4904814a1c4e9..286e83ea37b6b 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -69,7 +69,9 @@ then `Secret.grantRead` and `Secret.grantWrite` will also grant the role the relevant encrypt and decrypt permissions to the KMS key through the SecretsManager service principal. -### Rotating a Secret with a custom Lambda function +### Rotating a Secret + +#### Using a Custom Lambda Function A rotation schedule can be added to a Secret using a custom Lambda function: @@ -85,6 +87,31 @@ secret.addRotationSchedule('RotationSchedule', { See [Overview of the Lambda Rotation Function](https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html) on how to implement a Lambda Rotation Function. +#### Using a Hosted Lambda Function + +Use the `hostedRotation` prop to rotate a secret with a hosted Lambda function: + +```ts +const secret = new secretsmanager.Secret(this, 'Secret'); + +secret.addRotationSchedule('RotationSchedule', { + hostedRotation: secretsmanager.HostedRotation.mysqlSingleUser(), +}); +``` + +Hosted rotation is available for secrets representing credentials for MySQL, PostgreSQL, Oracle, +MariaDB, SQLServer, Redshift and MongoDB (both for the single and multi user schemes). + +When deployed in a VPC, the hosted rotation implements `ec2.IConnectable`: + +```ts +const myHostedRotation = secretsmanager.HostedRotation.mysqlSingleUser({ vpc: myVpc }); +secret.addRotationSchedule('RotationSchedule', { hostedRotation: myHostedRotation }); +dbConnections.allowDefaultPortFrom(hostedRotation); +``` + +See also [Automating secret creation in AWS CloudFormation](https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_cloudformation.html). + ### Rotating database credentials Define a `SecretRotation` to rotate database credentials: diff --git a/packages/@aws-cdk/aws-secretsmanager/jest.config.js b/packages/@aws-cdk/aws-secretsmanager/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts index 49d6170004e71..1243976963386 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts @@ -1,5 +1,6 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, Resource } from '@aws-cdk/core'; +import { Duration, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ISecret } from './secret'; import { CfnRotationSchedule } from './secretsmanager.generated'; @@ -9,9 +10,18 @@ import { CfnRotationSchedule } from './secretsmanager.generated'; */ export interface RotationScheduleOptions { /** - * The Lambda function that can rotate the secret. + * A Lambda function that can rotate the secret. + * + * @default - either `rotationLambda` or `hostedRotation` must be specified */ - readonly rotationLambda: lambda.IFunction; + readonly rotationLambda?: lambda.IFunction; + + /** + * Hosted rotation + * + * @default - either `rotationLambda` or `hostedRotation` must be specified + */ + readonly hostedRotation?: HostedRotation; /** * Specifies the number of days after the previous rotation before @@ -28,6 +38,23 @@ export interface RotationScheduleOptions { export interface RotationScheduleProps extends RotationScheduleOptions { /** * The secret to rotate. + * + * If hosted rotation is used, this must be a JSON string with the following format: + * + * ``` + * { + * "engine": , + * "host": , + * "username": , + * "password": , + * "dbname": , + * "port": , + * "masterarn": + * } + * ``` + * + * This is typically the case for a secret referenced from an `AWS::SecretsManager::SecretTargetAttachment` + * or an `ISecret` returned by the `attach()` method of `Secret`. */ readonly secret: ISecret; } @@ -39,12 +66,254 @@ export class RotationSchedule extends Resource { constructor(scope: Construct, id: string, props: RotationScheduleProps) { super(scope, id); + if ((!props.rotationLambda && !props.hostedRotation) || (props.rotationLambda && props.hostedRotation)) { + throw new Error('One of `rotationLambda` or `hostedRotation` must be specified.'); + } + new CfnRotationSchedule(this, 'Resource', { secretId: props.secret.secretArn, - rotationLambdaArn: props.rotationLambda.functionArn, + rotationLambdaArn: props.rotationLambda?.functionArn, + hostedRotationLambda: props.hostedRotation?.bind(props.secret, this), rotationRules: { automaticallyAfterDays: props.automaticallyAfter && props.automaticallyAfter.toDays() || 30, }, }); + + // Prevent secrets deletions when rotation is in place + props.secret.denyAccountRootDelete(); + } +} + +/** + * Single user hosted rotation options + */ +export interface SingleUserHostedRotationOptions { + /** + * A name for the Lambda created to rotate the secret + * + * @default - a CloudFormation generated name + */ + readonly functionName?: string; + + /** + * A list of security groups for the Lambda created to rotate the secret + * + * @default - a new security group is created + */ + readonly securityGroups?: ec2.ISecurityGroup[]; + + /** + * The VPC where the Lambda rotation function will run. + * + * @default - the Lambda is not deployed in a VPC + */ + readonly vpc?: ec2.IVpc; + + /** + * The type of subnets in the VPC where the Lambda rotation function will run. + * + * @default - the Vpc default strategy if not specified. + */ + readonly vpcSubnets?: ec2.SubnetSelection; +} + +/** + * Multi user hosted rotation options + */ +export interface MultiUserHostedRotationOptions extends SingleUserHostedRotationOptions { + /** + * The master secret for a multi user rotation scheme + */ + readonly masterSecret: ISecret; +} + +/** + * A hosted rotation + */ +export class HostedRotation implements ec2.IConnectable { + /** MySQL Single User */ + public static mysqlSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.MYSQL_SINGLE_USER, options); + } + + /** MySQL Multi User */ + public static mysqlMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.MYSQL_MULTI_USER, options, options.masterSecret); + } + + /** PostgreSQL Single User */ + public static postgreSqlSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.POSTGRESQL_SINGLE_USER, options); + } + + /** PostgreSQL Multi User */ + public static postgreSqlMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.POSTGRESQL_MULTI_USER, options, options.masterSecret); + } + + /** Oracle Single User */ + public static oracleSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.ORACLE_SINGLE_USER, options); + } + + /** Oracle Multi User */ + public static oracleMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.ORACLE_MULTI_USER, options, options.masterSecret); + } + + /** MariaDB Single User */ + public static mariaDbSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.MARIADB_SINGLE_USER, options); } + + /** MariaDB Multi User */ + public static mariaDbMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.MARIADB_MULTI_USER, options, options.masterSecret); + } + + /** SQL Server Single User */ + public static sqlServerSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.SQLSERVER_SINGLE_USER, options); + } + + /** SQL Server Multi User */ + public static sqlServerMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.SQLSERVER_MULTI_USER, options, options.masterSecret); + } + + /** Redshift Single User */ + public static redshiftSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.REDSHIFT_SINGLE_USER, options); + } + + /** Redshift Multi User */ + public static redshiftMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.REDSHIFT_MULTI_USER, options, options.masterSecret); + } + + /** MongoDB Single User */ + public static mongoDbSingleUser(options: SingleUserHostedRotationOptions = {}) { + return new HostedRotation(HostedRotationType.MONGODB_SINGLE_USER, options); + } + + /** MongoDB Multi User */ + public static mongoDbMultiUser(options: MultiUserHostedRotationOptions) { + return new HostedRotation(HostedRotationType.MONGODB_MULTI_USER, options, options.masterSecret); + } + + private _connections?: ec2.Connections; + + private constructor( + private readonly type: HostedRotationType, + private readonly props: SingleUserHostedRotationOptions | MultiUserHostedRotationOptions, + private readonly masterSecret?: ISecret, + ) { + if (type.isMultiUser && !masterSecret) { + throw new Error('The `masterSecret` must be specified when using the multi user scheme.'); + } + } + + /** + * Binds this hosted rotation to a secret + */ + public bind(secret: ISecret, scope: Construct): CfnRotationSchedule.HostedRotationLambdaProperty { + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html + Stack.of(scope).addTransform('AWS::SecretsManager-2020-07-23'); + + if (!this.props.vpc && this.props.securityGroups) { + throw new Error('`vpc` must be specified when specifying `securityGroups`.'); + } + + if (this.props.vpc) { + this._connections = new ec2.Connections({ + securityGroups: this.props.securityGroups || [new ec2.SecurityGroup(scope, 'SecurityGroup', { + vpc: this.props.vpc, + })], + }); + } + + // Prevent master secret deletion when rotation is in place + if (this.masterSecret) { + this.masterSecret.denyAccountRootDelete(); + } + + return { + rotationType: this.type.name, + kmsKeyArn: secret.encryptionKey?.keyArn, + masterSecretArn: this.masterSecret?.secretArn, + masterSecretKmsKeyArn: this.masterSecret?.encryptionKey?.keyArn, + rotationLambdaName: this.props.functionName, + vpcSecurityGroupIds: this._connections?.securityGroups?.map(s => s.securityGroupId).join(','), + vpcSubnetIds: this.props.vpc?.selectSubnets(this.props.vpcSubnets).subnetIds.join(','), + }; + } + + /** + * Security group connections for this hosted rotation + */ + public get connections() { + if (!this.props.vpc) { + throw new Error('Cannot use connections for a hosted rotation that is not deployed in a VPC'); + } + + // If we are in a vpc and bind() has been called _connections should be defined + if (!this._connections) { + throw new Error('Cannot use connections for a hosted rotation that has not been bound to a secret'); + } + + return this._connections; + } +} + +/** + * Hosted rotation type + */ +export class HostedRotationType { + /** MySQL Single User */ + public static readonly MYSQL_SINGLE_USER = new HostedRotationType('MySQLSingleUser'); + + /** MySQL Multi User */ + public static readonly MYSQL_MULTI_USER = new HostedRotationType('MySQLMultiUser', true); + + /** PostgreSQL Single User */ + public static readonly POSTGRESQL_SINGLE_USER = new HostedRotationType('PostgreSQLSingleUser'); + + /** PostgreSQL Multi User */ + public static readonly POSTGRESQL_MULTI_USER = new HostedRotationType('PostgreSQLMultiUser', true); + + /** Oracle Single User */ + public static readonly ORACLE_SINGLE_USER = new HostedRotationType('OracleSingleUser'); + + /** Oracle Multi User */ + public static readonly ORACLE_MULTI_USER = new HostedRotationType('OracleMultiUser', true); + + /** MariaDB Single User */ + public static readonly MARIADB_SINGLE_USER = new HostedRotationType('MariaDBSingleUser'); + + /** MariaDB Multi User */ + public static readonly MARIADB_MULTI_USER = new HostedRotationType('MariaDBMultiUser', true); + + /** SQL Server Single User */ + public static readonly SQLSERVER_SINGLE_USER = new HostedRotationType('SQLServerSingleUser') + + /** SQL Server Multi User */ + public static readonly SQLSERVER_MULTI_USER = new HostedRotationType('SQLServerMultiUser', true); + + /** Redshift Single User */ + public static readonly REDSHIFT_SINGLE_USER = new HostedRotationType('RedshiftSingleUser') + + /** Redshift Multi User */ + public static readonly REDSHIFT_MULTI_USER = new HostedRotationType('RedshiftMultiUser', true); + + /** MongoDB Single User */ + public static readonly MONGODB_SINGLE_USER = new HostedRotationType('MongoDBSingleUser'); + + /** MongoDB Multi User */ + public static readonly MONGODB_MULTI_USER = new HostedRotationType('MongoDBMultiUser', true); + + /** + * @param name The type of rotation + * @param isMultiUser Whether the rotation uses the mutli user scheme + */ + private constructor(public readonly name: string, public readonly isMultiUser?: boolean) {} } diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index cdd51ff5cedbb..388933895af51 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -136,6 +136,7 @@ export class SecretRotationApplication { export interface SecretRotationProps { /** * The secret to rotate. It must be a JSON string with the following format: + * * ``` * { * "engine": , @@ -148,8 +149,8 @@ export interface SecretRotationProps { * } * ``` * - * This is typically the case for a secret referenced from an - * AWS::SecretsManager::SecretTargetAttachment or an `ISecret` returned by the `attach()` method of `Secret`. + * This is typically the case for a secret referenced from an `AWS::SecretsManager::SecretTargetAttachment` + * or an `ISecret` returned by the `attach()` method of `Secret`. * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secrettargetattachment.html */ @@ -270,8 +271,7 @@ export class SecretRotation extends CoreConstruct { automaticallyAfter: props.automaticallyAfter, }); - // Prevent secrets deletions when rotation is in place - props.secret.denyAccountRootDelete(); + // Prevent master secret deletion when rotation is in place if (props.masterSecret) { props.masterSecret.denyAccountRootDelete(); } diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index 12af802e6694b..430f2b4a62980 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -50,10 +50,12 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SecretsManager", + "jest": true, "env": { "AWSLINT_BASE_CONSTRUCT": true } @@ -72,11 +74,9 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.expected.json new file mode 100644 index 0000000000000..be2f63be0aa79 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.expected.json @@ -0,0 +1,61 @@ +{ + "Transform": "AWS::SecretsManager-2020-07-23", + "Resources": { + "SecretA720EF05": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {} + } + }, + "SecretSchedule18F2CB66": { + "Type": "AWS::SecretsManager::RotationSchedule", + "Properties": { + "SecretId": { + "Ref": "SecretA720EF05" + }, + "HostedRotationLambda": { + "RotationType": "MySQLSingleUser" + }, + "RotationRules": { + "AutomaticallyAfterDays": 30 + } + } + }, + "SecretPolicy06C9821C": { + "Type": "AWS::SecretsManager::ResourcePolicy", + "Properties": { + "ResourcePolicy": { + "Statement": [ + { + "Action": "secretsmanager:DeleteSecret", + "Effect": "Deny", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "SecretId": { + "Ref": "SecretA720EF05" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.ts new file mode 100644 index 0000000000000..10109f91496cd --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.ts @@ -0,0 +1,18 @@ +import * as cdk from '@aws-cdk/core'; +import * as secretsmanager from '../lib'; + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const secret = new secretsmanager.Secret(this, 'Secret'); + + secret.addRotationSchedule('Schedule', { + hostedRotation: secretsmanager.HostedRotation.mysqlSingleUser(), + }); + } +} + +const app = new cdk.App(); +new TestStack(app, 'cdk-integ-secret-hosted-rotation'); +app.synth(); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/rotation-schedule.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/rotation-schedule.test.ts new file mode 100644 index 0000000000000..56eff9534a776 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/rotation-schedule.test.ts @@ -0,0 +1,347 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as secretsmanager from '../lib'; + +let stack: cdk.Stack; +beforeEach(() => { + stack = new cdk.Stack(); +}); + +test('create a rotation schedule with a rotation Lambda', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + const rotationLambda = new lambda.Function(stack, 'Lambda', { + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('export.handler = event => event;'), + handler: 'index.handler', + }); + + // WHEN + new secretsmanager.RotationSchedule(stack, 'RotationSchedule', { + secret, + rotationLambda, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'LambdaD247545B', + 'Arn', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); +}); + +describe('hosted rotation', () => { + test('single user not in a vpc', () => { + // GIVEN + const app = new cdk.App(); + stack = new cdk.Stack(app, 'TestStack'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + secret.addRotationSchedule('RotationSchedule', { + hostedRotation: secretsmanager.HostedRotation.mysqlSingleUser(), + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + HostedRotationLambda: { + RotationType: 'MySQLSingleUser', + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(app.synth().getStackByName(stack.stackName).template).toEqual(expect.objectContaining({ + Transform: 'AWS::SecretsManager-2020-07-23', + })); + + expect(stack).toHaveResource('AWS::SecretsManager::ResourcePolicy', { + ResourcePolicy: { + Statement: [ + { + Action: 'secretsmanager:DeleteSecret', + Effect: 'Deny', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + SecretId: { + Ref: 'SecretA720EF05', + }, + }); + }); + + test('multi user not in a vpc', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + const masterSecret = new secretsmanager.Secret(stack, 'MasterSecret'); + + // WHEN + secret.addRotationSchedule('RotationSchedule', { + hostedRotation: secretsmanager.HostedRotation.postgreSqlMultiUser({ + masterSecret, + }), + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + HostedRotationLambda: { + MasterSecretArn: { + Ref: 'MasterSecretA11BF785', + }, + RotationType: 'PostgreSQLMultiUser', + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResource('AWS::SecretsManager::ResourcePolicy', { + ResourcePolicy: { + Statement: [ + { + Action: 'secretsmanager:DeleteSecret', + Effect: 'Deny', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + SecretId: { + Ref: 'MasterSecretA11BF785', + }, + }); + }); + + test('single user in a vpc', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const dbSecurityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); + const dbConnections = new ec2.Connections({ + defaultPort: ec2.Port.tcp(3306), + securityGroups: [dbSecurityGroup], + }); + + // WHEN + const hostedRotation = secretsmanager.HostedRotation.mysqlSingleUser({ vpc }); + secret.addRotationSchedule('RotationSchedule', { hostedRotation }); + dbConnections.allowDefaultPortFrom(hostedRotation); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + HostedRotationLambda: { + RotationType: 'MySQLSingleUser', + VpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'SecretRotationScheduleSecurityGroup3F1F76EA', + 'GroupId', + ], + }, + VpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ',', + { + Ref: 'VpcPrivateSubnet2Subnet3788AAA1', + }, + ], + ], + }, + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { + FromPort: 3306, + GroupId: { + 'Fn::GetAtt': [ + 'SecurityGroupDD263621', + 'GroupId', + ], + }, + SourceSecurityGroupId: { + 'Fn::GetAtt': [ + 'SecretRotationScheduleSecurityGroup3F1F76EA', + 'GroupId', + ], + }, + ToPort: 3306, + }); + }); + + test('single user in a vpc with security groups', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const dbSecurityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); + const dbConnections = new ec2.Connections({ + defaultPort: ec2.Port.tcp(3306), + securityGroups: [dbSecurityGroup], + }); + + // WHEN + const hostedRotation = secretsmanager.HostedRotation.mysqlSingleUser({ + vpc, + securityGroups: [ + new ec2.SecurityGroup(stack, 'SG1', { vpc }), + new ec2.SecurityGroup(stack, 'SG2', { vpc }), + ], + }); + secret.addRotationSchedule('RotationSchedule', { hostedRotation }); + dbConnections.allowDefaultPortFrom(hostedRotation); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + HostedRotationLambda: { + RotationType: 'MySQLSingleUser', + VpcSecurityGroupIds: { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'SG1BA065B6E', + 'GroupId', + ], + }, + ',', + { + 'Fn::GetAtt': [ + 'SG20CE3219C', + 'GroupId', + ], + }, + ], + ], + }, + VpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ',', + { + Ref: 'VpcPrivateSubnet2Subnet3788AAA1', + }, + ], + ], + }, + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { + FromPort: 3306, + GroupId: { + 'Fn::GetAtt': [ + 'SecurityGroupDD263621', + 'GroupId', + ], + }, + SourceSecurityGroupId: { + 'Fn::GetAtt': [ + 'SG20CE3219C', + 'GroupId', + ], + }, + ToPort: 3306, + }); + }); + + test('throws with security groups and no vpc', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // THEN + expect(() => secret.addRotationSchedule('RotationSchedule', { + hostedRotation: secretsmanager.HostedRotation.oracleSingleUser({ + securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(secret, 'SG', 'sg-12345678')], + }), + })).toThrow(/`vpc` must be specified when specifying `securityGroups`/); + }); + + test('throws when accessing the connections object when not in a vpc', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + const hostedRotation = secretsmanager.HostedRotation.sqlServerSingleUser(); + secret.addRotationSchedule('RotationSchedule', { hostedRotation }); + + // THEN + expect(() => hostedRotation.connections.allowToAnyIpv4(ec2.Port.allTraffic())) + .toThrow(/Cannot use connections for a hosted rotation that is not deployed in a VPC/); + }); +}); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts new file mode 100644 index 0000000000000..f2b6b016dd582 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts @@ -0,0 +1,343 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as secretsmanager from '../lib'; + +let stack: cdk.Stack; +let vpc: ec2.IVpc; +let secret: secretsmanager.ISecret; +let securityGroup: ec2.SecurityGroup; +let target: ec2.Connections; +beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'VPC'); + secret = new secretsmanager.Secret(stack, 'Secret'); + securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); + target = new ec2.Connections({ + defaultPort: ec2.Port.tcp(3306), + securityGroups: [securityGroup], + }); +}); + + +test('secret rotation single user', () => { + // GIVEN + const excludeCharacters = ' ;+%{}' + '@\'"`/\\#'; // DMS and BASH problem chars + + // WHEN + new secretsmanager.SecretRotation(stack, 'SecretRotation', { + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secret, + target, + vpc, + excludeCharacters: excludeCharacters, + }); + + // THEN + expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { + IpProtocol: 'tcp', + Description: 'from SecretRotationSecurityGroupAEC520AB:3306', + FromPort: 3306, + GroupId: { + 'Fn::GetAtt': [ + 'SecurityGroupDD263621', + 'GroupId', + ], + }, + SourceSecurityGroupId: { + 'Fn::GetAtt': [ + 'SecretRotationSecurityGroup9985012B', + 'GroupId', + ], + }, + ToPort: 3306, + }); + + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'SecretRotationA9FFCFA9', + 'Outputs.RotationLambdaARN', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SecretRotation/SecurityGroup', + }); + + expect(stack).toHaveResource('AWS::Serverless::Application', { + Location: { + ApplicationId: 'arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser', + SemanticVersion: '1.1.60', + }, + Parameters: { + endpoint: { + 'Fn::Join': [ + '', + [ + 'https://secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + functionName: 'SecretRotation', + excludeCharacters: excludeCharacters, + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'SecretRotationSecurityGroup9985012B', + 'GroupId', + ], + }, + vpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + ',', + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + ], + }, + }, + }); + + expect(stack).toHaveResource('AWS::SecretsManager::ResourcePolicy', { + ResourcePolicy: { + Statement: [ + { + Action: 'secretsmanager:DeleteSecret', + Effect: 'Deny', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + SecretId: { + Ref: 'SecretA720EF05', + }, + }); +}); + +test('secret rotation multi user', () => { + // GIVEN + const masterSecret = new secretsmanager.Secret(stack, 'MasterSecret'); + + // WHEN + new secretsmanager.SecretRotation(stack, 'SecretRotation', { + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + secret, + masterSecret, + target, + vpc, + }); + + // THEN + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': [ + '', + [ + 'https://secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + functionName: 'SecretRotation', + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'SecretRotationSecurityGroup9985012B', + 'GroupId', + ], + }, + vpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + ',', + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + ], + }, + masterSecretArn: { + Ref: 'MasterSecretA11BF785', + }, + }, + }); + + expect(stack).toHaveResource('AWS::SecretsManager::ResourcePolicy', { + ResourcePolicy: { + Statement: [ + { + Action: 'secretsmanager:DeleteSecret', + Effect: 'Deny', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + SecretId: { + Ref: 'MasterSecretA11BF785', + }, + }); +}); + +test('secret rotation allows passing an empty string for excludeCharacters', () => { + // WHEN + new secretsmanager.SecretRotation(stack, 'SecretRotation', { + application: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, + secret, + target, + vpc, + excludeCharacters: '', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Parameters: { + excludeCharacters: '', + }, + }); +}); + +test('throws when connections object has no default port range', () => { + // WHEN + const targetWithoutDefaultPort = new ec2.Connections({ + securityGroups: [securityGroup], + }); + + // THEN + expect(() => new secretsmanager.SecretRotation(stack, 'Rotation', { + secret, + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + vpc, + target: targetWithoutDefaultPort, + })).toThrow(/`target`.+default port range/); +}); + +test('throws when master secret is missing for a multi user application', () => { + // THEN + expect(() => new secretsmanager.SecretRotation(stack, 'Rotation', { + secret, + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, + vpc, + target, + })).toThrow(/The `masterSecret` must be specified for application using the multi user scheme/); +}); + +test('rotation function name does not exceed 64 chars', () => { + // WHEN + const id = 'SecretRotation'.repeat(5); + new secretsmanager.SecretRotation(stack, id, { + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secret, + target, + vpc, + }); + + // THEN + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': [ + '', + [ + 'https://secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + functionName: 'RotationSecretRotationSecretRotationSecretRotationSecretRotation', + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'SecretRotationSecretRotationSecretRotationSecretRotationSecretRotationSecurityGroupBFCB171A', + 'GroupId', + ], + }, + vpcSubnetIds: { + 'Fn::Join': [ + '', + [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + ',', + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + ], + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts new file mode 100644 index 0000000000000..fab017f44e609 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -0,0 +1,713 @@ +import '@aws-cdk/assert/jest'; +import { ResourcePart } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as secretsmanager from '../lib'; + +let stack: cdk.Stack; +beforeEach(() => { + stack = new cdk.Stack(); +}); + +test('default secret', () => { + // WHEN + new secretsmanager.Secret(stack, 'Secret'); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: {}, + }); +}); + +test('set removalPolicy to secret', () => { + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', + { + DeletionPolicy: 'Retain', + }, ResourcePart.CompleteDefinition, + ); +}); + +test('secret with kms', () => { + // GIVEN + const key = new kms.Key(stack, 'KMS'); + + // WHEN + new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + + // THEN + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + { + Effect: 'Allow', + Resource: '*', + Action: [ + 'kms:Decrypt', + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + ], + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + }, + { + Effect: 'Allow', + Resource: '*', + Action: [ + 'kms:CreateGrant', + 'kms:DescribeKey', + ], + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('secret with generate secret string options', () => { + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + excludeUppercase: true, + passwordLength: 20, + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeUppercase: true, + PasswordLength: 20, + }, + }); +}); + +test('templated secret string', () => { + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }), + generateStringKey: 'password', + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + SecretStringTemplate: '{"username":"username"}', + GenerateStringKey: 'password', + }, + }); +}); + +test('grantRead', () => { + // GIVEN + const key = new kms.Key(stack, 'KMS'); + const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantRead(role); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + }], + }, + }); + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + {}, + {}, + { + Action: 'kms:Decrypt', + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grantRead with version label constraint', () => { + // GIVEN + const key = new kms.Key(stack, 'KMS'); + const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantRead(role, ['FOO', 'bar']); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + Condition: { + 'ForAnyValue:StringEquals': { + 'secretsmanager:VersionStage': ['FOO', 'bar'], + }, + }, + }], + }, + }); + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + {}, + {}, + { + Action: 'kms:Decrypt', + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('grantWrite', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret', {}); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantWrite(role); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + }], + }, + }); +}); + +test('grantWrite with kms', () => { + // GIVEN + const key = new kms.Key(stack, 'KMS'); + const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantWrite(role); + + // THEN + const expectStack = expect(stack); + expectStack.toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + }], + }, + }); + expectStack.toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + {}, + {}, + { + Action: [ + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + ], + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }, + Resource: '*', + }, + ], + }, + }); +}); + +test('secretValue', () => { + // GIVEN + const key = new kms.Key(stack, 'KMS'); + const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + + // WHEN + new cdk.CfnResource(stack, 'FakeResource', { + type: 'CDK::Phony::Resource', + properties: { + value: secret.secretValue, + }, + }); + + // THEN + expect(stack).toHaveResource('CDK::Phony::Resource', { + value: { + 'Fn::Join': ['', [ + '{{resolve:secretsmanager:', + { Ref: 'SecretA720EF05' }, + ':SecretString:::}}', + ]], + }, + }); +}); + +test('import by secretArn', () => { + // GIVEN + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretArn(stack, 'Secret', secretArn); + + // THEN + expect(secret.secretArn).toBe(secretArn); + expect(secret.secretName).toBe('MySecret'); + expect(secret.encryptionKey).toBeUndefined(); + expect(stack.resolve(secret.secretValue)).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); +}); + +test('import by secretArn throws if ARN is malformed', () => { + // GIVEN + const arnWithoutResourceName = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret'; + + // WHEN + expect(() => secretsmanager.Secret.fromSecretArn(stack, 'Secret1', arnWithoutResourceName)).toThrow(/invalid ARN format/); +}); + +test('import by secretArn supports secret ARNs without suffixes', () => { + // GIVEN + const arnWithoutSecretsManagerSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretArn(stack, 'Secret', arnWithoutSecretsManagerSuffix); + + // THEN + expect(secret.secretArn).toBe(arnWithoutSecretsManagerSuffix); + expect(secret.secretName).toBe('MySecret'); +}); + +test('import by secretArn supports tokens for ARNs', () => { + // GIVEN + const app = new cdk.App(); + const stackA = new cdk.Stack(app, 'StackA'); + const stackB = new cdk.Stack(app, 'StackB'); + const secretA = new secretsmanager.Secret(stackA, 'SecretA'); + + // WHEN + const secretB = secretsmanager.Secret.fromSecretArn(stackB, 'SecretB', secretA.secretArn); + new cdk.CfnOutput(stackB, 'secretBSecretName', { value: secretB.secretName }); + + // THEN + expect(secretB.secretArn).toBe(secretA.secretArn); + expect(stackB).toHaveOutput({ + outputName: 'secretBSecretName', + outputValue: { 'Fn::Select': [6, { 'Fn::Split': [':', { 'Fn::ImportValue': 'StackA:ExportsOutputRefSecretA188F281703FC8A52' }] }] }, + }); +}); + +test('import by attributes', () => { + // GIVEN + const encryptionKey = new kms.Key(stack, 'KMS'); + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretArn, encryptionKey, + }); + + // THEN + expect(secret.secretArn).toBe(secretArn); + expect(secret.secretName).toBe('MySecret'); + expect(secret.encryptionKey).toBe(encryptionKey); + expect(stack.resolve(secret.secretValue)).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); +}); + +test('import by secret name', () => { + // GIVEN + const secretName = 'MySecret'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretName(stack, 'Secret', secretName); + + // THEN + expect(secret.secretArn).toBe(secretName); + expect(secret.secretName).toBe(secretName); + expect(stack.resolve(secret.secretValue)).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:password::}}`); +}); + +test('import by secret name with grants', () => { + // GIVEN + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + const secret = secretsmanager.Secret.fromSecretName(stack, 'Secret', 'MySecret'); + + // WHEN + secret.grantRead(role); + secret.grantWrite(role); + + // THEN + const expectedSecretReference = { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':secret:MySecret*', + ]], + }; + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: expectedSecretReference, + }, + { + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: expectedSecretReference, + }], + }, + }); +}); + +test('can attach a secret with attach()', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + secret.attach({ + asSecretAttachmentTarget: () => ({ + targetId: 'target-id', + targetType: 'target-type' as secretsmanager.AttachmentTargetType, + }), + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::SecretTargetAttachment', { + SecretId: { + Ref: 'SecretA720EF05', + }, + TargetId: 'target-id', + TargetType: 'target-type', + }); +}); + +test('throws when trying to attach a target multiple times to a secret', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + const target = { + asSecretAttachmentTarget: () => ({ + targetId: 'target-id', + targetType: 'target-type' as secretsmanager.AttachmentTargetType, + }), + }; + secret.attach(target); + + // THEN + expect(() => secret.attach(target)).toThrow(/Secret is already attached to a target/); +}); + +test('add a rotation schedule to an attached secret', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + const attachedSecret = secret.attach({ + asSecretAttachmentTarget: () => ({ + targetId: 'target-id', + targetType: 'target-type' as secretsmanager.AttachmentTargetType, + }), + }); + const rotationLambda = new lambda.Function(stack, 'Lambda', { + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('export.handler = event => event;'), + handler: 'index.handler', + }); + + // WHEN + attachedSecret.addRotationSchedule('RotationSchedule', { + rotationLambda, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretAttachment2E1B7C3B', // The secret returned by the attachment, not the secret itself. + }, + }); +}); + +test('throws when specifying secretStringTemplate but not generateStringKey', () => { + expect(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }), + }, + })).toThrow(/`secretStringTemplate`.+`generateStringKey`/); +}); + +test('throws when specifying generateStringKey but not secretStringTemplate', () => { + expect(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + generateStringKey: 'password', + }, + })).toThrow(/`secretStringTemplate`.+`generateStringKey`/); +}); + +test('equivalence of SecretValue and Secret.fromSecretAttributes', () => { + // GIVEN + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + + // WHEN + const imported = secretsmanager.Secret.fromSecretAttributes(stack, 'Imported', { secretArn: secretArn }).secretValueFromJson('password'); + const value = cdk.SecretValue.secretsManager(secretArn, { jsonField: 'password' }); + + // THEN + expect(stack.resolve(imported)).toEqual(stack.resolve(value)); +}); + +test('can add to the resource policy of a secret', () => { + // GIVEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + secret.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['secretsmanager:GetSecretValue'], + resources: ['*'], + principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/cool-user')], + })); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::ResourcePolicy', { + ResourcePolicy: { + Statement: [ + { + Action: 'secretsmanager:GetSecretValue', + Effect: 'Allow', + Principal: { + AWS: 'arn:aws:iam::123456789012:user/cool-user', + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + SecretId: { + Ref: 'SecretA720EF05', + }, + }); +}); + +test('fails if secret policy has no actions', () => { + // GIVEN + const app = new cdk.App(); + stack = new cdk.Stack(app, 'my-stack'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + secret.addToResourcePolicy(new iam.PolicyStatement({ + resources: ['*'], + principals: [new iam.ArnPrincipal('arn')], + })); + + // THEN + expect(() => app.synth()).toThrow(/A PolicyStatement must specify at least one \'action\' or \'notAction\'/); +}); + +test('fails if secret policy has no IAM principals', () => { + // GIVEN + const app = new cdk.App(); + stack = new cdk.Stack(app, 'my-stack'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + secret.addToResourcePolicy(new iam.PolicyStatement({ + resources: ['*'], + actions: ['secretsmanager:*'], + })); + + // THEN + expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); +}); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts deleted file mode 100644 index ff2efc8ea05ae..0000000000000 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect, haveResource } from '@aws-cdk/assert'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as secretsmanager from '../lib'; - -export = { - 'create a rotation schedule'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const rotationLambda = new lambda.Function(stack, 'Lambda', { - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('export.handler = event => event;'), - handler: 'index.handler', - }); - - // WHEN - new secretsmanager.RotationSchedule(stack, 'RotationSchedule', { - secret, - rotationLambda, - }); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { - SecretId: { - Ref: 'SecretA720EF05', - }, - RotationLambdaARN: { - 'Fn::GetAtt': [ - 'LambdaD247545B', - 'Arn', - ], - }, - RotationRules: { - AutomaticallyAfterDays: 30, - }, - })); - - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts deleted file mode 100644 index 76ab7930d7dce..0000000000000 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-rotation.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as cdk from '@aws-cdk/core'; -import { ICallbackFunction, Test } from 'nodeunit'; -import * as secretsmanager from '../lib'; - -let stack: cdk.Stack; -let vpc: ec2.IVpc; -let secret: secretsmanager.ISecret; -let securityGroup: ec2.SecurityGroup; -let target: ec2.Connections; - -export = { - 'setUp'(cb: ICallbackFunction) { - stack = new cdk.Stack(); - vpc = new ec2.Vpc(stack, 'VPC'); - secret = new secretsmanager.Secret(stack, 'Secret'); - securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); - target = new ec2.Connections({ - defaultPort: ec2.Port.tcp(3306), - securityGroups: [securityGroup], - }); - - cb(); - }, - - 'secret rotation single user'(test: Test) { - // GIVEN - const excludeCharacters = ' ;+%{}' + '@\'"`/\\#'; // DMS and BASH problem chars - - // WHEN - new secretsmanager.SecretRotation(stack, 'SecretRotation', { - application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, - secret, - target, - vpc, - excludeCharacters: excludeCharacters, - }); - - // THEN - expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { - IpProtocol: 'tcp', - Description: 'from SecretRotationSecurityGroupAEC520AB:3306', - FromPort: 3306, - GroupId: { - 'Fn::GetAtt': [ - 'SecurityGroupDD263621', - 'GroupId', - ], - }, - SourceSecurityGroupId: { - 'Fn::GetAtt': [ - 'SecretRotationSecurityGroup9985012B', - 'GroupId', - ], - }, - ToPort: 3306, - })); - - expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { - SecretId: { - Ref: 'SecretA720EF05', - }, - RotationLambdaARN: { - 'Fn::GetAtt': [ - 'SecretRotationA9FFCFA9', - 'Outputs.RotationLambdaARN', - ], - }, - RotationRules: { - AutomaticallyAfterDays: 30, - }, - })); - - expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { - GroupDescription: 'Default/SecretRotation/SecurityGroup', - })); - - expect(stack).to(haveResource('AWS::Serverless::Application', { - Location: { - ApplicationId: 'arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser', - SemanticVersion: '1.1.60', - }, - Parameters: { - endpoint: { - 'Fn::Join': [ - '', - [ - 'https://secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, - functionName: 'SecretRotation', - excludeCharacters: excludeCharacters, - vpcSecurityGroupIds: { - 'Fn::GetAtt': [ - 'SecretRotationSecurityGroup9985012B', - 'GroupId', - ], - }, - vpcSubnetIds: { - 'Fn::Join': [ - '', - [ - { - Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', - }, - ',', - { - Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', - }, - ], - ], - }, - }, - })); - - expect(stack).to(haveResource('AWS::SecretsManager::ResourcePolicy', { - ResourcePolicy: { - Statement: [ - { - Action: 'secretsmanager:DeleteSecret', - Effect: 'Deny', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - SecretId: { - Ref: 'SecretA720EF05', - }, - })); - - test.done(); - }, - - 'secret rotation multi user'(test: Test) { - // GIVEN - const masterSecret = new secretsmanager.Secret(stack, 'MasterSecret'); - - // WHEN - new secretsmanager.SecretRotation(stack, 'SecretRotation', { - application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, - secret, - masterSecret, - target, - vpc, - }); - - // THEN - expect(stack).to(haveResource('AWS::Serverless::Application', { - Parameters: { - endpoint: { - 'Fn::Join': [ - '', - [ - 'https://secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, - functionName: 'SecretRotation', - vpcSecurityGroupIds: { - 'Fn::GetAtt': [ - 'SecretRotationSecurityGroup9985012B', - 'GroupId', - ], - }, - vpcSubnetIds: { - 'Fn::Join': [ - '', - [ - { - Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', - }, - ',', - { - Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', - }, - ], - ], - }, - masterSecretArn: { - Ref: 'MasterSecretA11BF785', - }, - }, - })); - - expect(stack).to(haveResource('AWS::SecretsManager::ResourcePolicy', { - ResourcePolicy: { - Statement: [ - { - Action: 'secretsmanager:DeleteSecret', - Effect: 'Deny', - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - SecretId: { - Ref: 'MasterSecretA11BF785', - }, - })); - - test.done(); - }, - - 'secret rotation allows passing an empty string for excludeCharacters'(test: Test) { - // WHEN - new secretsmanager.SecretRotation(stack, 'SecretRotation', { - application: secretsmanager.SecretRotationApplication.MARIADB_ROTATION_SINGLE_USER, - secret, - target, - vpc, - excludeCharacters: '', - }); - - // THEN - expect(stack).to(haveResourceLike('AWS::Serverless::Application', { - Parameters: { - excludeCharacters: '', - }, - })); - - test.done(); - }, - - 'throws when connections object has no default port range'(test: Test) { - // WHEN - const targetWithoutDefaultPort = new ec2.Connections({ - securityGroups: [securityGroup], - }); - - // THEN - test.throws(() => new secretsmanager.SecretRotation(stack, 'Rotation', { - secret, - application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, - vpc, - target: targetWithoutDefaultPort, - }), /`target`.+default port range/); - - test.done(); - }, - - 'throws when master secret is missing for a multi user application'(test: Test) { - // THEN - test.throws(() => new secretsmanager.SecretRotation(stack, 'Rotation', { - secret, - application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_MULTI_USER, - vpc, - target, - }), /The `masterSecret` must be specified for application using the multi user scheme/); - - test.done(); - }, - - 'rotation function name does not exceed 64 chars'(test: Test) { - // WHEN - const id = 'SecretRotation'.repeat(5); - new secretsmanager.SecretRotation(stack, id, { - application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, - secret, - target, - vpc, - }); - - // THEN - expect(stack).to(haveResource('AWS::Serverless::Application', { - Parameters: { - endpoint: { - 'Fn::Join': [ - '', - [ - 'https://secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, - functionName: 'RotationSecretRotationSecretRotationSecretRotationSecretRotation', - vpcSecurityGroupIds: { - 'Fn::GetAtt': [ - 'SecretRotationSecretRotationSecretRotationSecretRotationSecretRotationSecurityGroupBFCB171A', - 'GroupId', - ], - }, - vpcSubnetIds: { - 'Fn::Join': [ - '', - [ - { - Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', - }, - ',', - { - Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', - }, - ], - ], - }, - }, - })); - - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts deleted file mode 100644 index a7b4482597bca..0000000000000 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ /dev/null @@ -1,790 +0,0 @@ -import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; -import * as iam from '@aws-cdk/aws-iam'; -import * as kms from '@aws-cdk/aws-kms'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as secretsmanager from '../lib'; - -export = { - 'default secret'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new secretsmanager.Secret(stack, 'Secret'); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::Secret', { - GenerateSecretString: {}, - })); - - test.done(); - }, - - 'set removalPolicy to secret'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new secretsmanager.Secret(stack, 'Secret', { - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // THEN - expect(stack).to(haveResourceLike('AWS::SecretsManager::Secret', - { - DeletionPolicy: 'Retain', - }, ResourcePart.CompleteDefinition, - )); - - test.done(); - }, - - 'secret with kms'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - - // WHEN - new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - - // THEN - expect(stack).to(haveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - {}, - { - Effect: 'Allow', - Resource: '*', - Action: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - ], - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - }, - { - Effect: 'Allow', - Resource: '*', - Action: [ - 'kms:CreateGrant', - 'kms:DescribeKey', - ], - Principal: { - AWS: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::', - { - Ref: 'AWS::AccountId', - }, - ':root', - ], - ], - }, - }, - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - }, - ], - Version: '2012-10-17', - }, - })); - test.done(); - }, - - 'secret with generate secret string options'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new secretsmanager.Secret(stack, 'Secret', { - generateSecretString: { - excludeUppercase: true, - passwordLength: 20, - }, - }); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::Secret', { - GenerateSecretString: { - ExcludeUppercase: true, - PasswordLength: 20, - }, - })); - - test.done(); - }, - - 'templated secret string'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new secretsmanager.Secret(stack, 'Secret', { - generateSecretString: { - secretStringTemplate: JSON.stringify({ username: 'username' }), - generateStringKey: 'password', - }, - }); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::Secret', { - GenerateSecretString: { - SecretStringTemplate: '{"username":"username"}', - GenerateStringKey: 'password', - }, - })); - - test.done(); - }, - - 'grantRead'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantRead(role); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: [ - 'secretsmanager:GetSecretValue', - 'secretsmanager:DescribeSecret', - ], - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - }], - }, - })); - expect(stack).to(haveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - {}, - {}, - {}, - { - Action: 'kms:Decrypt', - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::GetAtt': [ - 'Role1ABCC5F0', - 'Arn', - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - test.done(); - }, - - 'grantRead with version label constraint'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantRead(role, ['FOO', 'bar']); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: [ - 'secretsmanager:GetSecretValue', - 'secretsmanager:DescribeSecret', - ], - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - Condition: { - 'ForAnyValue:StringEquals': { - 'secretsmanager:VersionStage': ['FOO', 'bar'], - }, - }, - }], - }, - })); - expect(stack).to(haveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - {}, - {}, - {}, - { - Action: 'kms:Decrypt', - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::GetAtt': [ - 'Role1ABCC5F0', - 'Arn', - ], - }, - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - })); - test.done(); - }, - - 'grantWrite'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret', {}); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantWrite(role); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: [ - 'secretsmanager:PutSecretValue', - 'secretsmanager:UpdateSecret', - ], - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - }], - }, - })); - test.done(); - }, - - 'grantWrite with kms'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantWrite(role); - - // THEN - const expectStack = expect(stack); - expectStack.to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: [ - 'secretsmanager:PutSecretValue', - 'secretsmanager:UpdateSecret', - ], - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - }], - }, - })); - expectStack.to(haveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - {}, - {}, - {}, - { - Action: [ - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - ], - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::GetAtt': [ - 'Role1ABCC5F0', - 'Arn', - ], - }, - }, - Resource: '*', - }, - ], - }, - })); - test.done(); - }, - - 'secretValue'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - - // WHEN - new cdk.CfnResource(stack, 'FakeResource', { - type: 'CDK::Phony::Resource', - properties: { - value: secret.secretValue, - }, - }); - - // THEN - expect(stack).to(haveResource('CDK::Phony::Resource', { - value: { - 'Fn::Join': ['', [ - '{{resolve:secretsmanager:', - { Ref: 'SecretA720EF05' }, - ':SecretString:::}}', - ]], - }, - })); - test.done(); - }, - - 'import by secretArn'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; - - // WHEN - const secret = secretsmanager.Secret.fromSecretArn(stack, 'Secret', secretArn); - - // THEN - test.equals(secret.secretArn, secretArn); - test.equals(secret.secretName, 'MySecret'); - test.same(secret.encryptionKey, undefined); - test.deepEqual(stack.resolve(secret.secretValue), `{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); - test.deepEqual(stack.resolve(secret.secretValueFromJson('password')), `{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); - test.done(); - }, - - 'import by secretArn throws if ARN is malformed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const arnWithoutResourceName = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret'; - - // WHEN - test.throws(() => secretsmanager.Secret.fromSecretArn(stack, 'Secret1', arnWithoutResourceName), /invalid ARN format/); - - test.done(); - }, - - 'import by secretArn supports secret ARNs without suffixes'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const arnWithoutSecretsManagerSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; - - // WHEN - const secret = secretsmanager.Secret.fromSecretArn(stack, 'Secret', arnWithoutSecretsManagerSuffix); - - // THEN - test.equals(secret.secretArn, arnWithoutSecretsManagerSuffix); - test.equals(secret.secretName, 'MySecret'); - - test.done(); - }, - - 'import by secretArn supports tokens for ARNs'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stackA = new cdk.Stack(app, 'StackA'); - const stackB = new cdk.Stack(app, 'StackB'); - const secretA = new secretsmanager.Secret(stackA, 'SecretA'); - - // WHEN - const secretB = secretsmanager.Secret.fromSecretArn(stackB, 'SecretB', secretA.secretArn); - new cdk.CfnOutput(stackB, 'secretBSecretName', { value: secretB.secretName }); - - // THEN - test.equals(secretB.secretArn, secretA.secretArn); - expect(stackB).toMatch({ - Outputs: { - secretBSecretName: { - Value: { 'Fn::Select': [6, { 'Fn::Split': [':', { 'Fn::ImportValue': 'StackA:ExportsOutputRefSecretA188F281703FC8A52' }] }] }, - }, - }, - }); - - test.done(); - }, - - 'import by attributes'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const encryptionKey = new kms.Key(stack, 'KMS'); - const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; - - // WHEN - const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { - secretArn, encryptionKey, - }); - - // THEN - test.equals(secret.secretArn, secretArn); - test.equals(secret.secretName, 'MySecret'); - test.same(secret.encryptionKey, encryptionKey); - test.deepEqual(stack.resolve(secret.secretValue), `{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); - test.deepEqual(stack.resolve(secret.secretValueFromJson('password')), `{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); - test.done(); - }, - - 'import by secret name'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secretName = 'MySecret'; - - // WHEN - const secret = secretsmanager.Secret.fromSecretName(stack, 'Secret', secretName); - - // THEN - test.equals(secret.secretArn, secretName); - test.equals(secret.secretName, secretName); - test.deepEqual(stack.resolve(secret.secretValue), `{{resolve:secretsmanager:${secretName}:SecretString:::}}`); - test.deepEqual(stack.resolve(secret.secretValueFromJson('password')), `{{resolve:secretsmanager:${secretName}:SecretString:password::}}`); - test.done(); - }, - - 'import by secret name with grants'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - const secret = secretsmanager.Secret.fromSecretName(stack, 'Secret', 'MySecret'); - - // WHEN - secret.grantRead(role); - secret.grantWrite(role); - - // THEN - const expectedSecretReference = { - 'Fn::Join': ['', [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':secretsmanager:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':secret:MySecret*', - ]], - }; - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: [ - 'secretsmanager:GetSecretValue', - 'secretsmanager:DescribeSecret', - ], - Effect: 'Allow', - Resource: expectedSecretReference, - }, - { - Action: [ - 'secretsmanager:PutSecretValue', - 'secretsmanager:UpdateSecret', - ], - Effect: 'Allow', - Resource: expectedSecretReference, - }], - }, - })); - - test.done(); - }, - - 'can attach a secret with attach()'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret'); - - // WHEN - secret.attach({ - asSecretAttachmentTarget: () => ({ - targetId: 'target-id', - targetType: 'target-type' as secretsmanager.AttachmentTargetType, - }), - }); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::SecretTargetAttachment', { - SecretId: { - Ref: 'SecretA720EF05', - }, - TargetId: 'target-id', - TargetType: 'target-type', - })); - - test.done(); - }, - - 'throws when trying to attach a target multiple times to a secret'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const target = { - asSecretAttachmentTarget: () => ({ - targetId: 'target-id', - targetType: 'target-type' as secretsmanager.AttachmentTargetType, - }), - }; - secret.attach(target); - - // THEN - test.throws(() => secret.attach(target), /Secret is already attached to a target/); - - test.done(); - }, - - 'add a rotation schedule to an attached secret'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret'); - const attachedSecret = secret.attach({ - asSecretAttachmentTarget: () => ({ - targetId: 'target-id', - targetType: 'target-type' as secretsmanager.AttachmentTargetType, - }), - }); - const rotationLambda = new lambda.Function(stack, 'Lambda', { - runtime: lambda.Runtime.NODEJS_10_X, - code: lambda.Code.fromInline('export.handler = event => event;'), - handler: 'index.handler', - }); - - // WHEN - attachedSecret.addRotationSchedule('RotationSchedule', { - rotationLambda, - }); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { - SecretId: { - Ref: 'SecretAttachment2E1B7C3B', // The secret returned by the attachment, not the secret itself. - }, - })); - - test.done(); - }, - - 'throws when specifying secretStringTemplate but not generateStringKey'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // THEN - test.throws(() => new secretsmanager.Secret(stack, 'Secret', { - generateSecretString: { - secretStringTemplate: JSON.stringify({ username: 'username' }), - }, - }), /`secretStringTemplate`.+`generateStringKey`/); - - test.done(); - }, - - 'throws when specifying generateStringKey but not secretStringTemplate'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // THEN - test.throws(() => new secretsmanager.Secret(stack, 'Secret', { - generateSecretString: { - generateStringKey: 'password', - }, - }), /`secretStringTemplate`.+`generateStringKey`/); - - test.done(); - }, - - 'equivalence of SecretValue and Secret.fromSecretAttributes'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; - - // WHEN - const imported = secretsmanager.Secret.fromSecretAttributes(stack, 'Imported', { secretArn: secretArn }).secretValueFromJson('password'); - const value = cdk.SecretValue.secretsManager(secretArn, { jsonField: 'password' }); - - // THEN - test.deepEqual(stack.resolve(imported), stack.resolve(value)); - test.done(); - }, - - 'can add to the resource policy of a secret'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret'); - - // WHEN - secret.addToResourcePolicy(new iam.PolicyStatement({ - actions: ['secretsmanager:GetSecretValue'], - resources: ['*'], - principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/cool-user')], - })); - - // THEN - expect(stack).to(haveResource('AWS::SecretsManager::ResourcePolicy', { - ResourcePolicy: { - Statement: [ - { - Action: 'secretsmanager:GetSecretValue', - Effect: 'Allow', - Principal: { - AWS: 'arn:aws:iam::123456789012:user/cool-user', - }, - Resource: '*', - }, - ], - Version: '2012-10-17', - }, - SecretId: { - Ref: 'SecretA720EF05', - }, - })); - - test.done(); - }, - - 'fails if secret policy has no actions'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'my-stack'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - - // WHEN - secret.addToResourcePolicy(new iam.PolicyStatement({ - resources: ['*'], - principals: [new iam.ArnPrincipal('arn')], - })); - - // THEN - test.throws(() => app.synth(), /A PolicyStatement must specify at least one \'action\' or \'notAction\'/); - test.done(); - }, - - 'fails if secret policy has no IAM principals'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'my-stack'); - const secret = new secretsmanager.Secret(stack, 'Secret'); - - // WHEN - secret.addToResourcePolicy(new iam.PolicyStatement({ - resources: ['*'], - actions: ['secretsmanager:*'], - })); - - // THEN - test.throws(() => app.synth(), /A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-securityhub/.npmignore b/packages/@aws-cdk/aws-securityhub/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-securityhub/.npmignore +++ b/packages/@aws-cdk/aws-securityhub/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-securityhub/package.json b/packages/@aws-cdk/aws-securityhub/package.json index cd4ff6bbc426d..a8cb19e6bf497 100644 --- a/packages/@aws-cdk/aws-securityhub/package.json +++ b/packages/@aws-cdk/aws-securityhub/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SecurityHub", diff --git a/packages/@aws-cdk/aws-servicecatalog/.npmignore b/packages/@aws-cdk/aws-servicecatalog/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-servicecatalog/.npmignore +++ b/packages/@aws-cdk/aws-servicecatalog/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index 1e93e64585ef2..e9689df96ec12 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ServiceCatalog", diff --git a/packages/@aws-cdk/aws-servicediscovery/.npmignore b/packages/@aws-cdk/aws-servicediscovery/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-servicediscovery/.npmignore +++ b/packages/@aws-cdk/aws-servicediscovery/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index 11be7c8fc3b3a..ba3c5463d4dba 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::ServiceDiscovery", diff --git a/packages/@aws-cdk/aws-ses-actions/.npmignore b/packages/@aws-cdk/aws-ses-actions/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-ses-actions/.npmignore +++ b/packages/@aws-cdk/aws-ses-actions/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses-actions/package.json b/packages/@aws-cdk/aws-ses-actions/package.json index 80842ef57969a..8ef2f699f5feb 100644 --- a/packages/@aws-cdk/aws-ses-actions/package.json +++ b/packages/@aws-cdk/aws-ses-actions/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-ses/.npmignore b/packages/@aws-cdk/aws-ses/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-ses/.npmignore +++ b/packages/@aws-cdk/aws-ses/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index 626397c3936eb..d20ba13ca5910 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SES", diff --git a/packages/@aws-cdk/aws-sns-subscriptions/.npmignore b/packages/@aws-cdk/aws-sns-subscriptions/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/.npmignore +++ b/packages/@aws-cdk/aws-sns-subscriptions/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/package.json b/packages/@aws-cdk/aws-sns-subscriptions/package.json index 44c9aa6963dff..aa22738a1b647 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/package.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-sns/.npmignore b/packages/@aws-cdk/aws-sns/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-sns/.npmignore +++ b/packages/@aws-cdk/aws-sns/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index b5baef7a3135a..c570761f59017 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -52,7 +52,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SNS", diff --git a/packages/@aws-cdk/aws-sqs/.npmignore b/packages/@aws-cdk/aws-sqs/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-sqs/.npmignore +++ b/packages/@aws-cdk/aws-sqs/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 68b8431b6a80a..4cc3cf6224ee5 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SQS", diff --git a/packages/@aws-cdk/aws-ssm/.npmignore b/packages/@aws-cdk/aws-ssm/.npmignore index 95a6e5fe5bb87..a94c531529866 100644 --- a/packages/@aws-cdk/aws-ssm/.npmignore +++ b/packages/@aws-cdk/aws-ssm/.npmignore @@ -22,4 +22,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 3d06a2eedbf03..986c258b57d5d 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SSM", diff --git a/packages/@aws-cdk/aws-sso/.npmignore b/packages/@aws-cdk/aws-sso/.npmignore index 72b7fcab0a22a..7b8fb69082a0f 100644 --- a/packages/@aws-cdk/aws-sso/.npmignore +++ b/packages/@aws-cdk/aws-sso/.npmignore @@ -24,3 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out junit.xml + +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sso/package.json b/packages/@aws-cdk/aws-sso/package.json index c8960a4309e39..cc6fa80e8a042 100644 --- a/packages/@aws-cdk/aws-sso/package.json +++ b/packages/@aws-cdk/aws-sso/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::SSO", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore index a0bf754e3cc79..c28149f1ec41d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore @@ -22,4 +22,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 860f8ec7d16ea..4e77e858735cf 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -69,7 +69,7 @@ "@aws-cdk/aws-sns-subscriptions": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json index 360eadea837cb..326a551cf89bd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json @@ -84,7 +84,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json index 57244344cbaed..06de00eb99d3f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.call-sagemaker.expected.json @@ -64,7 +64,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -227,7 +228,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { @@ -317,9 +319,9 @@ { "Ref": "CreateModelSagemakerRoleC2E07FC0" } - ] - } - }, + ] + } + }, "StateMachineRoleB840431D": { "Type": "AWS::IAM::Role", "Properties": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json index cf95e9f59a16e..fe1ebdecd9509 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/integ.create-training-job.expected.json @@ -64,7 +64,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Principal": { @@ -227,7 +228,8 @@ "Action": [ "kms:Encrypt", "kms:ReEncrypt*", - "kms:GenerateDataKey*" + "kms:GenerateDataKey*", + "kms:Decrypt" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-stepfunctions/.npmignore b/packages/@aws-cdk/aws-stepfunctions/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-stepfunctions/.npmignore +++ b/packages/@aws-cdk/aws-stepfunctions/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index a8d712400a2b5..206c6e8f0981d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -562,7 +562,7 @@ const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); new stepfunctions.StateMachine(stack, 'MyStateMachine', { definition: stepfunctions.Chain.start(new stepfunctions.Pass(stack, 'Pass')), logs: { - destinations: logGroup, + destination: logGroup, level: stepfunctions.LogLevel.ALL, } }); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 46ad90e48660d..52eecc773a3ad 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -363,7 +363,7 @@ export class StateMachine extends StateMachineBase { } /** - * Metric for the number of executions that succeeded + * Metric for the number of executions that timed out * * @default sum over 5 minutes */ @@ -418,7 +418,13 @@ export class StateMachine extends StateMachineBase { private buildTracingConfiguration(): CfnStateMachine.TracingConfigurationProperty { this.addToRolePolicy(new iam.PolicyStatement({ // https://docs.aws.amazon.com/xray/latest/devguide/security_iam_id-based-policy-examples.html#xray-permissions-resources - actions: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'], + // https://docs.aws.amazon.com/step-functions/latest/dg/xray-iam.html + actions: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + 'xray:GetSamplingRules', + 'xray:GetSamplingTargets', + ], resources: ['*'], })); diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index c37edc6fca586..832fe6c9df5e0 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::StepFunctions", diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts index 86475be650276..33353447abeec 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts @@ -144,6 +144,8 @@ describe('State Machine', () => { Action: [ 'xray:PutTraceSegments', 'xray:PutTelemetryRecords', + 'xray:GetSamplingRules', + 'xray:GetSamplingTargets', ], Effect: 'Allow', Resource: '*', diff --git a/packages/@aws-cdk/aws-synthetics/.npmignore b/packages/@aws-cdk/aws-synthetics/.npmignore index 548b39048e917..b3858c23d8230 100644 --- a/packages/@aws-cdk/aws-synthetics/.npmignore +++ b/packages/@aws-cdk/aws-synthetics/.npmignore @@ -23,4 +23,5 @@ tsconfig.json jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index 3c53ad9c2bfa8..bcd59674419eb 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Synthetics", diff --git a/packages/@aws-cdk/aws-timestream/.eslintrc.js b/packages/@aws-cdk/aws-timestream/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-timestream/.gitignore b/packages/@aws-cdk/aws-timestream/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js +junit.xml diff --git a/packages/@aws-cdk/aws-timestream/.npmignore b/packages/@aws-cdk/aws-timestream/.npmignore new file mode 100644 index 0000000000000..63ab95621c764 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/.npmignore @@ -0,0 +1,27 @@ +# 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 +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-timestream/LICENSE b/packages/@aws-cdk/aws-timestream/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/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-timestream/NOTICE b/packages/@aws-cdk/aws-timestream/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/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-timestream/README.md b/packages/@aws-cdk/aws-timestream/README.md new file mode 100644 index 0000000000000..231eeb65ba599 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/README.md @@ -0,0 +1,16 @@ +## AWS::Timestream Construct Library + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import timestream = require('@aws-cdk/aws-timestream'); +``` diff --git a/packages/@aws-cdk/aws-timestream/jest.config.js b/packages/@aws-cdk/aws-timestream/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-timestream/lib/index.ts b/packages/@aws-cdk/aws-timestream/lib/index.ts new file mode 100644 index 0000000000000..37d9f1ae9e1cd --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Timestream CloudFormation Resources: +export * from './timestream.generated'; diff --git a/packages/@aws-cdk/aws-timestream/package.json b/packages/@aws-cdk/aws-timestream/package.json new file mode 100644 index 0000000000000..57285b6625155 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/package.json @@ -0,0 +1,96 @@ +{ + "name": "@aws-cdk/aws-timestream", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Timestream", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Timestream", + "packageId": "Amazon.CDK.AWS.Timestream", + "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.timestream", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "timestream" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-timestream", + "module": "aws_cdk.aws_timestream" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-timestream" + }, + "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", + "gen": "cfn2ts" + }, + "cdk-build": { + "cloudformation": "AWS::Timestream", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Timestream", + "aws-timestream" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-timestream/test/timestream.test.ts b/packages/@aws-cdk/aws-timestream/test/timestream.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/test/timestream.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/aws-transfer/.npmignore b/packages/@aws-cdk/aws-transfer/.npmignore index 5c003cc4e3040..de98f3be1b6f4 100644 --- a/packages/@aws-cdk/aws-transfer/.npmignore +++ b/packages/@aws-cdk/aws-transfer/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-transfer/package.json b/packages/@aws-cdk/aws-transfer/package.json index 2b11c123c59cc..49c4a61f942d4 100644 --- a/packages/@aws-cdk/aws-transfer/package.json +++ b/packages/@aws-cdk/aws-transfer/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "build+test": "npm run build && npm test", "build+test+package": "npm run build+test && npm run package", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::Transfer", diff --git a/packages/@aws-cdk/aws-waf/.npmignore b/packages/@aws-cdk/aws-waf/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-waf/.npmignore +++ b/packages/@aws-cdk/aws-waf/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 03101648417b6..8bda60d5f2ef2 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WAF", diff --git a/packages/@aws-cdk/aws-wafregional/.npmignore b/packages/@aws-cdk/aws-wafregional/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-wafregional/.npmignore +++ b/packages/@aws-cdk/aws-wafregional/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index 3f1b6333fcfc2..fbd5fad56a5bd 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WAFRegional", diff --git a/packages/@aws-cdk/aws-wafv2/.npmignore b/packages/@aws-cdk/aws-wafv2/.npmignore index a7c5b49852b3b..c2827f80c26db 100644 --- a/packages/@aws-cdk/aws-wafv2/.npmignore +++ b/packages/@aws-cdk/aws-wafv2/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-wafv2/package.json b/packages/@aws-cdk/aws-wafv2/package.json index 3469018e07c3b..fa2e95eeeb743 100644 --- a/packages/@aws-cdk/aws-wafv2/package.json +++ b/packages/@aws-cdk/aws-wafv2/package.json @@ -50,7 +50,8 @@ "cfn2ts": "cfn2ts", "compat": "cdk-compat", "build+test": "npm run build && npm test", - "build+test+package": "npm run build+test && npm run package" + "build+test+package": "npm run build+test && npm run package", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WAFv2", diff --git a/packages/@aws-cdk/aws-workspaces/.npmignore b/packages/@aws-cdk/aws-workspaces/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/aws-workspaces/.npmignore +++ b/packages/@aws-cdk/aws-workspaces/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index 54198f3012d6f..500617ab61464 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -49,7 +49,8 @@ "cfn2ts": "cfn2ts", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::WorkSpaces", diff --git a/packages/@aws-cdk/cdk-assets-schema/.npmignore b/packages/@aws-cdk/cdk-assets-schema/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/cdk-assets-schema/.npmignore +++ b/packages/@aws-cdk/cdk-assets-schema/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index a2f6e7a88f7e8..2bae672bd0d70 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@types/jest": "^26.0.14", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "repository": { diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 6ef142cd6ce2b..80dcd61c194ee 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,82 @@ +# CloudFormation Resource Specification v18.7.0 + +## New Resource Types + +* AWS::CodeArtifact::Domain +* AWS::CodeArtifact::Repository +* AWS::Timestream::Database +* AWS::Timestream::Table + +## Attribute Changes + +* AWS::ImageBuilder::Component Name (__deleted__) +* AWS::ImageBuilder::DistributionConfiguration Name (__deleted__) +* AWS::ImageBuilder::ImagePipeline Name (__deleted__) +* AWS::ImageBuilder::ImageRecipe Name (__deleted__) +* AWS::ImageBuilder::InfrastructureConfiguration Name (__deleted__) + +## Property Changes + +* AWS::ImageBuilder::Component Name (__added__) +* AWS::ImageBuilder::DistributionConfiguration Name (__added__) +* AWS::ImageBuilder::ImagePipeline Name (__added__) +* AWS::ImageBuilder::ImageRecipe Name (__added__) +* AWS::ImageBuilder::InfrastructureConfiguration Name (__added__) +* AWS::KMS::Key KeyUsage.UpdateType (__changed__) + * Old: Immutable + * New: Mutable + +## Property Type Changes + + + +# CloudFormation Resource Specification v18.6.0 + +## New Resource Types + +* AWS::WorkSpaces::ConnectionAlias + +## Attribute Changes + +* AWS::ImageBuilder::Component Name (__added__) +* AWS::ImageBuilder::DistributionConfiguration Name (__added__) +* AWS::ImageBuilder::Image Name (__added__) +* AWS::ImageBuilder::ImagePipeline Name (__added__) +* AWS::ImageBuilder::ImageRecipe Name (__added__) +* AWS::ImageBuilder::InfrastructureConfiguration Name (__added__) + +## Property Changes + +* AWS::ApiGateway::DomainName DomainName.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::Config::ConformancePack DeliveryS3Bucket.Required (__changed__) + * Old: true + * New: false +* AWS::Config::OrganizationConformancePack DeliveryS3Bucket.Required (__changed__) + * Old: true + * New: false +* AWS::ImageBuilder::Component Name (__deleted__) +* AWS::ImageBuilder::DistributionConfiguration Name (__deleted__) +* AWS::ImageBuilder::ImagePipeline Name (__deleted__) +* AWS::ImageBuilder::ImageRecipe Name (__deleted__) +* AWS::ImageBuilder::InfrastructureConfiguration Name (__deleted__) +* AWS::Kendra::Faq FileFormat (__added__) +* AWS::StepFunctions::Activity Arn (__deleted__) +* AWS::StepFunctions::Activity Name (__added__) +* AWS::StepFunctions::Activity Tags.DuplicatesAllowed (__deleted__) + +## Property Type Changes + +* AWS::Backup::BackupPlan.AdvancedBackupSettingResourceType (__added__) +* AWS::Backup::BackupPlan.BackupPlanResourceType AdvancedBackupSettings (__added__) +* AWS::CloudFront::CachePolicy.ParametersInCacheKeyAndForwardedToOrigin EnableAcceptEncodingBrotli (__added__) +* AWS::CodeBuild::Project.ProjectTriggers BuildType (__added__) +* AWS::ECS::Service.NetworkConfiguration AwsVpcConfiguration (__deleted__) +* AWS::ECS::Service.NetworkConfiguration AwsvpcConfiguration (__added__) +* AWS::Synthetics::Canary.RunConfig ActiveTracing (__added__) + + # CloudFormation Resource Specification v18.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 09041b0634d19..2bf2b707b6a09 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -152,6 +152,7 @@ async function main() { 'build+test+package': 'npm run build+test && npm run package', 'build+test': 'npm run build && npm test', compat: 'cdk-compat', + gen: 'cfn2ts', }, 'cdk-build': { cloudformation: namespace, @@ -241,7 +242,11 @@ async function main() { '', '.eslintrc.js', 'jest.config.js', + '', + '# exclude cdk artifacts', + '**/cdk.out', 'junit.xml', + 'test/', ]); await write('lib/index.ts', [ diff --git a/packages/@aws-cdk/cfnspec/build-tools/update.sh b/packages/@aws-cdk/cfnspec/build-tools/update.sh index 12dacedc5f5c2..684e46b35af53 100755 --- a/packages/@aws-cdk/cfnspec/build-tools/update.sh +++ b/packages/@aws-cdk/cfnspec/build-tools/update.sh @@ -71,7 +71,7 @@ node ${scriptdir}/create-missing-libraries.js || { # update decdk dep list (cd ${scriptdir}/../../../decdk && node ./deps.js || true) -(cd ${scriptdir}/../../../monocdk-experiment && yarn gen || true) +(cd ${scriptdir}/../../../monocdk && yarn gen || true) # append old changelog after new and replace as the last step because otherwise we will not be idempotent _changelog_contents=$(cat CHANGELOG.md.new) diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 9020bd13e71ad..fb67e3d517039 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -18.5.0 +18.7.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index c62ff373855fd..a118251db1d89 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -6900,9 +6900,33 @@ } } }, + "AWS::Backup::BackupPlan.AdvancedBackupSettingResourceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-advancedbackupsettingresourcetype.html", + "Properties": { + "BackupOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-advancedbackupsettingresourcetype.html#cfn-backup-backupplan-advancedbackupsettingresourcetype-backupoptions", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Mutable" + }, + "ResourceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-advancedbackupsettingresourcetype.html#cfn-backup-backupplan-advancedbackupsettingresourcetype-resourcetype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Backup::BackupPlan.BackupPlanResourceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-backupplanresourcetype.html", "Properties": { + "AdvancedBackupSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-backupplanresourcetype.html#cfn-backup-backupplan-backupplanresourcetype-advancedbackupsettings", + "ItemType": "AdvancedBackupSettingResourceType", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "BackupPlanName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupplan-backupplanresourcetype.html#cfn-backup-backupplan-backupplanresourcetype-backupplanname", "PrimitiveType": "String", @@ -8181,6 +8205,12 @@ "Type": "CookiesConfig", "UpdateType": "Mutable" }, + "EnableAcceptEncodingBrotli": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-cachepolicy-parametersincachekeyandforwardedtoorigin.html#cfn-cloudfront-cachepolicy-parametersincachekeyandforwardedtoorigin-enableacceptencodingbrotli", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "EnableAcceptEncodingGzip": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-cachepolicy-parametersincachekeyandforwardedtoorigin.html#cfn-cloudfront-cachepolicy-parametersincachekeyandforwardedtoorigin-enableacceptencodinggzip", "PrimitiveType": "Boolean", @@ -9747,6 +9777,12 @@ "AWS::CodeBuild::Project.ProjectTriggers": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projecttriggers.html", "Properties": { + "BuildType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projecttriggers.html#cfn-codebuild-project-projecttriggers-buildtype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "FilterGroups": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projecttriggers.html#cfn-codebuild-project-projecttriggers-filtergroups", "ItemType": "FilterGroup", @@ -15335,7 +15371,7 @@ "AWS::ECS::Service.NetworkConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-networkconfiguration.html", "Properties": { - "AwsVpcConfiguration": { + "AwsvpcConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-networkconfiguration.html#cfn-ecs-service-networkconfiguration-awsvpcconfiguration", "Required": false, "Type": "AwsVpcConfiguration", @@ -39403,6 +39439,12 @@ "AWS::Synthetics::Canary.RunConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-runconfig.html", "Properties": { + "ActiveTracing": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-runconfig.html#cfn-synthetics-canary-runconfig-activetracing", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "MemoryInMB": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-runconfig.html#cfn-synthetics-canary-runconfig-memoryinmb", "PrimitiveType": "Integer", @@ -41505,6 +41547,35 @@ } } }, + "AWS::WorkSpaces::ConnectionAlias.ConnectionAliasAssociation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html", + "Properties": { + "AssociatedAccountId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html#cfn-workspaces-connectionalias-connectionaliasassociation-associatedaccountid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AssociationStatus": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html#cfn-workspaces-connectionalias-connectionaliasassociation-associationstatus", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ConnectionIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html#cfn-workspaces-connectionalias-connectionaliasassociation-connectionidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ResourceId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html#cfn-workspaces-connectionalias-connectionaliasassociation-resourceid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::WorkSpaces::Workspace.WorkspaceProperties": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-workspace-workspaceproperties.html", "Properties": { @@ -41627,7 +41698,7 @@ } } }, - "ResourceSpecificationVersion": "18.5.0", + "ResourceSpecificationVersion": "18.7.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -42569,7 +42640,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-domainname", "PrimitiveType": "String", "Required": false, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "EndpointConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-endpointconfiguration", @@ -47353,6 +47424,88 @@ } } }, + "AWS::CodeArtifact::Domain": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "EncryptionKey": { + "PrimitiveType": "String" + }, + "Name": { + "PrimitiveType": "String" + }, + "Owner": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-domain.html", + "Properties": { + "DomainName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-domain.html#cfn-codeartifact-domain-domainname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PermissionsPolicyDocument": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-domain.html#cfn-codeartifact-domain-permissionspolicydocument", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::CodeArtifact::Repository": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "DomainName": { + "PrimitiveType": "String" + }, + "DomainOwner": { + "PrimitiveType": "String" + }, + "Name": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html#cfn-codeartifact-repository-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ExternalConnections": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html#cfn-codeartifact-repository-externalconnections", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "PermissionsPolicyDocument": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html#cfn-codeartifact-repository-permissionspolicydocument", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "RepositoryName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html#cfn-codeartifact-repository-repositoryname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Upstreams": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html#cfn-codeartifact-repository-upstreams", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::CodeBuild::Project": { "Attributes": { "Arn": { @@ -48954,7 +49107,7 @@ "DeliveryS3Bucket": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html#cfn-config-conformancepack-deliverys3bucket", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "DeliveryS3KeyPrefix": { @@ -49055,7 +49208,7 @@ "DeliveryS3Bucket": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html#cfn-config-organizationconformancepack-deliverys3bucket", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "DeliveryS3KeyPrefix": { @@ -58064,6 +58217,9 @@ }, "ImageId": { "PrimitiveType": "String" + }, + "Name": { + "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html", @@ -59031,7 +59187,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-key.html#cfn-kms-key-keyusage", "PrimitiveType": "String", "Required": false, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "PendingWindowInDays": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-key.html#cfn-kms-key-pendingwindowindays", @@ -59127,6 +59283,12 @@ "Required": false, "UpdateType": "Immutable" }, + "FileFormat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kendra-faq.html#cfn-kendra-faq-fileformat", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "IndexId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kendra-faq.html#cfn-kendra-faq-indexid", "PrimitiveType": "String", @@ -67538,15 +67700,14 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html", "Properties": { - "Arn": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html#cfn-stepfunctions-activity-arn", + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html#cfn-stepfunctions-activity-name", "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" + "Required": true, + "UpdateType": "Immutable" }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html#cfn-stepfunctions-activity-tags", - "DuplicatesAllowed": true, "ItemType": "TagsEntry", "Required": false, "Type": "List", @@ -67710,6 +67871,70 @@ } } }, + "AWS::Timestream::Database": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-database.html", + "Properties": { + "DatabaseName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-database.html#cfn-timestream-database-databasename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-database.html#cfn-timestream-database-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-database.html#cfn-timestream-database-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Timestream::Table": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html", + "Properties": { + "DatabaseName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html#cfn-timestream-table-databasename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RetentionProperties": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html#cfn-timestream-table-retentionproperties", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "TableName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html#cfn-timestream-table-tablename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html#cfn-timestream-table-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::Transfer::Server": { "Attributes": { "Arn": { @@ -68466,6 +68691,37 @@ } } }, + "AWS::WorkSpaces::ConnectionAlias": { + "Attributes": { + "AliasId": { + "PrimitiveType": "String" + }, + "Associations": { + "ItemType": "ConnectionAliasAssociation", + "Type": "List" + }, + "ConnectionAliasState": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-workspaces-connectionalias.html", + "Properties": { + "ConnectionString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-workspaces-connectionalias.html#cfn-workspaces-connectionalias-connectionstring", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-workspaces-connectionalias.html#cfn-workspaces-connectionalias-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, "AWS::WorkSpaces::Workspace": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-workspaces-workspace.html", "Properties": { diff --git a/packages/@aws-cdk/cfnspec/spec-source/660_StepFunctions_Activity_patch.json b/packages/@aws-cdk/cfnspec/spec-source/660_StepFunctions_Activity_patch.json deleted file mode 100644 index 260d79e4398d6..0000000000000 --- a/packages/@aws-cdk/cfnspec/spec-source/660_StepFunctions_Activity_patch.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "ResourceTypes": { - "AWS::StepFunctions::Activity": { - "patch": { - "description": "The StepFunctions resource is using the CloudFormation Registry and this was an errant entry. It will be restored in the next versions of the spec but is incorrect in v18.5", - "operations": [ - { - "op": "remove", - "path": "/Properties/Arn" - }, - { - "op": "add", - "path": "/Properties/Name", - "value": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html#cfn-stepfunctions-activity-name", - "PrimitiveType": "String", - "Required": true, - "UpdateType": "Immutable" - } - } - ] - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/.npmignore b/packages/@aws-cdk/cloud-assembly-schema/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.npmignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 90c423d3cc36e..003e36566b529 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.14", "@types/mock-fs": "^4.10.0", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "mock-fs": "^4.13.0", "pkglint": "0.0.0", "typescript-json-schema": "^0.43.0" @@ -82,7 +82,7 @@ "exclude": [] }, "dependencies": { - "jsonschema": "^1.2.7", + "jsonschema": "^1.2.10", "semver": "^7.3.2" }, "awscdkio": { diff --git a/packages/@aws-cdk/cloudformation-diff/.npmignore b/packages/@aws-cdk/cloudformation-diff/.npmignore index 582a6ea324723..6f149ce45fddd 100644 --- a/packages/@aws-cdk/cloudformation-diff/.npmignore +++ b/packages/@aws-cdk/cloudformation-diff/.npmignore @@ -18,4 +18,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index a3c33a52eae91..3449a436cfd91 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -34,7 +34,7 @@ "@types/table": "^5.0.0", "cdk-build-tools": "0.0.0", "fast-check": "^2.4.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "ts-jest": "^26.4.1" }, diff --git a/packages/@aws-cdk/cloudformation-include/.npmignore b/packages/@aws-cdk/cloudformation-include/.npmignore index 5f0a7e632cc2b..89953b9a92f1b 100644 --- a/packages/@aws-cdk/cloudformation-include/.npmignore +++ b/packages/@aws-cdk/cloudformation-include/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 336f213dcbe3e..0e340761cb8a5 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -94,6 +94,7 @@ "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-cloudtrail": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-codeartifact": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", @@ -184,6 +185,7 @@ "@aws-cdk/aws-sso": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", + "@aws-cdk/aws-timestream": "0.0.0", "@aws-cdk/aws-transfer": "0.0.0", "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", @@ -222,6 +224,7 @@ "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-cloudtrail": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-codeartifact": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", @@ -312,6 +315,7 @@ "@aws-cdk/aws-sso": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", + "@aws-cdk/aws-timestream": "0.0.0", "@aws-cdk/aws-transfer": "0.0.0", "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", @@ -326,7 +330,7 @@ "@types/jest": "^26.0.14", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "ts-jest": "^26.4.1" }, diff --git a/packages/@aws-cdk/core/.npmignore b/packages/@aws-cdk/core/.npmignore index 90948e3dcecea..cab9567cc557d 100644 --- a/packages/@aws-cdk/core/.npmignore +++ b/packages/@aws-cdk/core/.npmignore @@ -26,4 +26,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out junit.xml -jest.config.js \ No newline at end of file +jest.config.js +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 70444124de25a..e6029a34d7f66 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import * as fs from 'fs-extra'; +import * as minimatch from 'minimatch'; import { AssetHashType, AssetOptions } from './assets'; import { BundlingOptions } from './bundling'; import { FileSystem, FingerprintOptions } from './fs'; @@ -107,6 +108,8 @@ export class AssetStaging extends CoreConstruct { private bundleDir?: string; + private readonly cacheKey: string; + constructor(scope: Construct, id: string, props: AssetStagingProps) { super(scope, id); @@ -126,7 +129,7 @@ export class AssetStaging extends CoreConstruct { // staged this asset (e.g. the same asset with the same configuration is used // in multiple stacks). In this case we can completely skip file system and // bundling operations. - const cacheKey = calculateCacheKey({ + this.cacheKey = calculateCacheKey({ sourcePath: path.resolve(props.sourcePath), bundling: props.bundling, assetHashType: hashType, @@ -137,10 +140,10 @@ export class AssetStaging extends CoreConstruct { if (props.bundling) { // Check if we actually have to bundle for this stack const bundlingStacks: string[] = this.node.tryGetContext(cxapi.BUNDLING_STACKS) ?? ['*']; - const runBundling = bundlingStacks.includes(Stack.of(this).stackName) || bundlingStacks.includes('*'); + const runBundling = !!bundlingStacks.find(pattern => minimatch(Stack.of(this).stackName, pattern)); if (runBundling) { const bundling = props.bundling; - this.assetHash = AssetStaging.getOrCalcAssetHash(cacheKey, () => { + this.assetHash = AssetStaging.getOrCalcAssetHash(this.cacheKey, () => { // Determine the source hash in advance of bundling if the asset hash type // is SOURCE so that the bundler can opt to re-use its previous output. const sourceHash = hashType === AssetHashType.SOURCE @@ -152,7 +155,7 @@ export class AssetStaging extends CoreConstruct { this.relativePath = renderAssetFilename(this.assetHash); this.stagedPath = this.relativePath; } else { // Bundling is skipped - this.assetHash = AssetStaging.getOrCalcAssetHash(cacheKey, () => { + this.assetHash = AssetStaging.getOrCalcAssetHash(this.cacheKey, () => { return props.assetHashType === AssetHashType.BUNDLE || props.assetHashType === AssetHashType.OUTPUT ? this.calculateHash(AssetHashType.CUSTOM, this.node.path) // Use node path as dummy hash because we're not bundling : this.calculateHash(hashType, props.assetHash); @@ -160,7 +163,7 @@ export class AssetStaging extends CoreConstruct { this.stagedPath = this.sourcePath; } } else { - this.assetHash = AssetStaging.getOrCalcAssetHash(cacheKey, () => this.calculateHash(hashType, props.assetHash)); + this.assetHash = AssetStaging.getOrCalcAssetHash(this.cacheKey, () => this.calculateHash(hashType, props.assetHash)); this.relativePath = renderAssetFilename(this.assetHash, path.extname(this.sourcePath)); this.stagedPath = this.relativePath; } @@ -239,18 +242,16 @@ export class AssetStaging extends CoreConstruct { // once before, so we'll give the caller nothing. return bundleDir; } - - fs.ensureDirSync(bundleDir); } else { // When the asset hash isn't known in advance, bundler outputs to an - // intermediate directory. - - // Create temp directory for bundling inside the temp staging directory - bundleDir = path.resolve(fs.mkdtempSync(path.join(outdir, 'bundling-temp-'))); - // Chmod the bundleDir to full access. - fs.chmodSync(bundleDir, 0o777); + // intermediate directory named after the asset's cache key + bundleDir = path.resolve(path.join(outdir, `bundling-temp-${this.cacheKey}`)); } + fs.ensureDirSync(bundleDir); + // Chmod the bundleDir to full access. + fs.chmodSync(bundleDir, 0o777); + let user: string; if (options.user) { user = options.user; @@ -280,7 +281,7 @@ export class AssetStaging extends CoreConstruct { localBundling = options.local?.tryBundle(bundleDir, options); if (!localBundling) { - options.image._run({ + options.image.run({ command: options.command, user, volumes, @@ -392,4 +393,3 @@ function sortObject(object: { [key: string]: any }): { [key: string]: any } { } return ret; } - diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 103d405c1bb2e..8326d8f55e3aa 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -110,6 +110,7 @@ export class BundlingDockerImage { const dockerArgs: string[] = [ 'build', '-q', + ...(options.file ? ['-f', options.file] : []), ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), path, ]; @@ -145,10 +146,8 @@ export class BundlingDockerImage { /** * Runs a Docker image - * - * @internal */ - public _run(options: DockerRunOptions = {}) { + public run(options: DockerRunOptions = {}) { const volumes = options.volumes || []; const environment = options.environment || {}; const command = options.command || []; @@ -175,6 +174,27 @@ export class BundlingDockerImage { ], }); } + + /** + * Copies a file or directory out of the Docker image to the local filesystem + */ + public cp(imagePath: string, outputPath: string) { + const { stdout } = dockerExec(['create', this.image]); + const match = stdout.toString().match(/([0-9a-f]{16,})/); + if (!match) { + throw new Error('Failed to extract container ID from Docker create output'); + } + + const containerId = match[1]; + const containerPath = `${containerId}:${imagePath}`; + try { + dockerExec(['cp', containerPath, outputPath]); + } catch (err) { + throw new Error(`Failed to copy files from ${containerPath} to ${outputPath}: ${err}`); + } finally { + dockerExec(['rm', '-v', containerId]); + } + } } /** @@ -221,7 +241,7 @@ export enum DockerVolumeConsistency { /** * Docker run options */ -interface DockerRunOptions { +export interface DockerRunOptions { /** * The command to run in the container. * @@ -268,6 +288,13 @@ export interface DockerBuildOptions { * @default - no build args */ readonly buildArgs?: { [key: string]: string }; + + /** + * Name of the Dockerfile + * + * @default - The Dockerfile immediately within the build context path + */ + readonly file?: string; } function flatten(x: string[][]) { diff --git a/packages/@aws-cdk/core/lib/cfn-codedeploy-blue-green-hook.ts b/packages/@aws-cdk/core/lib/cfn-codedeploy-blue-green-hook.ts index c0950afb258af..7ea09579e0b04 100644 --- a/packages/@aws-cdk/core/lib/cfn-codedeploy-blue-green-hook.ts +++ b/packages/@aws-cdk/core/lib/cfn-codedeploy-blue-green-hook.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { CfnHook } from './cfn-hook'; import { FromCloudFormationOptions } from './cfn-parse'; import { CfnResource } from './cfn-resource'; +import { undefinedIfAllValuesAreEmpty } from './util'; /** * The possible types of traffic shifting for the blue-green deployment configuration. @@ -487,27 +488,27 @@ export class CfnCodeDeployBlueGreenHook extends CfnHook { }, }, })), - TrafficRoutingConfig: { + TrafficRoutingConfig: undefinedIfAllValuesAreEmpty({ Type: this.trafficRoutingConfig?.type, - TimeBasedCanary: { + TimeBasedCanary: undefinedIfAllValuesAreEmpty({ StepPercentage: this.trafficRoutingConfig?.timeBasedCanary?.stepPercentage, BakeTimeMins: this.trafficRoutingConfig?.timeBasedCanary?.bakeTimeMins, - }, - TimeBasedLinear: { + }), + TimeBasedLinear: undefinedIfAllValuesAreEmpty({ StepPercentage: this.trafficRoutingConfig?.timeBasedLinear?.stepPercentage, BakeTimeMins: this.trafficRoutingConfig?.timeBasedLinear?.bakeTimeMins, - }, - }, - AdditionalOptions: { + }), + }), + AdditionalOptions: undefinedIfAllValuesAreEmpty({ TerminationWaitTimeInMinutes: this.additionalOptions?.terminationWaitTimeInMinutes, - }, - LifecycleEventHooks: { + }), + LifecycleEventHooks: undefinedIfAllValuesAreEmpty({ BeforeInstall: this.lifecycleEventHooks?.beforeInstall, AfterInstall: this.lifecycleEventHooks?.afterInstall, AfterAllowTestTraffic: this.lifecycleEventHooks?.afterAllowTestTraffic, BeforeAllowTraffic: this.lifecycleEventHooks?.beforeAllowTraffic, AfterAllowTraffic: this.lifecycleEventHooks?.afterAllowTraffic, - }, + }), }; } } diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 886a9228b3d0a..3013c7f757296 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -14,6 +14,7 @@ import { CfnReference, ReferenceRendering } from './private/cfn-reference'; import { IResolvable } from './resolvable'; import { Mapper, Validator } from './runtime'; import { isResolvableObject, Token } from './token'; +import { undefinedIfAllValuesAreEmpty } from './util'; /** * This class contains static methods called when going from @@ -707,7 +708,3 @@ export class CfnParser { return this.options.parameters || {}; } } - -function undefinedIfAllValuesAreEmpty(object: object): object | undefined { - return Object.values(object).some(v => v !== undefined) ? object : undefined; -} diff --git a/packages/@aws-cdk/core/lib/size.ts b/packages/@aws-cdk/core/lib/size.ts index 2cf445b16aab2..e8b9c66e35b72 100644 --- a/packages/@aws-cdk/core/lib/size.ts +++ b/packages/@aws-cdk/core/lib/size.ts @@ -12,6 +12,10 @@ export class Size { /** * Create a Storage representing an amount kibibytes. * 1 KiB = 1024 bytes + * + * @param amount the amount of kibibytes to be represented + * + * @returns a new `Size` instance */ public static kibibytes(amount: number): Size { return new Size(amount, StorageUnit.Kibibytes); @@ -20,6 +24,10 @@ export class Size { /** * Create a Storage representing an amount mebibytes. * 1 MiB = 1024 KiB + * + * @param amount the amount of mebibytes to be represented + * + * @returns a new `Size` instance */ public static mebibytes(amount: number): Size { return new Size(amount, StorageUnit.Mebibytes); @@ -28,6 +36,10 @@ export class Size { /** * Create a Storage representing an amount gibibytes. * 1 GiB = 1024 MiB + * + * @param amount the amount of gibibytes to be represented + * + * @returns a new `Size` instance */ public static gibibytes(amount: number): Size { return new Size(amount, StorageUnit.Gibibytes); @@ -36,6 +48,10 @@ export class Size { /** * Create a Storage representing an amount tebibytes. * 1 TiB = 1024 GiB + * + * @param amount the amount of tebibytes to be represented + * + * @returns a new `Size` instance */ public static tebibytes(amount: number): Size { return new Size(amount, StorageUnit.Tebibytes); @@ -44,8 +60,22 @@ export class Size { /** * Create a Storage representing an amount pebibytes. * 1 PiB = 1024 TiB + * + * @deprecated use `pebibytes` instead */ public static pebibyte(amount: number): Size { + return Size.pebibytes(amount); + } + + /** + * Create a Storage representing an amount pebibytes. + * 1 PiB = 1024 TiB + * + * @param amount the amount of pebibytes to be represented + * + * @returns a new `Size` instance + */ + public static pebibytes(amount: number): Size { return new Size(amount, StorageUnit.Pebibytes); } @@ -62,6 +92,10 @@ export class Size { /** * Return this storage as a total number of kibibytes. + * + * @param opts the conversion options + * + * @returns the quantity of bytes expressed in kibibytes */ public toKibibytes(opts: SizeConversionOptions = {}): number { return convert(this.amount, this.unit, StorageUnit.Kibibytes, opts); @@ -69,6 +103,10 @@ export class Size { /** * Return this storage as a total number of mebibytes. + * + * @param opts the conversion options + * + * @returns the quantity of bytes expressed in mebibytes */ public toMebibytes(opts: SizeConversionOptions = {}): number { return convert(this.amount, this.unit, StorageUnit.Mebibytes, opts); @@ -76,6 +114,10 @@ export class Size { /** * Return this storage as a total number of gibibytes. + * + * @param opts the conversion options + * + * @returns the quantity of bytes expressed in gibibytes */ public toGibibytes(opts: SizeConversionOptions = {}): number { return convert(this.amount, this.unit, StorageUnit.Gibibytes, opts); @@ -83,6 +125,10 @@ export class Size { /** * Return this storage as a total number of tebibytes. + * + * @param opts the conversion options + * + * @returns the quantity of bytes expressed in tebibytes */ public toTebibytes(opts: SizeConversionOptions = {}): number { return convert(this.amount, this.unit, StorageUnit.Tebibytes, opts); @@ -90,6 +136,10 @@ export class Size { /** * Return this storage as a total number of pebibytes. + * + * @param opts the conversion options + * + * @returns the quantity of bytes expressed in pebibytes */ public toPebibytes(opts: SizeConversionOptions = {}): number { return convert(this.amount, this.unit, StorageUnit.Pebibytes, opts); @@ -106,7 +156,6 @@ export enum SizeRoundingBehavior { FLOOR, /** Don't round. Return even if the result is a fraction. */ NONE, - } /** diff --git a/packages/@aws-cdk/core/lib/util.ts b/packages/@aws-cdk/core/lib/util.ts index e626600a206cd..b36e1b1339e82 100644 --- a/packages/@aws-cdk/core/lib/util.ts +++ b/packages/@aws-cdk/core/lib/util.ts @@ -119,4 +119,8 @@ export function findLastCommonElement(path1: T[], path2: T[]): T | undefined } return path1[i - 1]; -} \ No newline at end of file +} + +export function undefinedIfAllValuesAreEmpty(object: object): object | undefined { + return Object.values(object).some(v => v !== undefined) ? object : undefined; +} diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 9731f371c38d6..bc083b052f98b 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -134,14 +134,17 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cfn2ts" }, "cdk-build": { "cloudformation": "AWS::CloudFormation", "cfn2ts-core-import": ".", "jest": true, "pre": [ - "rm -rf test/fs/fixtures && cd test/fs && tar -xzf fixtures.tar.gz" + "rm -rf test/fs/fixtures", + "cd test/fs", + "tar -xzf fixtures.tar.gz" ], "env": { "AWSLINT_BASE_CONSTRUCT": "true" @@ -167,7 +170,7 @@ "devDependencies": { "@types/lodash": "^4.14.161", "@types/minimatch": "^3.0.3", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "@types/sinon": "^9.0.7", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index 411ddc0f1cb92..a33ee4a96b087 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -1,4 +1,5 @@ import * as child_process from 'child_process'; +import * as path from 'path'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as sinon from 'sinon'; import { BundlingDockerImage, FileSystem } from '../lib'; @@ -20,7 +21,7 @@ nodeunitShim({ }); const image = BundlingDockerImage.fromRegistry('alpine'); - image._run({ + image.run({ command: ['cool', 'command'], environment: { VAR1: 'value1', @@ -64,7 +65,7 @@ nodeunitShim({ TEST_ARG: 'cdk-test', }, }); - image._run(); + image.run(); test.ok(spawnSyncStub.firstCall.calledWith('docker', [ 'build', '-q', @@ -105,7 +106,7 @@ nodeunitShim({ }); const image = BundlingDockerImage.fromRegistry('alpine'); - test.throws(() => image._run(), /UnknownError/); + test.throws(() => image.run(), /UnknownError/); test.done(); }, @@ -120,7 +121,7 @@ nodeunitShim({ }); const image = BundlingDockerImage.fromRegistry('alpine'); - test.throws(() => image._run(), /\[Status -1\]/); + test.throws(() => image.run(), /\[Status -1\]/); test.done(); }, @@ -152,4 +153,79 @@ nodeunitShim({ test.ok(fingerprintStub.calledWith('docker-path', sinon.match({ extraHash: JSON.stringify({}) }))); test.done(); }, + + 'custom dockerfile is passed through to docker exec'(test: Test) { + const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from('sha256:1234567890abcdef'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + BundlingDockerImage.fromAsset(path.join(__dirname, 'fs/fixtures/test1'), { + file: 'my-dockerfile', + }); + + test.ok(spawnSyncStub.calledOnce); + test.ok(/-f my-dockerfile/.test(spawnSyncStub.firstCall.args[1]?.join(' ') ?? '')); + + test.done(); + }, + + 'cp utility copies from an image'(test: Test) { + // GIVEN + const containerId = '1234567890abcdef1234567890abcdef'; + const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from(`${containerId}\n`), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + // WHEN + BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + + // THEN + test.ok(spawnSyncStub.calledWith(sinon.match.any, ['create', 'alpine'], sinon.match.any)); + test.ok(spawnSyncStub.calledWith(sinon.match.any, ['cp', `${containerId}:/foo/bar`, '/baz'], sinon.match.any)); + test.ok(spawnSyncStub.calledWith(sinon.match.any, ['rm', '-v', containerId])); + + test.done(); + }, + + 'cp utility cleans up after itself'(test: Test) { + // GIVEN + const containerId = '1234567890abcdef1234567890abcdef'; + const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from(`${containerId}\n`), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + spawnSyncStub.withArgs(sinon.match.any, sinon.match.array.startsWith(['cp']), sinon.match.any) + .returns({ + status: 1, + stderr: Buffer.from('it failed for a very good reason'), + stdout: Buffer.from('stdout'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + // WHEN + test.throws(() => { + BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + }, /Failed.*copy/i); + + // THEN + test.ok(spawnSyncStub.calledWith(sinon.match.any, ['rm', '-v', containerId])); + test.done(); + }, }); diff --git a/packages/@aws-cdk/core/test/cfn-codedeploy-blue-green-hook.test.ts b/packages/@aws-cdk/core/test/cfn-codedeploy-blue-green-hook.test.ts new file mode 100644 index 0000000000000..b441e70f18728 --- /dev/null +++ b/packages/@aws-cdk/core/test/cfn-codedeploy-blue-green-hook.test.ts @@ -0,0 +1,93 @@ +import { CfnCodeDeployBlueGreenHook, CfnTrafficRoutingType, Stack } from '../lib'; +import { toCloudFormation } from './util'; + +describe('CodeDeploy blue-green deployment Hook', () => { + test('only renders the provided properties', () => { + const stack = new Stack(); + new CfnCodeDeployBlueGreenHook(stack, 'MyHook', { + trafficRoutingConfig: { + type: CfnTrafficRoutingType.TIME_BASED_LINEAR, + timeBasedLinear: { + bakeTimeMins: 15, + }, + }, + applications: [ + { + ecsAttributes: { + trafficRouting: { + targetGroups: ['blue-target-group', 'green-target-group'], + testTrafficRoute: { + logicalId: 'logicalId1', + type: 'AWS::ElasticLoadBalancingV2::Listener', + }, + prodTrafficRoute: { + logicalId: 'logicalId2', + type: 'AWS::ElasticLoadBalancingV2::Listener', + }, + }, + taskSets: ['blue-task-set', 'green-task-set'], + taskDefinitions: ['blue-task-def', 'green-task-def'], + }, + target: { + logicalId: 'logicalId', + type: 'AWS::ECS::Service', + }, + }, + ], + serviceRole: 'my-service-role', + }); + + const template = toCloudFormation(stack); + expect(template).toStrictEqual({ + Hooks: { + MyHook: { + Type: 'AWS::CodeDeploy::BlueGreen', + Properties: { + // no empty AdditionalOptions object present + // no empty LifecycleEventHooks object present + TrafficRoutingConfig: { + // no empty TimeBasedCanary object present + Type: 'TimeBasedLinear', + TimeBasedLinear: { + BakeTimeMins: 15, + }, + }, + Applications: [ + { + ECSAttributes: { + TaskDefinitions: [ + 'blue-task-def', + 'green-task-def', + ], + TaskSets: [ + 'blue-task-set', + 'green-task-set', + ], + TrafficRouting: { + TargetGroups: [ + 'blue-target-group', + 'green-target-group', + ], + ProdTrafficRoute: { + LogicalID: 'logicalId2', + Type: 'AWS::ElasticLoadBalancingV2::Listener', + }, + TestTrafficRoute: { + LogicalID: 'logicalId1', + Type: 'AWS::ElasticLoadBalancingV2::Listener', + }, + }, + }, + Target: { + LogicalID: 'logicalId', + Type: 'AWS::ECS::Service', + }, + }, + ], + ServiceRole: 'my-service-role', + }, + }, + }, + }); + }); +}); diff --git a/packages/@aws-cdk/core/test/size.test.ts b/packages/@aws-cdk/core/test/size.test.ts index e6e9534039b90..288db78304f2a 100644 --- a/packages/@aws-cdk/core/test/size.test.ts +++ b/packages/@aws-cdk/core/test/size.test.ts @@ -81,8 +81,8 @@ nodeunitShim({ test.done(); }, - 'Size in pebibyte'(test: Test) { - const size = Size.pebibyte(5); + 'Size in pebibytes'(test: Test) { + const size = Size.pebibytes(5); test.equal(size.toKibibytes(), 5_497_558_138_880); test.equal(size.toMebibytes(), 5_368_709_120); diff --git a/packages/@aws-cdk/core/test/staging.test.ts b/packages/@aws-cdk/core/test/staging.test.ts index a40e67edbfd96..5a6d144fea1de 100644 --- a/packages/@aws-cdk/core/test/staging.test.ts +++ b/packages/@aws-cdk/core/test/staging.test.ts @@ -326,7 +326,7 @@ nodeunitShim({ const app = new App(); const stack = new Stack(app, 'stack'); const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); - const mkdtempSyncSpy = sinon.spy(fs, 'mkdtempSync'); + const ensureDirSync = sinon.spy(fs, 'ensureDirSync'); const chmodSyncSpy = sinon.spy(fs, 'chmodSync'); const renameSyncSpy = sinon.spy(fs, 'renameSync'); @@ -343,7 +343,7 @@ nodeunitShim({ // THEN const assembly = app.synth(); - test.ok(mkdtempSyncSpy.calledWith(sinon.match(path.join(assembly.directory, 'bundling-temp-')))); + test.ok(ensureDirSync.calledWith(sinon.match(path.join(assembly.directory, 'bundling-temp-')))); test.ok(chmodSyncSpy.calledWith(sinon.match(path.join(assembly.directory, 'bundling-temp-')), 0o777)); test.ok(renameSyncSpy.calledWith(sinon.match(path.join(assembly.directory, 'bundling-temp-')), sinon.match(path.join(assembly.directory, 'asset.')))); @@ -706,6 +706,58 @@ nodeunitShim({ test.done(); }, + + 'bundling still occurs with partial wildcard'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + stack.node.setContext(cxapi.BUNDLING_STACKS, ['*Stack']); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const asset = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.BUNDLE, + bundling: { + image: BundlingDockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input:delegated -v /output:/asset-output:delegated -w /asset-input alpine DOCKER_STUB_SUCCESS`, + ); + test.equal(asset.assetHash, '33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); // hash of MyStack/Asset + + test.done(); + }, + + 'bundling still occurs with full wildcard'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + stack.node.setContext(cxapi.BUNDLING_STACKS, ['*']); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const asset = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + assetHashType: AssetHashType.BUNDLE, + bundling: { + image: BundlingDockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SUCCESS], + }, + }); + + test.equal( + readDockerStubInput(), + `run --rm ${USER_ARG} -v /input:/asset-input:delegated -v /output:/asset-output:delegated -w /asset-input alpine DOCKER_STUB_SUCCESS`, + ); + test.equal(asset.assetHash, '33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); // hash of MyStack/Asset + + test.done(); + }, }); // Reads a docker stub and cleans the volume paths out of the stub. diff --git a/packages/@aws-cdk/custom-resources/.npmignore b/packages/@aws-cdk/custom-resources/.npmignore index 3b641cc6c559c..fcd1908ac39cb 100644 --- a/packages/@aws-cdk/custom-resources/.npmignore +++ b/packages/@aws-cdk/custom-resources/.npmignore @@ -24,4 +24,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 1f704be83dd4f..280f69bc77ade 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -48,12 +48,10 @@ "awslint": "cdk-awslint", "build+test+package": "npm run build+test && npm run package", "build+test": "npm run build && npm test", - "compat": "cdk-compat" + "compat": "cdk-compat", + "gen": "cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/aws-custom-resource/sdk-api-metadata.json && rm -rf test/aws-custom-resource/cdk.out" }, "cdk-build": { - "pre": [ - "cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/aws-custom-resource/sdk-api-metadata.json && rm -rf test/aws-custom-resource/cdk.out" - ], "jest": true, "env": { "AWSLINT_BASE_CONSTRUCT": true diff --git a/packages/@aws-cdk/cx-api/.npmignore b/packages/@aws-cdk/cx-api/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/cx-api/.npmignore +++ b/packages/@aws-cdk/cx-api/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 4f4bc15e84a3a..abd8a47463b29 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -62,7 +62,7 @@ "@types/mock-fs": "^4.10.0", "@types/semver": "^7.3.4", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "mock-fs": "^4.13.0", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/example-construct-library/package.json b/packages/@aws-cdk/example-construct-library/package.json index 1fcfaf962ed3c..a15bb0cdd6284 100644 --- a/packages/@aws-cdk/example-construct-library/package.json +++ b/packages/@aws-cdk/example-construct-library/package.json @@ -68,7 +68,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/pipelines/.npmignore b/packages/@aws-cdk/pipelines/.npmignore index 43090522285b1..8b1d5e48f3c78 100644 --- a/packages/@aws-cdk/pipelines/.npmignore +++ b/packages/@aws-cdk/pipelines/.npmignore @@ -25,3 +25,4 @@ tsconfig.json jest.config.js junit.xml package/node_modules +test/ diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index a2c72b4c03bb9..b575fcd00732d 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -149,6 +149,7 @@ class MyPipelineStack extends Stack { // Replace these with your actual GitHub project name owner: 'OWNER', repo: 'REPO', + branch: 'main', // default: 'master' }), synthAction: SimpleSynthAction.standardNpmSynth({ @@ -299,6 +300,12 @@ pipeline.addApplicationStage(new MyApplication(this, 'Production', { })); ``` +> Be aware that adding new stages via `addApplicationStage()` will +> automatically add them to the pipeline and deploy the new stacks, but +> *removing* them from the pipeline or deleting the pipeline stack will not +> automatically delete deployed application stacks. You must delete those +> stacks by hand using the AWS CloudFormation console or the AWS CLI. + ### More Control Every *Application Stage* added by `addApplicationStage()` will lead to the addition of @@ -353,6 +360,8 @@ stage.addActions(new ShellScriptAction({ commands: ['curl -Ssf https://my.webservice.com/'], // Optionally specify a VPC if, for example, the service is deployed with a private load balancer vpc, + // Optionally specify SecurityGroups + securityGroups, // ... more configuration ... })); ``` @@ -535,6 +544,24 @@ class MyPipelineStack extends Stack { } ``` +### Developing the pipeline + +The self-mutation feature of the `CdkPipeline` might at times get in the way +of the pipeline development workflow. Each change to the pipeline must be pushed +to git, otherwise, after the pipeline was updated using `cdk deploy`, it will +automatically revert to the state found in git. + +To make the development more convenient, the self-mutation feature can be turned +off temporarily, by passing `selfMutating: false` property, example: + +```ts +const pipeline = new CdkPipeline(this, 'Pipeline', { + selfMutating: false, + ... +}); +``` + + ## CDK Environment Bootstrapping An *environment* is an *(account, region)* pair where you want to deploy a diff --git a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts index 7e8b46b7a4503..1e2adadafb52d 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts @@ -137,6 +137,10 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I project, input: this.props.cloudAssemblyInput, role: props.role, + // Add this purely so that the pipeline will selfupdate if the CLI version changes + environmentVariables: props.cdkCliVersion ? { + CDK_CLI_VERSION: { value: props.cdkCliVersion }, + } : undefined, }); } diff --git a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts index 9642fa7ba854e..20dce58246c27 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/update-pipeline-action.ts @@ -93,6 +93,10 @@ export class UpdatePipelineAction extends CoreConstruct implements codepipeline. actionName: 'SelfMutate', input: props.cloudAssemblyInput, project: selfMutationProject, + // Add this purely so that the pipeline will selfupdate if the CLI version changes + environmentVariables: props.cdkCliVersion ? { + CDK_CLI_VERSION: { value: props.cdkCliVersion }, + } : undefined, }); } diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index 28358928ecc35..cf9445681d200 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -105,6 +105,20 @@ export interface CdkPipelineProps { * @default - All private subnets. */ readonly subnetSelection?: ec2.SubnetSelection; + + /** + * Whether the pipeline will update itself + * + * This needs to be set to `true` to allow the pipeline to reconfigure + * itself when assets or stages are being added to it, and `true` is the + * recommended setting. + * + * You can temporarily set this to `false` while you are iterating + * on the pipeline itself and prefer to deploy changes using `cdk deploy`. + * + * @default true + */ + readonly selfMutating?: boolean; } /** @@ -178,15 +192,17 @@ export class CdkPipeline extends CoreConstruct { }); } - this._pipeline.addStage({ - stageName: 'UpdatePipeline', - actions: [new UpdatePipelineAction(this, 'UpdatePipeline', { - cloudAssemblyInput: this._cloudAssemblyArtifact, - pipelineStackName: pipelineStack.stackName, - cdkCliVersion: props.cdkCliVersion, - projectName: maybeSuffix(props.pipelineName, '-selfupdate'), - })], - }); + if (props.selfMutating ?? true) { + this._pipeline.addStage({ + stageName: 'UpdatePipeline', + actions: [new UpdatePipelineAction(this, 'UpdatePipeline', { + cloudAssemblyInput: this._cloudAssemblyArtifact, + pipelineStackName: pipelineStack.stackName, + cdkCliVersion: props.cdkCliVersion, + projectName: maybeSuffix(props.pipelineName, '-selfupdate'), + })], + }); + } this._assets = new AssetPublishing(this, 'Assets', { cloudAssemblyInput: this._cloudAssemblyArtifact, @@ -475,6 +491,7 @@ class AssetPublishing extends CoreConstruct { 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', ], resources: [codeBuildArn], })); diff --git a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts index 528cece1d0700..4fb610688e9a4 100644 --- a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts +++ b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts @@ -89,6 +89,17 @@ export interface ShellScriptActionProps { * @default - All private subnets. */ readonly subnetSelection?: ec2.SubnetSelection + + /** + * Which security group to associate with the script's project network interfaces. + * If no security group is identified, one will be created automatically. + * + * Only used if 'vpc' is supplied. + * + * @default - Security group will be automatically created. + * + */ + readonly securityGroups?: ec2.ISecurityGroup[]; } /** @@ -168,6 +179,7 @@ export class ShellScriptAction implements codepipeline.IAction, iam.IGrantable { this._project = new codebuild.PipelineProject(scope, 'Project', { environment: { buildImage: codebuild.LinuxBuildImage.STANDARD_4_0 }, vpc: this.props.vpc, + securityGroups: this.props.securityGroups, subnetSelection: this.props.subnetSelection, buildSpec: codebuild.BuildSpec.fromObject({ version: '0.2', diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index b23bccdd40701..80159b7a0e368 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -857,7 +857,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1167,7 +1168,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1354,7 +1356,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1550,7 +1553,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index c9cf293e1c173..2531bf13bc642 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -756,7 +756,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1066,7 +1067,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { @@ -1253,7 +1255,8 @@ "codebuild:CreateReportGroup", "codebuild:CreateReport", "codebuild:UpdateReport", - "codebuild:BatchPutTestCases" + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts index 3605c830529a5..2b4facf654fc6 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -328,7 +328,7 @@ function expectedAssetRolePolicy(assumeRolePattern: string, attachedRole: string }, }, { - Action: ['codebuild:CreateReportGroup', 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases'], + Action: ['codebuild:CreateReportGroup', 'codebuild:CreateReport', 'codebuild:UpdateReport', 'codebuild:BatchPutTestCases', 'codebuild:BatchPutCodeCoverages'], Effect: 'Allow', Resource: { 'Fn::Join': ['', [ diff --git a/packages/@aws-cdk/pipelines/test/pipeline.test.ts b/packages/@aws-cdk/pipelines/test/pipeline.test.ts index 8fa56d50f3ea0..003aacf021d47 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline.test.ts @@ -1,6 +1,15 @@ import * as fs from 'fs'; import * as path from 'path'; -import { anything, arrayWith, Capture, deepObjectLike, encodedJson, objectLike, stringLike } from '@aws-cdk/assert'; +import { + anything, + arrayWith, + Capture, + deepObjectLike, + encodedJson, + notMatching, + objectLike, + stringLike, +} from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as cp from '@aws-cdk/aws-codepipeline'; import * as cpa from '@aws-cdk/aws-codepipeline-actions'; @@ -332,6 +341,23 @@ test('selfmutation stage correctly identifies nested assembly of pipeline stack' }); }); +test('selfmutation feature can be turned off', () => { + const stack = new Stack(); + const cloudAssemblyArtifact = new cp.Artifact(); + // WHEN + new TestGitHubNpmPipeline(stack, 'Cdk', { + cloudAssemblyArtifact, + selfMutating: false, + }); + // THEN + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: notMatching(arrayWith({ + Name: 'UpdatePipeline', + Actions: anything(), + })), + }); +}); + test('overridden stack names are respected', () => { // WHEN pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App1')); @@ -385,6 +411,33 @@ test('can control fix/CLI version used in pipeline selfupdate', () => { }); }); +test('changing CLI version leads to a different pipeline structure (restarting it)', () => { + // GIVEN + const stack2 = new Stack(app, 'Stack2', { env: PIPELINE_ENV }); + const stack3 = new Stack(app, 'Stack3', { env: PIPELINE_ENV }); + const structure2 = Capture.anyType(); + const structure3 = Capture.anyType(); + + // WHEN + new TestGitHubNpmPipeline(stack2, 'Cdk', { + cdkCliVersion: '1.2.3', + }); + new TestGitHubNpmPipeline(stack3, 'Cdk', { + cdkCliVersion: '4.5.6', + }); + + // THEN + expect(stack2).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: structure2.capture(), + }); + expect(stack3).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: structure3.capture(), + }); + + expect(JSON.stringify(structure2.capturedValue)).not.toEqual(JSON.stringify(structure3.capturedValue)); + +}); + test('add another action to an existing stage', () => { // WHEN pipeline.stage('Source').addAction(new cpa.GitHubSourceAction({ diff --git a/packages/@aws-cdk/pipelines/test/validation.test.ts b/packages/@aws-cdk/pipelines/test/validation.test.ts index 2f22bb055ad4a..9986aad4d2758 100644 --- a/packages/@aws-cdk/pipelines/test/validation.test.ts +++ b/packages/@aws-cdk/pipelines/test/validation.test.ts @@ -290,6 +290,46 @@ test('run ShellScriptAction in a VPC', () => { }); }); +test('run ShellScriptAction with Security Group', () => { + // WHEN + const vpc = new ec2.Vpc(pipelineStack, 'VPC'); + const sg = new ec2.SecurityGroup(pipelineStack, 'SG', { vpc }); + pipeline.addStage('Test').addActions(new cdkp.ShellScriptAction({ + vpc, + securityGroups: [sg], + actionName: 'sgAction', + additionalArtifacts: [integTestArtifact], + commands: ['true'], + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Test', + Actions: [ + deepObjectLike({ + Name: 'sgAction', + }), + ], + }), + }); + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + VpcConfig: { + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'SGADB53937', + 'GroupId', + ], + }, + ], + VpcId: { + Ref: 'VPCB9E5F0B4', + }, + }, + }); +}); + class AppWithStackOutput extends Stage { public readonly output: CfnOutput; diff --git a/packages/@aws-cdk/region-info/.npmignore b/packages/@aws-cdk/region-info/.npmignore index d32b63723f317..4e84b879a486f 100644 --- a/packages/@aws-cdk/region-info/.npmignore +++ b/packages/@aws-cdk/region-info/.npmignore @@ -21,4 +21,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index 16124517c2d5e..6520348d3e2ad 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -65,6 +65,7 @@ export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { 'eu-west-2': 'Z3GKZC51ZF0DB4', 'eu-west-3': 'Z3R1K369G5AVDG', 'eu-north-1': 'Z3BAZG2TWCNX0D', + 'eu-south-1': 'Z3IXVV8C73GIO3', 'sa-east-1': 'Z7KQH4QJS55SO', 'me-south-1': 'Z1MPMWCPA7YB62', }; diff --git a/packages/@aws-cdk/region-info/package.json b/packages/@aws-cdk/region-info/package.json index 5ea296f02297d..433bd60aa6a17 100644 --- a/packages/@aws-cdk/region-info/package.json +++ b/packages/@aws-cdk/region-info/package.json @@ -33,9 +33,6 @@ "projectReferences": true }, "cdk-build": { - "pre": [ - "npm run gen" - ], "jest": true }, "scripts": { diff --git a/packages/@aws-cdk/yaml-cfn/.npmignore b/packages/@aws-cdk/yaml-cfn/.npmignore index bca0ae2513f1e..63ab95621c764 100644 --- a/packages/@aws-cdk/yaml-cfn/.npmignore +++ b/packages/@aws-cdk/yaml-cfn/.npmignore @@ -23,4 +23,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/yaml-cfn/package.json b/packages/@aws-cdk/yaml-cfn/package.json index ae3e876d12b80..579c9d6caa626 100644 --- a/packages/@aws-cdk/yaml-cfn/package.json +++ b/packages/@aws-cdk/yaml-cfn/package.json @@ -71,7 +71,7 @@ "@types/jest": "^26.0.14", "@types/yaml": "^1.9.7", "cdk-build-tools": "0.0.0", - "jest": "^25.5.4", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "bundledDependencies": [ diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index bdfa792c68113..e4a53277bd678 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -17,7 +17,7 @@ "cdk-build": { "jest": true, "pre": [ - "./clone.sh" + "./clone.sh" ], "eslint": { "disable": true @@ -35,11 +35,11 @@ "devDependencies": { "@monocdk-experiment/rewrite-imports": "0.0.0", "@types/jest": "^26.0.14", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "cdk-build-tools": "0.0.0", "constructs": "^3.0.4", - "jest": "^26.4.2", - "monocdk-experiment": "0.0.0", + "jest": "^26.6.0", + "monocdk": "0.0.0", "pkglint": "0.0.0", "ts-jest": "^26.4.1" }, @@ -48,8 +48,8 @@ }, "peerDependencies": { "constructs": "^3.0.4", - "jest": "^26.4.2", - "monocdk-experiment": "^0.0.0" + "jest": "^26.6.0", + "monocdk": "^0.0.0" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@monocdk-experiment/rewrite-imports/.npmignore b/packages/@monocdk-experiment/rewrite-imports/.npmignore index 1a0946f499b8d..4a2955c9ce9f9 100644 --- a/packages/@monocdk-experiment/rewrite-imports/.npmignore +++ b/packages/@monocdk-experiment/rewrite-imports/.npmignore @@ -13,4 +13,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/@monocdk-experiment/rewrite-imports/README.md b/packages/@monocdk-experiment/rewrite-imports/README.md index d4dd324816fac..1255d2913afb6 100644 --- a/packages/@monocdk-experiment/rewrite-imports/README.md +++ b/packages/@monocdk-experiment/rewrite-imports/README.md @@ -9,7 +9,7 @@ --- -Migrate TypeScript `import` statements from modular CDK (i.e. `@aws-cdk/aws-s3`) to mono-cdk (i.e. `monocdk-experiment/aws-s3`); +Migrate TypeScript `import` statements from modular CDK (i.e. `@aws-cdk/aws-s3`) to mono-cdk (i.e. `monocdk/aws-s3`); Usage: diff --git a/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts b/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts index f6dad5edbd89b..c0dd892cd6356 100644 --- a/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts +++ b/packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts @@ -98,7 +98,7 @@ function updatedLocationOf(modulePath: string): string | undefined { } if (modulePath === '@aws-cdk/core') { - return 'monocdk-experiment'; + return 'monocdk'; } if (modulePath === '@aws-cdk/assert') { @@ -109,5 +109,5 @@ function updatedLocationOf(modulePath: string): string | undefined { return '@monocdk-experiment/assert/jest'; } - return `monocdk-experiment/${modulePath.substring(9)}`; + return `monocdk/${modulePath.substring(9)}`; } diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index ed39eee4544bd..51912c5c951ff 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -1,7 +1,7 @@ { "name": "@monocdk-experiment/rewrite-imports", "version": "0.0.0", - "description": "Rewrites typescript 'import' statements from @aws-cdk/xxx to monocdk-experiment", + "description": "Rewrites typescript 'import' statements from @aws-cdk/xxx to monocdk", "bin": { "rewrite-imports": "bin/rewrite-imports" }, @@ -37,7 +37,7 @@ "devDependencies": { "@types/glob": "^7.1.3", "@types/jest": "^26.0.14", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "cdk-build-tools": "0.0.0", "pkglint": "0.0.0" }, diff --git a/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts b/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts index 689efb72ef79b..d932b8f356964 100644 --- a/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts +++ b/packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts @@ -45,9 +45,9 @@ describe(rewriteImports, () => { expect(output).toBe(` // something before - import * as s3 from 'monocdk-experiment/aws-s3'; + import * as s3 from 'monocdk/aws-s3'; import * as cfndiff from '@aws-cdk/cloudformation-diff'; - import { Construct } from "monocdk-experiment"; + import { Construct } from "monocdk"; // something after console.log('Look! I did something!');`); @@ -65,9 +65,9 @@ describe(rewriteImports, () => { expect(output).toBe(` // something before - import s3 = require('monocdk-experiment/aws-s3'); + import s3 = require('monocdk/aws-s3'); import cfndiff = require('@aws-cdk/cloudformation-diff'); - import { Construct } = require("monocdk-experiment"); + import { Construct } = require("monocdk"); // something after console.log('Look! I did something!');`); diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 55c8f73f38b4b..e79eacd364b00 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -32,10 +32,7 @@ "cdk-build": { "eslint": { "disable": true - }, - "pre": [ - "npm run gen" - ] + } }, "pkglint": { "exclude": [ @@ -91,7 +88,7 @@ "dependencies": { "case": "1.6.3", "fs-extra": "^9.0.1", - "jsonschema": "^1.2.7", + "jsonschema": "^1.2.10", "minimatch": "^3.0.4", "semver": "^7.3.2", "yaml": "1.10.0" @@ -132,6 +129,7 @@ "@aws-cdk/aws-cloudtrail": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-cloudwatch-actions": "0.0.0", + "@aws-cdk/aws-codeartifact": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", @@ -242,6 +240,7 @@ "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/aws-stepfunctions-tasks": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", + "@aws-cdk/aws-timestream": "0.0.0", "@aws-cdk/aws-transfer": "0.0.0", "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", @@ -256,7 +255,7 @@ "@aws-cdk/region-info": "0.0.0", "@aws-cdk/yaml-cfn": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "cdk-build-tools": "0.0.0", "constructs": "^3.0.4", "fs-extra": "^9.0.1", diff --git a/packages/aws-cdk/CONTRIBUTING.md b/packages/aws-cdk/CONTRIBUTING.md index b5416406b7136..cbf6933dff920 100644 --- a/packages/aws-cdk/CONTRIBUTING.md +++ b/packages/aws-cdk/CONTRIBUTING.md @@ -88,7 +88,7 @@ yarn integ-cli-no-regression #### Regression -Validate that previously tested functionality still works in light of recent changes to the CLI. This is done by fetching the functional tests of the latest published release, and running them against the new CLI code. +Validate that previously tested functionality still works in light of recent changes to the CLI. This is done by fetching the functional tests of the previous published release, and running them against the new CLI code. These tests run in two variations: @@ -97,17 +97,62 @@ These tests run in two variations: Use your local framework code. This is important to make sure the new CLI version will work properly with the new framework version. -- **against latest release code** + > See a concrete failure [example](https://github.com/aws/aws-cdk-rfcs/blob/master/text/00110-cli-framework-compatibility-strategy.md#remove---target-from-docker-build-command) - Fetches the framework code from the latest release. This is important to make sure +- **against previously release code** + + Fetches the framework code from the previous release. This is important to make sure the new CLI version does not rely on new framework features to provide the same functionality. + > See a concrete failure [example](https://github.com/aws/aws-cdk-rfcs/blob/master/text/00110-cli-framework-compatibility-strategy.md#change-artifact-metadata-type-value) + You can also run just these tests by executing: ```console yarn integ-cli-regression ``` +Note that these tests can only be executed using the `run-against-dist` wrapper. Why? well, it doesn't really make sense to `run-against-repo` when testing the **previously released code**, since we obviously cannot use the repo. Granted, running **against local framework code** can somehow work, but it required a few too many hacks in the current codebase to make it seem worthwhile. + +##### Implementation + +The implemention of the regression suites is not trivial to reason about and follow. Even though the code includes inline comments, we break down the exact details to better serve us in maintaining it and regaining context. + +Before diving into it, we establish a few key concepts: + +- `CANDIDATE_VERSION` - This is the version of the code that is being built in the pipeline, and its value is stored in the `build.json` file of the packaged artifact of the repo. +- `PREVIOUS_VERSION` - This is the version previous to the `CANDIDATE_VERSION`. +- `CLI_VERSION` - This is the version of the CLI we are testing. It is **always** the same as the `CANDIDATE_VERSION` since we want to test the latest CLI code. +- `FRAMEWORK_VERSION` - This is the version of the framework we are testing. It varries between the two variation of the regression suites. +Its value can either be that of `CANDIDATE_VERSION` (for testing against the latest framework code), or `PREVIOUS_VERSION` (for testing against the previously published version of the framework code). + +Following are the steps invovled in running these tests: + +1. Run [`./bump-candidate.sh`](../../bump-candidate.sh) to differentiate between the local version and the published version. For example, if the version in `lerna.json` is `1.67.0`, this script will result in a version `1.67.0-rc.0`. This is needed so that we can launch a verdaccio instance serving local tarballs without worrying about conflicts with the public npm uplink. This will help us avoid version quirks that might happen during the *post-release-pre-merge-back* time window. + +2. Run [`./align-version.sh`](../../scripts/align-version.sh) to configure the above version in all our packages. + +3. Build and Pack the repository. The produced tarballs will be versioned with the above version. + +4. Run `test/integ/run-against-dist test/integ/test-cli-regression-against-latest-release.sh` (or `test/integ/test-cli-regression-against-latest-code.sh`) + +5. First, the `run-against-dist` wrapper will run and: + + - Read the `CANDIDATE_VERSION` from `build.json` and export it. + - [Launch verdaccio](./test/integ/run-against-dist#L29) to serve all local tarballs (serves the `CANDIDATE_VERSION` now) + - [Install the CLI](./test/integ/run-against-dist#L30) using the `CANDIDATE_VERSION` version `CANDIDATE_VERSION` env variable. + - Execute the given script. + +6. Both cli regression test scripts run the same [`run_regression_against_framework_version`](./test/integ/test-cli-regression.bash#L22) function. This function accepts which framework version should the regression run against, it can be either `CANDIDATE_VERSION` or `PREVIOUS_VERSION`. Note that the argument is not the actual value of the versio, but instead is just an [indirection indentifier](./test/integ/test-cli-regression.bash#L81). The function will: + + - Calculate the actual value of the previous version based on the candidate version. (fetches from github) + - Download the previous version tarball from npm and extract the integration tests. + - Export a `FRAMWORK_VERSION` env variable based on the caller, and execute the integration tests of the previous version. + +7. Our integration tests now run and have knowledge of which framework version they should [install](./test/integ/cli/cdk-helpers.ts#L74). + +That "basically" it, hope it makes sense... + ### Init template integration tests Init template tests will initialize and compile the init templates that the diff --git a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts index 75082b9953966..30ac1704ccc36 100644 --- a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts +++ b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts @@ -83,6 +83,9 @@ export class AccountAccessKeyCache { // File doesn't exist or is not readable. This is a cache, // pretend we successfully loaded an empty map. if (e.code === 'ENOENT' || e.code === 'EACCES') { return {}; } + // File is not JSON, could be corrupted because of concurrent writes. + // Again, an empty cache is fine. + if (e instanceof SyntaxError) { return {}; } throw e; } } diff --git a/packages/aws-cdk/lib/commands/context.ts b/packages/aws-cdk/lib/commands/context.ts index 994bb7c675636..e4ed02a8b4212 100644 --- a/packages/aws-cdk/lib/commands/context.ts +++ b/packages/aws-cdk/lib/commands/context.ts @@ -28,8 +28,6 @@ export function handler(args: yargs.Arguments) { export async function realHandler(options: CommandOptions): Promise { const { configuration, args } = options; - const contextValues = configuration.context.all; - if (args.clear) { configuration.context.clear(); await configuration.saveContext(); @@ -40,9 +38,10 @@ export async function realHandler(options: CommandOptions): Promise { } else { // List -- support '--json' flag if (args.json) { + const contextValues = configuration.context.all; process.stdout.write(JSON.stringify(contextValues, undefined, 2)); } else { - listContext(contextValues); + listContext(configuration.context); } } await version.displayVersionMessage(); @@ -50,7 +49,7 @@ export async function realHandler(options: CommandOptions): Promise { return 0; } -function listContext(context: any) { +function listContext(context: Context) { const keys = contextKeys(context); if (keys.length === 0) { @@ -66,7 +65,7 @@ function listContext(context: any) { // Print config by default const data: any[] = [[colors.green('#'), colors.green('Key'), colors.green('Value')]]; for (const [i, key] of keys) { - const jsonWithoutNewlines = JSON.stringify(context[key], undefined, 2).replace(/\s+/g, ' '); + const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' '); data.push([i, key, jsonWithoutNewlines]); } @@ -81,7 +80,7 @@ function listContext(context: any) { function invalidateContext(context: Context, key: string) { const i = parseInt(key, 10); if (`${i}` === key) { - // Twas a number and we fully parsed it. + // was a number and we fully parsed it. key = keyByNumber(context, i); } @@ -94,7 +93,7 @@ function invalidateContext(context: Context, key: string) { } } -function keyByNumber(context: any, n: number) { +function keyByNumber(context: Context, n: number) { for (const [i, key] of contextKeys(context)) { if (n === i) { return key; diff --git a/packages/aws-cdk/lib/init-templates/app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/app/java/pom.template.xml index 18a08d803562f..58067e2745e2d 100644 --- a/packages/aws-cdk/lib/init-templates/app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/app/java/pom.template.xml @@ -27,7 +27,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 com.myorg.%name.PascalCased%App @@ -47,7 +47,7 @@ junit junit - 4.13 + 4.13.1 test diff --git a/packages/aws-cdk/lib/init-templates/sample-app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/sample-app/java/pom.template.xml index 7159e270096a4..a0f32927a3d5f 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/sample-app/java/pom.template.xml @@ -23,7 +23,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 com.myorg.%name.PascalCased%App @@ -59,7 +59,7 @@ junit junit - 4.13 + 4.13.1 test diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index d72fac19cd2ab..58b6a57b2bbe8 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -19,16 +19,14 @@ "build+test": "npm run build && npm test", "integ-cli": "npm run integ-cli-regression && npm run integ-cli-no-regression", "integ-cli-regression": "npm run integ-cli-regression-latest-release && npm run integ-cli-regression-latest-code", - "integ-cli-regression-latest-release": "test/integ/run-against-repo test/integ/test-cli-regression-against-latest-release.sh", - "integ-cli-regression-latest-code": "test/integ/run-against-repo test/integ/test-cli-regression-against-current-code.sh", + "integ-cli-regression-latest-release": "test/integ/run-against-dist test/integ/test-cli-regression-against-latest-release.sh", + "integ-cli-regression-latest-code": "test/integ/run-against-dist test/integ/test-cli-regression-against-current-code.sh", "integ-cli-no-regression": "test/integ/run-against-repo test/integ/cli/test.sh", - "integ-init": "test/integ/run-against-dist test/integ/init/test-all.sh" + "integ-init": "test/integ/run-against-dist test/integ/init/test-all.sh", + "gen": "./generate.sh" }, "cdk-build": { - "jest": true, - "pre": [ - "./generate.sh" - ] + "jest": true }, "cdk-package": { "shrinkWrap": true @@ -47,7 +45,7 @@ "@types/jest": "^26.0.14", "@types/minimatch": "^3.0.3", "@types/mockery": "^1.4.29", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "@types/promptly": "^3.0.0", "@types/semver": "^7.3.4", "@types/sinon": "^9.0.7", @@ -57,12 +55,14 @@ "@types/yargs": "^15.0.7", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "mockery": "^2.1.0", "pkglint": "0.0.0", "sinon": "^9.1.0", "ts-jest": "^26.4.1", - "ts-mock-imports": "^1.3.0" + "ts-mock-imports": "^1.3.0", + "@octokit/rest": "^18.0.6", + "make-runnable": "^1.3.8" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -87,7 +87,7 @@ "table": "^6.0.3", "uuid": "^8.3.0", "wrap-ansi": "^7.0.0", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/aws-cdk/test/account-cache.test.ts b/packages/aws-cdk/test/account-cache.test.ts index f8860046df584..89887cc627d71 100644 --- a/packages/aws-cdk/test/account-cache.test.ts +++ b/packages/aws-cdk/test/account-cache.test.ts @@ -111,3 +111,14 @@ test(`cache is nuked if it exceeds ${AccountAccessKeyCache.MAX_ENTRIES} entries` await nukeCache(cacheDir); } }); + +test('cache pretends to be empty if cache file does not contain JSON', async() => { + const { cacheDir, cacheFile, cache } = await makeCache(); + try { + await fs.writeFile(cacheFile, ''); + + await expect(cache.get('abc')).resolves.toEqual(undefined); + } finally { + await nukeCache(cacheDir); + } +}); diff --git a/packages/aws-cdk/test/commands/context-command.test.ts b/packages/aws-cdk/test/commands/context-command.test.ts index 47b9ffc070f82..6a73c8008b52e 100644 --- a/packages/aws-cdk/test/commands/context-command.test.ts +++ b/packages/aws-cdk/test/commands/context-command.test.ts @@ -1,6 +1,22 @@ import { realHandler } from '../../lib/commands/context'; import { Configuration } from '../../lib/settings'; +test('context list', async() => { + // GIVEN + const configuration = new Configuration(); + configuration.context.set('foo', 'bar'); + + expect(configuration.context.all).toEqual({ + foo: 'bar', + }); + + // WHEN + await realHandler({ + configuration, + args: {}, + } as any); +}); + test('context reset can remove a context key', async () => { // GIVEN const configuration = new Configuration(); diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/NOTES.md new file mode 100644 index 0000000000000..19bd85e22f5f2 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/NOTES.md @@ -0,0 +1,2 @@ +Integration tests are now injected with environment variable that specifies which framework +version they should install and use. This patch includes the cdk-helpers.js file that is responsible for that. \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/cdk-helpers.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/cdk-helpers.js new file mode 100644 index 0000000000000..7bb7790818e40 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.67.0/cdk-helpers.js @@ -0,0 +1,331 @@ +"use strict"; +var _a, _b; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.randomString = exports.rimraf = exports.shell = exports.TestFixture = exports.cloneDirectory = exports.withDefaultFixture = exports.withCdkApp = exports.withAws = void 0; +const child_process = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const aws_helpers_1 = require("./aws-helpers"); +const resource_pool_1 = require("./resource-pool"); +const REGIONS = process.env.AWS_REGIONS + ? process.env.AWS_REGIONS.split(',') + : [(_b = (_a = process.env.AWS_REGION) !== null && _a !== void 0 ? _a : process.env.AWS_DEFAULT_REGION) !== null && _b !== void 0 ? _b : 'us-east-1']; +const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION; +process.stdout.write(`Using regions: ${REGIONS}\n`); +process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION}\n`); +const REGION_POOL = new resource_pool_1.ResourcePool(REGIONS); +/** + * Higher order function to execute a block with an AWS client setup + * + * Allocate the next region from the REGION pool and dispose it afterwards. + */ +function withAws(block) { + return (context) => REGION_POOL.using(async (region) => { + const aws = await aws_helpers_1.AwsClients.forRegion(region, context.output); + await sanityCheck(aws); + return block({ ...context, aws }); + }); +} +exports.withAws = withAws; +/** + * Higher order function to execute a block with a CDK app fixture + * + * Requires an AWS client to be passed in. + * + * For backwards compatibility with existing tests (so we don't have to change + * too much) the inner block is expected to take a `TestFixture` object. + */ +function withCdkApp(block) { + return async (context) => { + const randy = randomString(); + const stackNamePrefix = `cdktest-${randy}`; + const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`); + context.output.write(` Stack prefix: ${stackNamePrefix}\n`); + context.output.write(` Test directory: ${integTestDir}\n`); + context.output.write(` Region: ${context.aws.region}\n`); + await cloneDirectory(path.join(__dirname, 'app'), integTestDir, context.output); + const fixture = new TestFixture(integTestDir, stackNamePrefix, context.output, context.aws); + let success = true; + try { + let modules = [ + '@aws-cdk/core', + '@aws-cdk/aws-sns', + '@aws-cdk/aws-iam', + '@aws-cdk/aws-lambda', + '@aws-cdk/aws-ssm', + '@aws-cdk/aws-ecr-assets', + '@aws-cdk/aws-cloudformation', + '@aws-cdk/aws-ec2', + ]; + if (FRAMEWORK_VERSION) { + modules = modules.map(module => `${module}@${FRAMEWORK_VERSION}`); + } + await fixture.shell(['npm', 'install', ...modules]); + await ensureBootstrapped(fixture); + await block(fixture); + } + catch (e) { + success = false; + throw e; + } + finally { + await fixture.dispose(success); + } + }; +} +exports.withCdkApp = withCdkApp; +/** + * Default test fixture for most (all?) integ tests + * + * It's a composition of withAws/withCdkApp, expecting the test block to take a `TestFixture` + * object. + * + * We could have put `withAws(withCdkApp(fixture => { /... actual test here.../ }))` in every + * test declaration but centralizing it is going to make it convenient to modify in the future. + */ +function withDefaultFixture(block) { + return withAws(withCdkApp(block)); + // ^~~~~~ this is disappointing TypeScript! Feels like you should have been able to derive this. +} +exports.withDefaultFixture = withDefaultFixture; +/** + * Prepare a target dir byreplicating a source directory + */ +async function cloneDirectory(source, target, output) { + await shell(['rm', '-rf', target], { output }); + await shell(['mkdir', '-p', target], { output }); + await shell(['cp', '-R', source + '/*', target], { output }); +} +exports.cloneDirectory = cloneDirectory; +class TestFixture { + constructor(integTestDir, stackNamePrefix, output, aws) { + this.integTestDir = integTestDir; + this.stackNamePrefix = stackNamePrefix; + this.output = output; + this.aws = aws; + this.qualifier = randomString().substr(0, 10); + this.bucketsToDelete = new Array(); + } + log(s) { + this.output.write(`${s}\n`); + } + async shell(command, options = {}) { + return shell(command, { + output: this.output, + cwd: this.integTestDir, + ...options, + }); + } + async cdkDeploy(stackNames, options = {}) { + var _a, _b; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + const neverRequireApproval = (_a = options.neverRequireApproval) !== null && _a !== void 0 ? _a : true; + return this.cdk(['deploy', + ...(neverRequireApproval ? ['--require-approval=never'] : []), // Default to no approval in an unattended test + ...((_b = options.options) !== null && _b !== void 0 ? _b : []), ...this.fullStackName(stackNames)], options); + } + async cdkDestroy(stackNames, options = {}) { + var _a; + stackNames = typeof stackNames === 'string' ? [stackNames] : stackNames; + return this.cdk(['destroy', + '-f', // We never want a prompt in an unattended test + ...((_a = options.options) !== null && _a !== void 0 ? _a : []), ...this.fullStackName(stackNames)], options); + } + async cdk(args, options = {}) { + var _a; + const verbose = (_a = options.verbose) !== null && _a !== void 0 ? _a : true; + return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], { + ...options, + modEnv: { + AWS_REGION: this.aws.region, + AWS_DEFAULT_REGION: this.aws.region, + STACK_NAME_PREFIX: this.stackNamePrefix, + ...options.modEnv, + }, + }); + } + fullStackName(stackNames) { + if (typeof stackNames === 'string') { + return `${this.stackNamePrefix}-${stackNames}`; + } + else { + return stackNames.map(s => `${this.stackNamePrefix}-${s}`); + } + } + /** + * Append this to the list of buckets to potentially delete + * + * At the end of a test, we clean up buckets that may not have gotten destroyed + * (for whatever reason). + */ + rememberToDeleteBucket(bucketName) { + this.bucketsToDelete.push(bucketName); + } + /** + * Cleanup leftover stacks and buckets + */ + async dispose(success) { + const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix); + // Bootstrap stacks have buckets that need to be cleaned + const bucketNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('BucketName', stack)).filter(defined); + await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b))); + // Bootstrap stacks have ECR repositories with images which should be deleted + const imageRepositoryNames = stacksToDelete.map(stack => aws_helpers_1.outputFromStack('ImageRepositoryName', stack)).filter(defined); + await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r))); + await this.aws.deleteStacks(...stacksToDelete.map(s => s.StackName)); + // We might have leaked some buckets by upgrading the bootstrap stack. Be + // sure to clean everything. + for (const bucket of this.bucketsToDelete) { + await this.aws.deleteBucket(bucket); + } + // If the tests completed successfully, happily delete the fixture + // (otherwise leave it for humans to inspect) + if (success) { + rimraf(this.integTestDir); + } + } + /** + * Return the stacks starting with our testing prefix that should be deleted + */ + async deleteableStacks(prefix) { + var _a; + const statusFilter = [ + 'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE', + 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE', + 'DELETE_FAILED', + 'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS', + 'UPDATE_ROLLBACK_FAILED', + 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', + 'UPDATE_ROLLBACK_COMPLETE', 'REVIEW_IN_PROGRESS', + 'IMPORT_IN_PROGRESS', 'IMPORT_COMPLETE', + 'IMPORT_ROLLBACK_IN_PROGRESS', 'IMPORT_ROLLBACK_FAILED', + 'IMPORT_ROLLBACK_COMPLETE', + ]; + const response = await this.aws.cloudFormation('describeStacks', {}); + return ((_a = response.Stacks) !== null && _a !== void 0 ? _a : []) + .filter(s => s.StackName.startsWith(prefix)) + .filter(s => statusFilter.includes(s.StackStatus)) + .filter(s => s.RootId === undefined); // Only delete parent stacks. Nested stacks are deleted in the process + } +} +exports.TestFixture = TestFixture; +/** + * Perform a one-time quick sanity check that the AWS clients has properly configured credentials + * + * If we don't do this, calls are going to fail and they'll be retried and everything will take + * forever before the user notices a simple misconfiguration. + * + * We can't check for the presence of environment variables since credentials could come from + * anywhere, so do simple account retrieval. + * + * Only do it once per process. + */ +async function sanityCheck(aws) { + if (sanityChecked === undefined) { + try { + await aws.account(); + sanityChecked = true; + } + catch (e) { + sanityChecked = false; + throw new Error(`AWS credentials probably not configured, got error: ${e.message}`); + } + } + if (!sanityChecked) { + throw new Error('AWS credentials probably not configured, see previous error'); + } +} +let sanityChecked; +/** + * Make sure that the given environment is bootstrapped + * + * Since we go striping across regions, it's going to suck doing this + * by hand so let's just mass-automate it. + */ +async function ensureBootstrapped(fixture) { + // Old-style bootstrap stack with default name + if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { + await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); + } +} +/** + * A shell command that does what you want + * + * Is platform-aware, handles errors nicely. + */ +async function shell(command, options = {}) { + var _a, _b; + if (options.modEnv && options.env) { + throw new Error('Use either env or modEnv but not both'); + } + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(`💻 ${command.join(' ')}\n`); + const env = (_b = options.env) !== null && _b !== void 0 ? _b : (options.modEnv ? { ...process.env, ...options.modEnv } : undefined); + const child = child_process.spawn(command[0], command.slice(1), { + ...options, + env, + // Need this for Windows where we want .cmd and .bat to be found as well. + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + return new Promise((resolve, reject) => { + const stdout = new Array(); + const stderr = new Array(); + child.stdout.on('data', chunk => { + var _a; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + stdout.push(chunk); + }); + child.stderr.on('data', chunk => { + var _a, _b; + (_a = options.output) === null || _a === void 0 ? void 0 : _a.write(chunk); + if ((_b = options.captureStderr) !== null && _b !== void 0 ? _b : true) { + stderr.push(chunk); + } + }); + child.once('error', reject); + child.once('close', code => { + if (code === 0 || options.allowErrExit) { + resolve((Buffer.concat(stdout).toString('utf-8') + Buffer.concat(stderr).toString('utf-8')).trim()); + } + else { + reject(new Error(`'${command.join(' ')}' exited with error code ${code}`)); + } + }); + }); +} +exports.shell = shell; +function defined(x) { + return x !== undefined; +} +/** + * rm -rf reimplementation, don't want to depend on an NPM package for this + */ +function rimraf(fsPath) { + try { + const isDir = fs.lstatSync(fsPath).isDirectory(); + if (isDir) { + for (const file of fs.readdirSync(fsPath)) { + rimraf(path.join(fsPath, file)); + } + fs.rmdirSync(fsPath); + } + else { + fs.unlinkSync(fsPath); + } + } + catch (e) { + // We will survive ENOENT + if (e.code !== 'ENOENT') { + throw e; + } + } +} +exports.rimraf = rimraf; +function randomString() { + // Crazy + return Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); +} +exports.randomString = randomString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsK0NBQStDO0FBQy9DLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtDQUE0RDtBQUM1RCxtREFBK0M7QUFHL0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXO0lBQ3JDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0lBQ3BDLENBQUMsQ0FBQyxhQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxtQ0FBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixtQ0FBSSxXQUFXLENBQUMsQ0FBQztBQUU5RSxNQUFNLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUM7QUFFeEQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsa0JBQWtCLE9BQU8sSUFBSSxDQUFDLENBQUM7QUFDcEQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNEJBQTRCLGlCQUFpQixJQUFJLENBQUMsQ0FBQztBQUV4RSxNQUFNLFdBQVcsR0FBRyxJQUFJLDRCQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7QUFLOUM7Ozs7R0FJRztBQUNILFNBQWdCLE9BQU8sQ0FBd0IsS0FBaUQ7SUFDOUYsT0FBTyxDQUFDLE9BQVUsRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDeEQsTUFBTSxHQUFHLEdBQUcsTUFBTSx3QkFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQy9ELE1BQU0sV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXZCLE9BQU8sS0FBSyxDQUFDLEVBQUUsR0FBRyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFQRCwwQkFPQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxTQUFnQixVQUFVLENBQXFDLEtBQThDO0lBQzNHLE9BQU8sS0FBSyxFQUFFLE9BQVUsRUFBRSxFQUFFO1FBQzFCLE1BQU0sS0FBSyxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQzdCLE1BQU0sZUFBZSxHQUFHLFdBQVcsS0FBSyxFQUFFLENBQUM7UUFDM0MsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUUsYUFBYSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBRWxFLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixlQUFlLElBQUksQ0FBQyxDQUFDO1FBQzlELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixZQUFZLElBQUksQ0FBQyxDQUFDO1FBQzNELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sSUFBSSxDQUFDLENBQUM7UUFFakUsTUFBTSxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLEVBQUUsWUFBWSxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoRixNQUFNLE9BQU8sR0FBRyxJQUFJLFdBQVcsQ0FDN0IsWUFBWSxFQUNaLGVBQWUsRUFDZixPQUFPLENBQUMsTUFBTSxFQUNkLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUVmLElBQUksT0FBTyxHQUFHLElBQUksQ0FBQztRQUNuQixJQUFJO1lBQ0YsSUFBSSxPQUFPLEdBQUc7Z0JBQ1osZUFBZTtnQkFDZixrQkFBa0I7Z0JBQ2xCLGtCQUFrQjtnQkFDbEIscUJBQXFCO2dCQUNyQixrQkFBa0I7Z0JBQ2xCLHlCQUF5QjtnQkFDekIsNkJBQTZCO2dCQUM3QixrQkFBa0I7YUFDbkIsQ0FBQztZQUNGLElBQUksaUJBQWlCLEVBQUU7Z0JBQ3JCLE9BQU8sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLElBQUksaUJBQWlCLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1lBQ0QsTUFBTSxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLFNBQVMsRUFBRSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFFcEQsTUFBTSxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUVsQyxNQUFNLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztTQUN0QjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUNoQixNQUFNLENBQUMsQ0FBQztTQUNUO2dCQUFTO1lBQ1IsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQ2hDO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQTVDRCxnQ0E0Q0M7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQWdCLGtCQUFrQixDQUFDLEtBQThDO0lBQy9FLE9BQU8sT0FBTyxDQUFjLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQy9DLDZHQUE2RztBQUMvRyxDQUFDO0FBSEQsZ0RBR0M7QUFrQ0Q7O0dBRUc7QUFDSSxLQUFLLFVBQVUsY0FBYyxDQUFDLE1BQWMsRUFBRSxNQUFjLEVBQUUsTUFBOEI7SUFDakcsTUFBTSxLQUFLLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUMvQyxNQUFNLEtBQUssQ0FBQyxDQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sS0FBSyxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEdBQUcsSUFBSSxFQUFFLE1BQU0sQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztBQUMvRCxDQUFDO0FBSkQsd0NBSUM7QUFFRCxNQUFhLFdBQVc7SUFJdEIsWUFDa0IsWUFBb0IsRUFDcEIsZUFBdUIsRUFDdkIsTUFBNkIsRUFDN0IsR0FBZTtRQUhmLGlCQUFZLEdBQVosWUFBWSxDQUFRO1FBQ3BCLG9CQUFlLEdBQWYsZUFBZSxDQUFRO1FBQ3ZCLFdBQU0sR0FBTixNQUFNLENBQXVCO1FBQzdCLFFBQUcsR0FBSCxHQUFHLENBQVk7UUFQakIsY0FBUyxHQUFHLFlBQVksRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDeEMsb0JBQWUsR0FBRyxJQUFJLEtBQUssRUFBVSxDQUFDO0lBT3ZELENBQUM7SUFFTSxHQUFHLENBQUMsQ0FBUztRQUNsQixJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUIsQ0FBQztJQUVNLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBaUIsRUFBRSxVQUE4QyxFQUFFO1FBQ3BGLE9BQU8sS0FBSyxDQUFDLE9BQU8sRUFBRTtZQUNwQixNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07WUFDbkIsR0FBRyxFQUFFLElBQUksQ0FBQyxZQUFZO1lBQ3RCLEdBQUcsT0FBTztTQUNYLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTSxLQUFLLENBQUMsU0FBUyxDQUFDLFVBQTZCLEVBQUUsVUFBeUIsRUFBRTs7UUFDL0UsVUFBVSxHQUFHLE9BQU8sVUFBVSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO1FBRXhFLE1BQU0sb0JBQW9CLFNBQUcsT0FBTyxDQUFDLG9CQUFvQixtQ0FBSSxJQUFJLENBQUM7UUFFbEUsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUTtZQUN2QixHQUFHLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFDLENBQUMsMEJBQTBCLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsK0NBQStDO1lBQzlHLEdBQUcsT0FBQyxPQUFPLENBQUMsT0FBTyxtQ0FBSSxFQUFFLENBQUMsRUFDMUIsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVNLEtBQUssQ0FBQyxVQUFVLENBQUMsVUFBNkIsRUFBRSxVQUF5QixFQUFFOztRQUNoRixVQUFVLEdBQUcsT0FBTyxVQUFVLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7UUFFeEUsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsU0FBUztZQUN4QixJQUFJLEVBQUUsK0NBQStDO1lBQ3JELEdBQUcsT0FBQyxPQUFPLENBQUMsT0FBTyxtQ0FBSSxFQUFFLENBQUMsRUFDMUIsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVNLEtBQUssQ0FBQyxHQUFHLENBQUMsSUFBYyxFQUFFLFVBQXlCLEVBQUU7O1FBQzFELE1BQU0sT0FBTyxTQUFHLE9BQU8sQ0FBQyxPQUFPLG1DQUFJLElBQUksQ0FBQztRQUV4QyxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsRUFBRTtZQUM5RCxHQUFHLE9BQU87WUFDVixNQUFNLEVBQUU7Z0JBQ04sVUFBVSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTTtnQkFDM0Isa0JBQWtCLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNO2dCQUNuQyxpQkFBaUIsRUFBRSxJQUFJLENBQUMsZUFBZTtnQkFDdkMsR0FBRyxPQUFPLENBQUMsTUFBTTthQUNsQjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7SUFJTSxhQUFhLENBQUMsVUFBNkI7UUFDaEQsSUFBSSxPQUFPLFVBQVUsS0FBSyxRQUFRLEVBQUU7WUFDbEMsT0FBTyxHQUFHLElBQUksQ0FBQyxlQUFlLElBQUksVUFBVSxFQUFFLENBQUM7U0FDaEQ7YUFBTTtZQUNMLE9BQU8sVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLGVBQWUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1NBQzVEO0lBQ0gsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksc0JBQXNCLENBQUMsVUFBa0I7UUFDOUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFnQjtRQUNuQyxNQUFNLGNBQWMsR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFekUsd0RBQXdEO1FBQ3hELE1BQU0sV0FBVyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyw2QkFBZSxDQUFDLFlBQVksRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN0RyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVqRSw2RUFBNkU7UUFDN0UsTUFBTSxvQkFBb0IsR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsNkJBQWUsQ0FBQyxxQkFBcUIsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN4SCxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0JBQW9CLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFcEYsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztRQUVyRSx5RUFBeUU7UUFDekUsNEJBQTRCO1FBQzVCLEtBQUssTUFBTSxNQUFNLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRTtZQUN6QyxNQUFNLElBQUksQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1NBQ3JDO1FBRUQsa0VBQWtFO1FBQ2xFLDZDQUE2QztRQUM3QyxJQUFJLE9BQU8sRUFBRTtZQUNYLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7U0FDM0I7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsTUFBYzs7UUFDM0MsTUFBTSxZQUFZLEdBQUc7WUFDbkIsb0JBQW9CLEVBQUUsZUFBZSxFQUFFLGlCQUFpQjtZQUN4RCxzQkFBc0IsRUFBRSxpQkFBaUIsRUFBRSxtQkFBbUI7WUFDOUQsZUFBZTtZQUNmLG9CQUFvQixFQUFFLHFDQUFxQztZQUMzRCxpQkFBaUIsRUFBRSw2QkFBNkI7WUFDaEQsd0JBQXdCO1lBQ3hCLDhDQUE4QztZQUM5QywwQkFBMEIsRUFBRSxvQkFBb0I7WUFDaEQsb0JBQW9CLEVBQUUsaUJBQWlCO1lBQ3ZDLDZCQUE2QixFQUFFLHdCQUF3QjtZQUN2RCwwQkFBMEI7U0FDM0IsQ0FBQztRQUVGLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsZ0JBQWdCLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFckUsT0FBTyxPQUFDLFFBQVEsQ0FBQyxNQUFNLG1DQUFJLEVBQUUsQ0FBQzthQUMzQixNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQzthQUMzQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQzthQUNqRCxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsc0VBQXNFO0lBQ2hILENBQUM7Q0FDRjtBQW5JRCxrQ0FtSUM7QUFFRDs7Ozs7Ozs7OztHQVVHO0FBQ0gsS0FBSyxVQUFVLFdBQVcsQ0FBQyxHQUFlO0lBQ3hDLElBQUksYUFBYSxLQUFLLFNBQVMsRUFBRTtRQUMvQixJQUFJO1lBQ0YsTUFBTSxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDcEIsYUFBYSxHQUFHLElBQUksQ0FBQztTQUN0QjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsYUFBYSxHQUFHLEtBQUssQ0FBQztZQUN0QixNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztTQUNyRjtLQUNGO0lBQ0QsSUFBSSxDQUFDLGFBQWEsRUFBRTtRQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLDZEQUE2RCxDQUFDLENBQUM7S0FDaEY7QUFDSCxDQUFDO0FBQ0QsSUFBSSxhQUFrQyxDQUFDO0FBRXZDOzs7OztHQUtHO0FBQ0gsS0FBSyxVQUFVLGtCQUFrQixDQUFDLE9BQW9CO0lBQ3BELDhDQUE4QztJQUM5QyxJQUFJLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUMsWUFBWSxDQUFDLEtBQUssU0FBUyxFQUFFO1FBQzdELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFdBQVcsRUFBRSxTQUFTLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNoRztBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0ksS0FBSyxVQUFVLEtBQUssQ0FBQyxPQUFpQixFQUFFLFVBQXdCLEVBQUU7O0lBQ3ZFLElBQUksT0FBTyxDQUFDLE1BQU0sSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFO1FBQ2pDLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztLQUMxRDtJQUVELE1BQUEsT0FBTyxDQUFDLE1BQU0sMENBQUUsS0FBSyxDQUFDLE1BQU0sT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFO0lBRW5ELE1BQU0sR0FBRyxTQUFHLE9BQU8sQ0FBQyxHQUFHLG1DQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBRWhHLE1BQU0sS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUU7UUFDOUQsR0FBRyxPQUFPO1FBQ1YsR0FBRztRQUNILHlFQUF5RTtRQUN6RSxLQUFLLEVBQUUsSUFBSTtRQUNYLEtBQUssRUFBRSxDQUFDLFFBQVEsRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO0tBQ2xDLENBQUMsQ0FBQztJQUVILE9BQU8sSUFBSSxPQUFPLENBQVMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDN0MsTUFBTSxNQUFNLEdBQUcsSUFBSSxLQUFLLEVBQVUsQ0FBQztRQUNuQyxNQUFNLE1BQU0sR0FBRyxJQUFJLEtBQUssRUFBVSxDQUFDO1FBRW5DLEtBQUssQ0FBQyxNQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRTs7WUFDL0IsTUFBQSxPQUFPLENBQUMsTUFBTSwwQ0FBRSxLQUFLLENBQUMsS0FBSyxFQUFFO1lBQzdCLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxLQUFLLENBQUMsTUFBTyxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLEVBQUU7O1lBQy9CLE1BQUEsT0FBTyxDQUFDLE1BQU0sMENBQUUsS0FBSyxDQUFDLEtBQUssRUFBRTtZQUM3QixVQUFJLE9BQU8sQ0FBQyxhQUFhLG1DQUFJLElBQUksRUFBRTtnQkFDakMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUNwQjtRQUNILENBQUMsQ0FBQyxDQUFDO1FBRUgsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFNUIsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLEVBQUU7WUFDekIsSUFBSSxJQUFJLEtBQUssQ0FBQyxJQUFJLE9BQU8sQ0FBQyxZQUFZLEVBQUU7Z0JBQ3RDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQzthQUNyRztpQkFBTTtnQkFDTCxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyw0QkFBNEIsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO2FBQzVFO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUEzQ0Qsc0JBMkNDO0FBRUQsU0FBUyxPQUFPLENBQUksQ0FBSTtJQUN0QixPQUFPLENBQUMsS0FBSyxTQUFTLENBQUM7QUFDekIsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsTUFBTSxDQUFDLE1BQWM7SUFDbkMsSUFBSTtRQUNGLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7UUFFakQsSUFBSSxLQUFLLEVBQUU7WUFDVCxLQUFLLE1BQU0sSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUU7Z0JBQ3pDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO2FBQ2pDO1lBQ0QsRUFBRSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQztTQUN0QjthQUFNO1lBQ0wsRUFBRSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztTQUN2QjtLQUNGO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDVix5QkFBeUI7UUFDekIsSUFBSSxDQUFDLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRTtZQUFFLE1BQU0sQ0FBQyxDQUFDO1NBQUU7S0FDdEM7QUFDSCxDQUFDO0FBaEJELHdCQWdCQztBQUVELFNBQWdCLFlBQVk7SUFDMUIsUUFBUTtJQUNSLE9BQU8sSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQy9ELENBQUM7QUFIRCxvQ0FHQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGNoaWxkX3Byb2Nlc3MgZnJvbSAnY2hpbGRfcHJvY2Vzcyc7XG5pbXBvcnQgKiBhcyBmcyBmcm9tICdmcyc7XG5pbXBvcnQgKiBhcyBvcyBmcm9tICdvcyc7XG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHsgb3V0cHV0RnJvbVN0YWNrLCBBd3NDbGllbnRzIH0gZnJvbSAnLi9hd3MtaGVscGVycyc7XG5pbXBvcnQgeyBSZXNvdXJjZVBvb2wgfSBmcm9tICcuL3Jlc291cmNlLXBvb2wnO1xuaW1wb3J0IHsgVGVzdENvbnRleHQgfSBmcm9tICcuL3Rlc3QtaGVscGVycyc7XG5cbmNvbnN0IFJFR0lPTlMgPSBwcm9jZXNzLmVudi5BV1NfUkVHSU9OU1xuICA/IHByb2Nlc3MuZW52LkFXU19SRUdJT05TLnNwbGl0KCcsJylcbiAgOiBbcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTiA/PyBwcm9jZXNzLmVudi5BV1NfREVGQVVMVF9SRUdJT04gPz8gJ3VzLWVhc3QtMSddO1xuXG5jb25zdCBGUkFNRVdPUktfVkVSU0lPTiA9IHByb2Nlc3MuZW52LkZSQU1FV09SS19WRVJTSU9OO1xuXG5wcm9jZXNzLnN0ZG91dC53cml0ZShgVXNpbmcgcmVnaW9uczogJHtSRUdJT05TfVxcbmApO1xucHJvY2Vzcy5zdGRvdXQud3JpdGUoYFVzaW5nIGZyYW1ld29yayB2ZXJzaW9uOiAke0ZSQU1FV09SS19WRVJTSU9OfVxcbmApO1xuXG5jb25zdCBSRUdJT05fUE9PTCA9IG5ldyBSZXNvdXJjZVBvb2woUkVHSU9OUyk7XG5cblxuZXhwb3J0IHR5cGUgQXdzQ29udGV4dCA9IHsgcmVhZG9ubHkgYXdzOiBBd3NDbGllbnRzIH07XG5cbi8qKlxuICogSGlnaGVyIG9yZGVyIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgYSBibG9jayB3aXRoIGFuIEFXUyBjbGllbnQgc2V0dXBcbiAqXG4gKiBBbGxvY2F0ZSB0aGUgbmV4dCByZWdpb24gZnJvbSB0aGUgUkVHSU9OIHBvb2wgYW5kIGRpc3Bvc2UgaXQgYWZ0ZXJ3YXJkcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdpdGhBd3M8QSBleHRlbmRzIFRlc3RDb250ZXh0PihibG9jazogKGNvbnRleHQ6IEEgJiBBd3NDb250ZXh0KSA9PiBQcm9taXNlPHZvaWQ+KSB7XG4gIHJldHVybiAoY29udGV4dDogQSkgPT4gUkVHSU9OX1BPT0wudXNpbmcoYXN5bmMgKHJlZ2lvbikgPT4ge1xuICAgIGNvbnN0IGF3cyA9IGF3YWl0IEF3c0NsaWVudHMuZm9yUmVnaW9uKHJlZ2lvbiwgY29udGV4dC5vdXRwdXQpO1xuICAgIGF3YWl0IHNhbml0eUNoZWNrKGF3cyk7XG5cbiAgICByZXR1cm4gYmxvY2soeyAuLi5jb250ZXh0LCBhd3MgfSk7XG4gIH0pO1xufVxuXG4vKipcbiAqIEhpZ2hlciBvcmRlciBmdW5jdGlvbiB0byBleGVjdXRlIGEgYmxvY2sgd2l0aCBhIENESyBhcHAgZml4dHVyZVxuICpcbiAqIFJlcXVpcmVzIGFuIEFXUyBjbGllbnQgdG8gYmUgcGFzc2VkIGluLlxuICpcbiAqIEZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSB3aXRoIGV4aXN0aW5nIHRlc3RzIChzbyB3ZSBkb24ndCBoYXZlIHRvIGNoYW5nZVxuICogdG9vIG11Y2gpIHRoZSBpbm5lciBibG9jayBpcyBleHBlY3RlZCB0byB0YWtlIGEgYFRlc3RGaXh0dXJlYCBvYmplY3QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3aXRoQ2RrQXBwPEEgZXh0ZW5kcyBUZXN0Q29udGV4dCAmIEF3c0NvbnRleHQ+KGJsb2NrOiAoY29udGV4dDogVGVzdEZpeHR1cmUpID0+IFByb21pc2U8dm9pZD4pIHtcbiAgcmV0dXJuIGFzeW5jIChjb250ZXh0OiBBKSA9PiB7XG4gICAgY29uc3QgcmFuZHkgPSByYW5kb21TdHJpbmcoKTtcbiAgICBjb25zdCBzdGFja05hbWVQcmVmaXggPSBgY2RrdGVzdC0ke3JhbmR5fWA7XG4gICAgY29uc3QgaW50ZWdUZXN0RGlyID0gcGF0aC5qb2luKG9zLnRtcGRpcigpLCBgY2RrLWludGVnLSR7cmFuZHl9YCk7XG5cbiAgICBjb250ZXh0Lm91dHB1dC53cml0ZShgIFN0YWNrIHByZWZpeDogICAke3N0YWNrTmFtZVByZWZpeH1cXG5gKTtcbiAgICBjb250ZXh0Lm91dHB1dC53cml0ZShgIFRlc3QgZGlyZWN0b3J5OiAke2ludGVnVGVzdERpcn1cXG5gKTtcbiAgICBjb250ZXh0Lm91dHB1dC53cml0ZShgIFJlZ2lvbjogICAgICAgICAke2NvbnRleHQuYXdzLnJlZ2lvbn1cXG5gKTtcblxuICAgIGF3YWl0IGNsb25lRGlyZWN0b3J5KHBhdGguam9pbihfX2Rpcm5hbWUsICdhcHAnKSwgaW50ZWdUZXN0RGlyLCBjb250ZXh0Lm91dHB1dCk7XG4gICAgY29uc3QgZml4dHVyZSA9IG5ldyBUZXN0Rml4dHVyZShcbiAgICAgIGludGVnVGVzdERpcixcbiAgICAgIHN0YWNrTmFtZVByZWZpeCxcbiAgICAgIGNvbnRleHQub3V0cHV0LFxuICAgICAgY29udGV4dC5hd3MpO1xuXG4gICAgbGV0IHN1Y2Nlc3MgPSB0cnVlO1xuICAgIHRyeSB7XG4gICAgICBsZXQgbW9kdWxlcyA9IFtcbiAgICAgICAgJ0Bhd3MtY2RrL2NvcmUnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNucycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtaWFtJyxcbiAgICAgICAgJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLXNzbScsXG4gICAgICAgICdAYXdzLWNkay9hd3MtZWNyLWFzc2V0cycsXG4gICAgICAgICdAYXdzLWNkay9hd3MtY2xvdWRmb3JtYXRpb24nLFxuICAgICAgICAnQGF3cy1jZGsvYXdzLWVjMicsXG4gICAgICBdO1xuICAgICAgaWYgKEZSQU1FV09SS19WRVJTSU9OKSB7XG4gICAgICAgIG1vZHVsZXMgPSBtb2R1bGVzLm1hcChtb2R1bGUgPT4gYCR7bW9kdWxlfUAke0ZSQU1FV09SS19WRVJTSU9OfWApO1xuICAgICAgfVxuICAgICAgYXdhaXQgZml4dHVyZS5zaGVsbChbJ25wbScsICdpbnN0YWxsJywgLi4ubW9kdWxlc10pO1xuXG4gICAgICBhd2FpdCBlbnN1cmVCb290c3RyYXBwZWQoZml4dHVyZSk7XG5cbiAgICAgIGF3YWl0IGJsb2NrKGZpeHR1cmUpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIHN1Y2Nlc3MgPSBmYWxzZTtcbiAgICAgIHRocm93IGU7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGF3YWl0IGZpeHR1cmUuZGlzcG9zZShzdWNjZXNzKTtcbiAgICB9XG4gIH07XG59XG5cbi8qKlxuICogRGVmYXVsdCB0ZXN0IGZpeHR1cmUgZm9yIG1vc3QgKGFsbD8pIGludGVnIHRlc3RzXG4gKlxuICogSXQncyBhIGNvbXBvc2l0aW9uIG9mIHdpdGhBd3Mvd2l0aENka0FwcCwgZXhwZWN0aW5nIHRoZSB0ZXN0IGJsb2NrIHRvIHRha2UgYSBgVGVzdEZpeHR1cmVgXG4gKiBvYmplY3QuXG4gKlxuICogV2UgY291bGQgaGF2ZSBwdXQgYHdpdGhBd3Mod2l0aENka0FwcChmaXh0dXJlID0+IHsgLy4uLiBhY3R1YWwgdGVzdCBoZXJlLi4uLyB9KSlgIGluIGV2ZXJ5XG4gKiB0ZXN0IGRlY2xhcmF0aW9uIGJ1dCBjZW50cmFsaXppbmcgaXQgaXMgZ29pbmcgdG8gbWFrZSBpdCBjb252ZW5pZW50IHRvIG1vZGlmeSBpbiB0aGUgZnV0dXJlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2l0aERlZmF1bHRGaXh0dXJlKGJsb2NrOiAoY29udGV4dDogVGVzdEZpeHR1cmUpID0+IFByb21pc2U8dm9pZD4pIHtcbiAgcmV0dXJuIHdpdGhBd3M8VGVzdENvbnRleHQ+KHdpdGhDZGtBcHAoYmxvY2spKTtcbiAgLy8gICAgICAgICAgICAgIF5+fn5+fiB0aGlzIGlzIGRpc2FwcG9pbnRpbmcgVHlwZVNjcmlwdCEgRmVlbHMgbGlrZSB5b3Ugc2hvdWxkIGhhdmUgYmVlbiBhYmxlIHRvIGRlcml2ZSB0aGlzLlxufVxuXG5leHBvcnQgaW50ZXJmYWNlIFNoZWxsT3B0aW9ucyBleHRlbmRzIGNoaWxkX3Byb2Nlc3MuU3Bhd25PcHRpb25zIHtcbiAgLyoqXG4gICAqIFByb3BlcnRpZXMgdG8gYWRkIHRvICdlbnYnXG4gICAqL1xuICBtb2RFbnY/OiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuXG4gIC8qKlxuICAgKiBEb24ndCBmYWlsIHdoZW4gZXhpdGluZyB3aXRoIGFuIGVycm9yXG4gICAqXG4gICAqIEBkZWZhdWx0IGZhbHNlXG4gICAqL1xuICBhbGxvd0VyckV4aXQ/OiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHRvIGNhcHR1cmUgc3RkZXJyXG4gICAqXG4gICAqIEBkZWZhdWx0IHRydWVcbiAgICovXG4gIGNhcHR1cmVTdGRlcnI/OiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBQYXNzIG91dHB1dCBoZXJlXG4gICAqL1xuICBvdXRwdXQ/OiBOb2RlSlMuV3JpdGFibGVTdHJlYW07XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2RrQ2xpT3B0aW9ucyBleHRlbmRzIFNoZWxsT3B0aW9ucyB7XG4gIG9wdGlvbnM/OiBzdHJpbmdbXTtcbiAgbmV2ZXJSZXF1aXJlQXBwcm92YWw/OiBib29sZWFuO1xuICB2ZXJib3NlPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBQcmVwYXJlIGEgdGFyZ2V0IGRpciBieXJlcGxpY2F0aW5nIGEgc291cmNlIGRpcmVjdG9yeVxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2xvbmVEaXJlY3Rvcnkoc291cmNlOiBzdHJpbmcsIHRhcmdldDogc3RyaW5nLCBvdXRwdXQ/OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0pIHtcbiAgYXdhaXQgc2hlbGwoWydybScsICctcmYnLCB0YXJnZXRdLCB7IG91dHB1dCB9KTtcbiAgYXdhaXQgc2hlbGwoWydta2RpcicsICctcCcsIHRhcmdldF0sIHsgb3V0cHV0IH0pO1xuICBhd2FpdCBzaGVsbChbJ2NwJywgJy1SJywgc291cmNlICsgJy8qJywgdGFyZ2V0XSwgeyBvdXRwdXQgfSk7XG59XG5cbmV4cG9ydCBjbGFzcyBUZXN0Rml4dHVyZSB7XG4gIHB1YmxpYyByZWFkb25seSBxdWFsaWZpZXIgPSByYW5kb21TdHJpbmcoKS5zdWJzdHIoMCwgMTApO1xuICBwcml2YXRlIHJlYWRvbmx5IGJ1Y2tldHNUb0RlbGV0ZSA9IG5ldyBBcnJheTxzdHJpbmc+KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHVibGljIHJlYWRvbmx5IGludGVnVGVzdERpcjogc3RyaW5nLFxuICAgIHB1YmxpYyByZWFkb25seSBzdGFja05hbWVQcmVmaXg6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgb3V0cHV0OiBOb2RlSlMuV3JpdGFibGVTdHJlYW0sXG4gICAgcHVibGljIHJlYWRvbmx5IGF3czogQXdzQ2xpZW50cykge1xuICB9XG5cbiAgcHVibGljIGxvZyhzOiBzdHJpbmcpIHtcbiAgICB0aGlzLm91dHB1dC53cml0ZShgJHtzfVxcbmApO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIHNoZWxsKGNvbW1hbmQ6IHN0cmluZ1tdLCBvcHRpb25zOiBPbWl0PFNoZWxsT3B0aW9ucywgJ2N3ZCd8J291dHB1dCc+ID0ge30pOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiBzaGVsbChjb21tYW5kLCB7XG4gICAgICBvdXRwdXQ6IHRoaXMub3V0cHV0LFxuICAgICAgY3dkOiB0aGlzLmludGVnVGVzdERpcixcbiAgICAgIC4uLm9wdGlvbnMsXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgYXN5bmMgY2RrRGVwbG95KHN0YWNrTmFtZXM6IHN0cmluZyB8IHN0cmluZ1tdLCBvcHRpb25zOiBDZGtDbGlPcHRpb25zID0ge30pIHtcbiAgICBzdGFja05hbWVzID0gdHlwZW9mIHN0YWNrTmFtZXMgPT09ICdzdHJpbmcnID8gW3N0YWNrTmFtZXNdIDogc3RhY2tOYW1lcztcblxuICAgIGNvbnN0IG5ldmVyUmVxdWlyZUFwcHJvdmFsID0gb3B0aW9ucy5uZXZlclJlcXVpcmVBcHByb3ZhbCA/PyB0cnVlO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVwbG95JyxcbiAgICAgIC4uLihuZXZlclJlcXVpcmVBcHByb3ZhbCA/IFsnLS1yZXF1aXJlLWFwcHJvdmFsPW5ldmVyJ10gOiBbXSksIC8vIERlZmF1bHQgdG8gbm8gYXBwcm92YWwgaW4gYW4gdW5hdHRlbmRlZCB0ZXN0XG4gICAgICAuLi4ob3B0aW9ucy5vcHRpb25zID8/IFtdKSxcbiAgICAgIC4uLnRoaXMuZnVsbFN0YWNrTmFtZShzdGFja05hbWVzKV0sIG9wdGlvbnMpO1xuICB9XG5cbiAgcHVibGljIGFzeW5jIGNka0Rlc3Ryb3koc3RhY2tOYW1lczogc3RyaW5nIHwgc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIHN0YWNrTmFtZXMgPSB0eXBlb2Ygc3RhY2tOYW1lcyA9PT0gJ3N0cmluZycgPyBbc3RhY2tOYW1lc10gOiBzdGFja05hbWVzO1xuXG4gICAgcmV0dXJuIHRoaXMuY2RrKFsnZGVzdHJveScsXG4gICAgICAnLWYnLCAvLyBXZSBuZXZlciB3YW50IGEgcHJvbXB0IGluIGFuIHVuYXR0ZW5kZWQgdGVzdFxuICAgICAgLi4uKG9wdGlvbnMub3B0aW9ucyA/PyBbXSksXG4gICAgICAuLi50aGlzLmZ1bGxTdGFja05hbWUoc3RhY2tOYW1lcyldLCBvcHRpb25zKTtcbiAgfVxuXG4gIHB1YmxpYyBhc3luYyBjZGsoYXJnczogc3RyaW5nW10sIG9wdGlvbnM6IENka0NsaU9wdGlvbnMgPSB7fSkge1xuICAgIGNvbnN0IHZlcmJvc2UgPSBvcHRpb25zLnZlcmJvc2UgPz8gdHJ1ZTtcblxuICAgIHJldHVybiB0aGlzLnNoZWxsKFsnY2RrJywgLi4uKHZlcmJvc2UgPyBbJy12J10gOiBbXSksIC4uLmFyZ3NdLCB7XG4gICAgICAuLi5vcHRpb25zLFxuICAgICAgbW9kRW52OiB7XG4gICAgICAgIEFXU19SRUdJT046IHRoaXMuYXdzLnJlZ2lvbixcbiAgICAgICAgQVdTX0RFRkFVTFRfUkVHSU9OOiB0aGlzLmF3cy5yZWdpb24sXG4gICAgICAgIFNUQUNLX05BTUVfUFJFRklYOiB0aGlzLnN0YWNrTmFtZVByZWZpeCxcbiAgICAgICAgLi4ub3B0aW9ucy5tb2RFbnYsXG4gICAgICB9LFxuICAgIH0pO1xuICB9XG5cbiAgcHVibGljIGZ1bGxTdGFja05hbWUoc3RhY2tOYW1lOiBzdHJpbmcpOiBzdHJpbmc7XG4gIHB1YmxpYyBmdWxsU3RhY2tOYW1lKHN0YWNrTmFtZXM6IHN0cmluZ1tdKTogc3RyaW5nW107XG4gIHB1YmxpYyBmdWxsU3RhY2tOYW1lKHN0YWNrTmFtZXM6IHN0cmluZyB8IHN0cmluZ1tdKTogc3RyaW5nIHwgc3RyaW5nW10ge1xuICAgIGlmICh0eXBlb2Ygc3RhY2tOYW1lcyA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHJldHVybiBgJHt0aGlzLnN0YWNrTmFtZVByZWZpeH0tJHtzdGFja05hbWVzfWA7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiBzdGFja05hbWVzLm1hcChzID0+IGAke3RoaXMuc3RhY2tOYW1lUHJlZml4fS0ke3N9YCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEFwcGVuZCB0aGlzIHRvIHRoZSBsaXN0IG9mIGJ1Y2tldHMgdG8gcG90ZW50aWFsbHkgZGVsZXRlXG4gICAqXG4gICAqIEF0IHRoZSBlbmQgb2YgYSB0ZXN0LCB3ZSBjbGVhbiB1cCBidWNrZXRzIHRoYXQgbWF5IG5vdCBoYXZlIGdvdHRlbiBkZXN0cm95ZWRcbiAgICogKGZvciB3aGF0ZXZlciByZWFzb24pLlxuICAgKi9cbiAgcHVibGljIHJlbWVtYmVyVG9EZWxldGVCdWNrZXQoYnVja2V0TmFtZTogc3RyaW5nKSB7XG4gICAgdGhpcy5idWNrZXRzVG9EZWxldGUucHVzaChidWNrZXROYW1lKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhbnVwIGxlZnRvdmVyIHN0YWNrcyBhbmQgYnVja2V0c1xuICAgKi9cbiAgcHVibGljIGFzeW5jIGRpc3Bvc2Uoc3VjY2VzczogYm9vbGVhbikge1xuICAgIGNvbnN0IHN0YWNrc1RvRGVsZXRlID0gYXdhaXQgdGhpcy5kZWxldGVhYmxlU3RhY2tzKHRoaXMuc3RhY2tOYW1lUHJlZml4KTtcblxuICAgIC8vIEJvb3RzdHJhcCBzdGFja3MgaGF2ZSBidWNrZXRzIHRoYXQgbmVlZCB0byBiZSBjbGVhbmVkXG4gICAgY29uc3QgYnVja2V0TmFtZXMgPSBzdGFja3NUb0RlbGV0ZS5tYXAoc3RhY2sgPT4gb3V0cHV0RnJvbVN0YWNrKCdCdWNrZXROYW1lJywgc3RhY2spKS5maWx0ZXIoZGVmaW5lZCk7XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoYnVja2V0TmFtZXMubWFwKGIgPT4gdGhpcy5hd3MuZW1wdHlCdWNrZXQoYikpKTtcblxuICAgIC8vIEJvb3RzdHJhcCBzdGFja3MgaGF2ZSBFQ1IgcmVwb3NpdG9yaWVzIHdpdGggaW1hZ2VzIHdoaWNoIHNob3VsZCBiZSBkZWxldGVkXG4gICAgY29uc3QgaW1hZ2VSZXBvc2l0b3J5TmFtZXMgPSBzdGFja3NUb0RlbGV0ZS5tYXAoc3RhY2sgPT4gb3V0cHV0RnJvbVN0YWNrKCdJbWFnZVJlcG9zaXRvcnlOYW1lJywgc3RhY2spKS5maWx0ZXIoZGVmaW5lZCk7XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoaW1hZ2VSZXBvc2l0b3J5TmFtZXMubWFwKHIgPT4gdGhpcy5hd3MuZGVsZXRlSW1hZ2VSZXBvc2l0b3J5KHIpKSk7XG5cbiAgICBhd2FpdCB0aGlzLmF3cy5kZWxldGVTdGFja3MoLi4uc3RhY2tzVG9EZWxldGUubWFwKHMgPT4gcy5TdGFja05hbWUpKTtcblxuICAgIC8vIFdlIG1pZ2h0IGhhdmUgbGVha2VkIHNvbWUgYnVja2V0cyBieSB1cGdyYWRpbmcgdGhlIGJvb3RzdHJhcCBzdGFjay4gQmVcbiAgICAvLyBzdXJlIHRvIGNsZWFuIGV2ZXJ5dGhpbmcuXG4gICAgZm9yIChjb25zdCBidWNrZXQgb2YgdGhpcy5idWNrZXRzVG9EZWxldGUpIHtcbiAgICAgIGF3YWl0IHRoaXMuYXdzLmRlbGV0ZUJ1Y2tldChidWNrZXQpO1xuICAgIH1cblxuICAgIC8vIElmIHRoZSB0ZXN0cyBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5LCBoYXBwaWx5IGRlbGV0ZSB0aGUgZml4dHVyZVxuICAgIC8vIChvdGhlcndpc2UgbGVhdmUgaXQgZm9yIGh1bWFucyB0byBpbnNwZWN0KVxuICAgIGlmIChzdWNjZXNzKSB7XG4gICAgICByaW1yYWYodGhpcy5pbnRlZ1Rlc3REaXIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm4gdGhlIHN0YWNrcyBzdGFydGluZyB3aXRoIG91ciB0ZXN0aW5nIHByZWZpeCB0aGF0IHNob3VsZCBiZSBkZWxldGVkXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGRlbGV0ZWFibGVTdGFja3MocHJlZml4OiBzdHJpbmcpOiBQcm9taXNlPEFXUy5DbG91ZEZvcm1hdGlvbi5TdGFja1tdPiB7XG4gICAgY29uc3Qgc3RhdHVzRmlsdGVyID0gW1xuICAgICAgJ0NSRUFURV9JTl9QUk9HUkVTUycsICdDUkVBVEVfRkFJTEVEJywgJ0NSRUFURV9DT01QTEVURScsXG4gICAgICAnUk9MTEJBQ0tfSU5fUFJPR1JFU1MnLCAnUk9MTEJBQ0tfRkFJTEVEJywgJ1JPTExCQUNLX0NPTVBMRVRFJyxcbiAgICAgICdERUxFVEVfRkFJTEVEJyxcbiAgICAgICdVUERBVEVfSU5fUFJPR1JFU1MnLCAnVVBEQVRFX0NPTVBMRVRFX0NMRUFOVVBfSU5fUFJPR1JFU1MnLFxuICAgICAgJ1VQREFURV9DT01QTEVURScsICdVUERBVEVfUk9MTEJBQ0tfSU5fUFJPR1JFU1MnLFxuICAgICAgJ1VQREFURV9ST0xMQkFDS19GQUlMRUQnLFxuICAgICAgJ1VQREFURV9ST0xMQkFDS19DT01QTEVURV9DTEVBTlVQX0lOX1BST0dSRVNTJyxcbiAgICAgICdVUERBVEVfUk9MTEJBQ0tfQ09NUExFVEUnLCAnUkVWSUVXX0lOX1BST0dSRVNTJyxcbiAgICAgICdJTVBPUlRfSU5fUFJPR1JFU1MnLCAnSU1QT1JUX0NPTVBMRVRFJyxcbiAgICAgICdJTVBPUlRfUk9MTEJBQ0tfSU5fUFJPR1JFU1MnLCAnSU1QT1JUX1JPTExCQUNLX0ZBSUxFRCcsXG4gICAgICAnSU1QT1JUX1JPTExCQUNLX0NPTVBMRVRFJyxcbiAgICBdO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0aGlzLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7fSk7XG5cbiAgICByZXR1cm4gKHJlc3BvbnNlLlN0YWNrcyA/PyBbXSlcbiAgICAgIC5maWx0ZXIocyA9PiBzLlN0YWNrTmFtZS5zdGFydHNXaXRoKHByZWZpeCkpXG4gICAgICAuZmlsdGVyKHMgPT4gc3RhdHVzRmlsdGVyLmluY2x1ZGVzKHMuU3RhY2tTdGF0dXMpKVxuICAgICAgLmZpbHRlcihzID0+IHMuUm9vdElkID09PSB1bmRlZmluZWQpOyAvLyBPbmx5IGRlbGV0ZSBwYXJlbnQgc3RhY2tzLiBOZXN0ZWQgc3RhY2tzIGFyZSBkZWxldGVkIGluIHRoZSBwcm9jZXNzXG4gIH1cbn1cblxuLyoqXG4gKiBQZXJmb3JtIGEgb25lLXRpbWUgcXVpY2sgc2FuaXR5IGNoZWNrIHRoYXQgdGhlIEFXUyBjbGllbnRzIGhhcyBwcm9wZXJseSBjb25maWd1cmVkIGNyZWRlbnRpYWxzXG4gKlxuICogSWYgd2UgZG9uJ3QgZG8gdGhpcywgY2FsbHMgYXJlIGdvaW5nIHRvIGZhaWwgYW5kIHRoZXknbGwgYmUgcmV0cmllZCBhbmQgZXZlcnl0aGluZyB3aWxsIHRha2VcbiAqIGZvcmV2ZXIgYmVmb3JlIHRoZSB1c2VyIG5vdGljZXMgYSBzaW1wbGUgbWlzY29uZmlndXJhdGlvbi5cbiAqXG4gKiBXZSBjYW4ndCBjaGVjayBmb3IgdGhlIHByZXNlbmNlIG9mIGVudmlyb25tZW50IHZhcmlhYmxlcyBzaW5jZSBjcmVkZW50aWFscyBjb3VsZCBjb21lIGZyb21cbiAqIGFueXdoZXJlLCBzbyBkbyBzaW1wbGUgYWNjb3VudCByZXRyaWV2YWwuXG4gKlxuICogT25seSBkbyBpdCBvbmNlIHBlciBwcm9jZXNzLlxuICovXG5hc3luYyBmdW5jdGlvbiBzYW5pdHlDaGVjayhhd3M6IEF3c0NsaWVudHMpIHtcbiAgaWYgKHNhbml0eUNoZWNrZWQgPT09IHVuZGVmaW5lZCkge1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBhd3MuYWNjb3VudCgpO1xuICAgICAgc2FuaXR5Q2hlY2tlZCA9IHRydWU7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgc2FuaXR5Q2hlY2tlZCA9IGZhbHNlO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBBV1MgY3JlZGVudGlhbHMgcHJvYmFibHkgbm90IGNvbmZpZ3VyZWQsIGdvdCBlcnJvcjogJHtlLm1lc3NhZ2V9YCk7XG4gICAgfVxuICB9XG4gIGlmICghc2FuaXR5Q2hlY2tlZCkge1xuICAgIHRocm93IG5ldyBFcnJvcignQVdTIGNyZWRlbnRpYWxzIHByb2JhYmx5IG5vdCBjb25maWd1cmVkLCBzZWUgcHJldmlvdXMgZXJyb3InKTtcbiAgfVxufVxubGV0IHNhbml0eUNoZWNrZWQ6IGJvb2xlYW4gfCB1bmRlZmluZWQ7XG5cbi8qKlxuICogTWFrZSBzdXJlIHRoYXQgdGhlIGdpdmVuIGVudmlyb25tZW50IGlzIGJvb3RzdHJhcHBlZFxuICpcbiAqIFNpbmNlIHdlIGdvIHN0cmlwaW5nIGFjcm9zcyByZWdpb25zLCBpdCdzIGdvaW5nIHRvIHN1Y2sgZG9pbmcgdGhpc1xuICogYnkgaGFuZCBzbyBsZXQncyBqdXN0IG1hc3MtYXV0b21hdGUgaXQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVuc3VyZUJvb3RzdHJhcHBlZChmaXh0dXJlOiBUZXN0Rml4dHVyZSkge1xuICAvLyBPbGQtc3R5bGUgYm9vdHN0cmFwIHN0YWNrIHdpdGggZGVmYXVsdCBuYW1lXG4gIGlmIChhd2FpdCBmaXh0dXJlLmF3cy5zdGFja1N0YXR1cygnQ0RLVG9vbGtpdCcpID09PSB1bmRlZmluZWQpIHtcbiAgICBhd2FpdCBmaXh0dXJlLmNkayhbJ2Jvb3RzdHJhcCcsIGBhd3M6Ly8ke2F3YWl0IGZpeHR1cmUuYXdzLmFjY291bnQoKX0vJHtmaXh0dXJlLmF3cy5yZWdpb259YF0pO1xuICB9XG59XG5cbi8qKlxuICogQSBzaGVsbCBjb21tYW5kIHRoYXQgZG9lcyB3aGF0IHlvdSB3YW50XG4gKlxuICogSXMgcGxhdGZvcm0tYXdhcmUsIGhhbmRsZXMgZXJyb3JzIG5pY2VseS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNoZWxsKGNvbW1hbmQ6IHN0cmluZ1tdLCBvcHRpb25zOiBTaGVsbE9wdGlvbnMgPSB7fSk6IFByb21pc2U8c3RyaW5nPiB7XG4gIGlmIChvcHRpb25zLm1vZEVudiAmJiBvcHRpb25zLmVudikge1xuICAgIHRocm93IG5ldyBFcnJvcignVXNlIGVpdGhlciBlbnYgb3IgbW9kRW52IGJ1dCBub3QgYm90aCcpO1xuICB9XG5cbiAgb3B0aW9ucy5vdXRwdXQ/LndyaXRlKGDwn5K7ICR7Y29tbWFuZC5qb2luKCcgJyl9XFxuYCk7XG5cbiAgY29uc3QgZW52ID0gb3B0aW9ucy5lbnYgPz8gKG9wdGlvbnMubW9kRW52ID8geyAuLi5wcm9jZXNzLmVudiwgLi4ub3B0aW9ucy5tb2RFbnYgfSA6IHVuZGVmaW5lZCk7XG5cbiAgY29uc3QgY2hpbGQgPSBjaGlsZF9wcm9jZXNzLnNwYXduKGNvbW1hbmRbMF0sIGNvbW1hbmQuc2xpY2UoMSksIHtcbiAgICAuLi5vcHRpb25zLFxuICAgIGVudixcbiAgICAvLyBOZWVkIHRoaXMgZm9yIFdpbmRvd3Mgd2hlcmUgd2Ugd2FudCAuY21kIGFuZCAuYmF0IHRvIGJlIGZvdW5kIGFzIHdlbGwuXG4gICAgc2hlbGw6IHRydWUsXG4gICAgc3RkaW86IFsnaWdub3JlJywgJ3BpcGUnLCAncGlwZSddLFxuICB9KTtcblxuICByZXR1cm4gbmV3IFByb21pc2U8c3RyaW5nPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgY29uc3Qgc3Rkb3V0ID0gbmV3IEFycmF5PEJ1ZmZlcj4oKTtcbiAgICBjb25zdCBzdGRlcnIgPSBuZXcgQXJyYXk8QnVmZmVyPigpO1xuXG4gICAgY2hpbGQuc3Rkb3V0IS5vbignZGF0YScsIGNodW5rID0+IHtcbiAgICAgIG9wdGlvbnMub3V0cHV0Py53cml0ZShjaHVuayk7XG4gICAgICBzdGRvdXQucHVzaChjaHVuayk7XG4gICAgfSk7XG5cbiAgICBjaGlsZC5zdGRlcnIhLm9uKCdkYXRhJywgY2h1bmsgPT4ge1xuICAgICAgb3B0aW9ucy5vdXRwdXQ/LndyaXRlKGNodW5rKTtcbiAgICAgIGlmIChvcHRpb25zLmNhcHR1cmVTdGRlcnIgPz8gdHJ1ZSkge1xuICAgICAgICBzdGRlcnIucHVzaChjaHVuayk7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICBjaGlsZC5vbmNlKCdlcnJvcicsIHJlamVjdCk7XG5cbiAgICBjaGlsZC5vbmNlKCdjbG9zZScsIGNvZGUgPT4ge1xuICAgICAgaWYgKGNvZGUgPT09IDAgfHwgb3B0aW9ucy5hbGxvd0VyckV4aXQpIHtcbiAgICAgICAgcmVzb2x2ZSgoQnVmZmVyLmNvbmNhdChzdGRvdXQpLnRvU3RyaW5nKCd1dGYtOCcpICsgQnVmZmVyLmNvbmNhdChzdGRlcnIpLnRvU3RyaW5nKCd1dGYtOCcpKS50cmltKCkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgJyR7Y29tbWFuZC5qb2luKCcgJyl9JyBleGl0ZWQgd2l0aCBlcnJvciBjb2RlICR7Y29kZX1gKSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH0pO1xufVxuXG5mdW5jdGlvbiBkZWZpbmVkPEE+KHg6IEEpOiB4IGlzIE5vbk51bGxhYmxlPEE+IHtcbiAgcmV0dXJuIHggIT09IHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBybSAtcmYgcmVpbXBsZW1lbnRhdGlvbiwgZG9uJ3Qgd2FudCB0byBkZXBlbmQgb24gYW4gTlBNIHBhY2thZ2UgZm9yIHRoaXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJpbXJhZihmc1BhdGg6IHN0cmluZykge1xuICB0cnkge1xuICAgIGNvbnN0IGlzRGlyID0gZnMubHN0YXRTeW5jKGZzUGF0aCkuaXNEaXJlY3RvcnkoKTtcblxuICAgIGlmIChpc0Rpcikge1xuICAgICAgZm9yIChjb25zdCBmaWxlIG9mIGZzLnJlYWRkaXJTeW5jKGZzUGF0aCkpIHtcbiAgICAgICAgcmltcmFmKHBhdGguam9pbihmc1BhdGgsIGZpbGUpKTtcbiAgICAgIH1cbiAgICAgIGZzLnJtZGlyU3luYyhmc1BhdGgpO1xuICAgIH0gZWxzZSB7XG4gICAgICBmcy51bmxpbmtTeW5jKGZzUGF0aCk7XG4gICAgfVxuICB9IGNhdGNoIChlKSB7XG4gICAgLy8gV2Ugd2lsbCBzdXJ2aXZlIEVOT0VOVFxuICAgIGlmIChlLmNvZGUgIT09ICdFTk9FTlQnKSB7IHRocm93IGU7IH1cbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gcmFuZG9tU3RyaW5nKCkge1xuICAvLyBDcmF6eVxuICByZXR1cm4gTWF0aC5yYW5kb20oKS50b1N0cmluZygzNikucmVwbGFjZSgvW15hLXowLTldKy9nLCAnJyk7XG59Il19 \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts index 01829ebff413f..c1049e330d77e 100644 --- a/packages/aws-cdk/test/integ/cli/cdk-helpers.ts +++ b/packages/aws-cdk/test/integ/cli/cdk-helpers.ts @@ -10,7 +10,10 @@ const REGIONS = process.env.AWS_REGIONS ? process.env.AWS_REGIONS.split(',') : [process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? 'us-east-1']; +const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION; + process.stdout.write(`Using regions: ${REGIONS}\n`); +process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION}\n`); const REGION_POOL = new ResourcePool(REGIONS); @@ -58,7 +61,7 @@ export function withCdkApp(block: (context: let success = true; try { - await fixture.shell(['npm', 'install', + let modules = [ '@aws-cdk/core', '@aws-cdk/aws-sns', '@aws-cdk/aws-iam', @@ -66,7 +69,12 @@ export function withCdkApp(block: (context: '@aws-cdk/aws-ssm', '@aws-cdk/aws-ecr-assets', '@aws-cdk/aws-cloudformation', - '@aws-cdk/aws-ec2']); + '@aws-cdk/aws-ec2', + ]; + if (FRAMEWORK_VERSION) { + modules = modules.map(module => `${module}@${FRAMEWORK_VERSION}`); + } + await fixture.shell(['npm', 'install', ...modules]); await ensureBootstrapped(fixture); diff --git a/packages/aws-cdk/test/integ/cli/test.sh b/packages/aws-cdk/test/integ/cli/test.sh index 42b4994b72ce9..05be97e5312c3 100755 --- a/packages/aws-cdk/test/integ/cli/test.sh +++ b/packages/aws-cdk/test/integ/cli/test.sh @@ -6,12 +6,6 @@ echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' echo 'CLI Integration Tests' echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' -current_version=$(node -p "require('${scriptdir}/../../../package.json').version") - -# This allows injecting different versions, not just the current one. -# Useful when testing. -export VERSION_UNDER_TEST=${VERSION_UNDER_TEST:-${current_version}} - cd $scriptdir # Install these dependencies that the tests (written in Jest) need. diff --git a/packages/aws-cdk/test/integ/github-helpers.ts b/packages/aws-cdk/test/integ/github-helpers.ts new file mode 100644 index 0000000000000..3a0bc1fb84195 --- /dev/null +++ b/packages/aws-cdk/test/integ/github-helpers.ts @@ -0,0 +1,30 @@ +import { Octokit } from '@octokit/rest'; +import * as semver from 'semver'; + +module.exports.fetchPreviousVersion = async function(base: string) { + const token = process.env.GITHUB_TOKEN; + if (!token) { + throw new Error('GITHUB_TOKEN must be set'); + } + + const github = new Octokit({ auth: token }); + const releases = await github.repos.listReleases({ + owner: 'aws', + repo: 'aws-cdk', + }); + + // this returns a list in decsending order, newest releases first + for (const release of releases.data) { + const version = release.name.replace('v', ''); + if (semver.lt(version, base)) { + return version; + } + } + throw new Error(`Unable to find previous version of ${base}`); + +}; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +require('make-runnable/custom')({ + printOutputFrame: false, +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/run-against-dist b/packages/aws-cdk/test/integ/run-against-dist index 3b314abc0e2c4..ed17ddee88243 100755 --- a/packages/aws-cdk/test/integ/run-against-dist +++ b/packages/aws-cdk/test/integ/run-against-dist @@ -23,8 +23,11 @@ if [[ ! -f $dist_root/build.json ]]; then exit 1 fi +export CANDIDATE_VERSION=$(node -p "require('${dist_root}/build.json').version") +export TEST_RUNNER=dist serve_npm_packages +install_cli prepare_java_packages prepare_nuget_packages diff --git a/packages/aws-cdk/test/integ/run-against-dist.bash b/packages/aws-cdk/test/integ/run-against-dist.bash index a77f564f1895f..83c29e82a5620 100644 --- a/packages/aws-cdk/test/integ/run-against-dist.bash +++ b/packages/aws-cdk/test/integ/run-against-dist.bash @@ -4,7 +4,6 @@ npmws=/tmp/cdk-rundist rm -rf $npmws mkdir -p $npmws - # This script must create 1 or 2 traps, and the 'trap' command will replace # the previous trap, so get some 'dynamic traps' mechanism in place TRAPS=() @@ -35,66 +34,24 @@ function serve_npm_packages() { return 1 fi - local_cli_version="$(node -e "console.log(require('${dist_root}/build.json').version)")" + #------------------------------------------------------------------------------ + # Start a mock npm repository from the given tarballs + #------------------------------------------------------------------------------ + header "Starting local NPM Repository (Serving version ${CANDIDATE_VERSION})" tarballs_glob="$dist_root/js/*.tgz" - if [ ! -z "${USE_PUBLISHED_FRAMEWORK_VERSION:-}" ]; then - - echo "Testing against latest published versions of the framework" - - header "Installing aws-cdk from local tarballs..." - # Need 'npm install --prefix' otherwise it goes to the wrong directory - (cd ${npmws} && npx serve-npm-tarballs --glob "${tarballs_glob}" -- npm install --prefix $npmws aws-cdk@${local_cli_version}) - export PATH=$npmws/node_modules/.bin:$PATH - - else - - echo "Testing against local versions of the framework" - - #------------------------------------------------------------------------------ - # Start a mock npm repository from the given tarballs - #------------------------------------------------------------------------------ - header "Starting local NPM Repository" - - # When using '--daemon', 'npm install' first so the files are permanent, or - # 'npx' will remove them too soon. - npm install serve-npm-tarballs - eval $(npx serve-npm-tarballs --glob "${tarballs_glob}" --daemon) - TRAPS+=("kill $SERVE_NPM_TARBALLS_PID") - - header "Installing aws-cdk from local tarballs..." - # Need 'npm install --prefix' otherwise it goes to the wrong directory - (cd ${npmws} && npm install --prefix $npmws aws-cdk@${local_cli_version}) - export PATH=$npmws/node_modules/.bin:$PATH - - fi - - # a bit silly, but it verifies the PATH exports and just makes sure - # that we run 'cdk' commands we use the version we just installed. - verify_installed_cli_version ${local_cli_version} - + # When using '--daemon', 'npm install' first so the files are permanent, or + # 'npx' will remove them too soon. + npm install serve-npm-tarballs + eval $(npx serve-npm-tarballs --glob "${tarballs_glob}" --daemon) + TRAPS+=("kill $SERVE_NPM_TARBALLS_PID") } -# Make sure that installed CLI matches the build version -function verify_installed_cli_version() { - - expected_version=$1 - - header "Expected CDK version: ${expected_version}" - - log "Found CDK: $(type -p cdk)" - - # Execute "cdk --version" as a validation that the toolkit is installed - local actual_version="$(cdk --version | cut -d" " -f1)" - - if [ "${expected_version}" != "${actual_version}" ]; then - log "Mismatched CDK version. Expected: ${expected_version}, actual: ${actual_version}" - cdk --version - exit 1 - else - log "Verified CDK version is: ${expected_version}" - fi +function install_cli() { + echo "Installing CLI aws-cdk@${CANDIDATE_VERSION}" + (cd ${npmws} && npm install --prefix $npmws aws-cdk@${CANDIDATE_VERSION}) + export PATH=$npmws/node_modules/.bin:$PATH } function prepare_java_packages() { @@ -180,4 +137,4 @@ function prepare_python_packages() { function pip() { pip_ "$@" -} +} \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/run-against-release b/packages/aws-cdk/test/integ/run-against-release index 5ea9bf34ff7d4..0865bd331a25d 100755 --- a/packages/aws-cdk/test/integ/run-against-release +++ b/packages/aws-cdk/test/integ/run-against-release @@ -14,6 +14,7 @@ mkdir -p $npmws # Install the CLI and put it on the PATH (cd $npmws && npm install aws-cdk) export PATH=$npmws/node_modules/.bin:$PATH +export TEST_RUNNER=release # Run the inner script exec "$@" diff --git a/packages/aws-cdk/test/integ/run-against-repo b/packages/aws-cdk/test/integ/run-against-repo index 601097c880d18..420850f2cd951 100755 --- a/packages/aws-cdk/test/integ/run-against-repo +++ b/packages/aws-cdk/test/integ/run-against-repo @@ -15,6 +15,7 @@ fi export REPO_ROOT="$repo_root" export ORIGINAL_NPM="$(type -p npm)" +export TEST_RUNNER=repo export PATH="$cli_dir/bin:$cli_dir/test/integ/run-wrappers/repo:$PATH" hash -r diff --git a/packages/aws-cdk/test/integ/run-wrappers/repo/npm b/packages/aws-cdk/test/integ/run-wrappers/repo/npm index e90e296bbef1d..4a49bd85621b5 100755 --- a/packages/aws-cdk/test/integ/run-wrappers/repo/npm +++ b/packages/aws-cdk/test/integ/run-wrappers/repo/npm @@ -5,11 +5,7 @@ command=$1 lerna=$REPO_ROOT/node_modules/.bin/lerna -if [ ! -z "${USE_PUBLISHED_FRAMEWORK_VERSION:-}" ]; then - # simply pass through to npm and install the published package. - echo "Running original NPM command since we are testing against published framework version" - exec $ORIGINAL_NPM "$@" -elif [[ "$command" == "install" || "$command" == "i" ]]; then +if [[ "$command" == "install" || "$command" == "i" ]]; then npmargs="install" shift @@ -33,3 +29,5 @@ elif [[ "$command" == "install" || "$command" == "i" ]]; then exec $ORIGINAL_NPM $npmargs fi + +exec $ORIGINAL_NPM "$@" \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh index dc62bab8f698c..ff50aa5f69a2b 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-current-code.sh @@ -1,93 +1,11 @@ #!/bin/bash # # Run our integration tests in regression mode against the -# local code of the framework and CLI. -# -# 1. Download the latest released version of the aws-cdk repo. -# 2. Copy its integration tests directory ((test/integ/cli)) here. -# 3. Run the integration tests from the copied directory. +# candidate version of the framework, which is the one we just packed. # set -euo pipefail integdir=$(cd $(dirname $0) && pwd) -temp_dir=$(mktemp -d) - -function cleanup { - # keep junit file to allow report creation - cp ${integ_under_test}/coverage/junit.xml . - rm -rf ${temp_dir} - rm -rf ${integ_under_test} -} - - -function get_latest_published_version { - - # fetch the latest published version number. - # this is stored in the github releases under the 'latest' tag. - - github_headers="" - if [[ "${GITHUB_TOKEN:-}" != "" ]]; then - github_headers="Authorization: token ${GITHUB_TOKEN}" - fi - - out="${temp_dir}/aws-cdk-release.json" - - curl -Ss --dump-header /dev/null -H "$github_headers" https://api.github.com/repos/aws/aws-cdk/releases/latest > ${out} - latest_version=$(node -p "require('${out}').name") - echo ${latest_version} -} - -function download_repo { - - # we need to download the repo code from GitHub in order to extract - # the integration tests that were present in that version of the repo. - # - # Download just the CLI tarball, which contains the tests. We can't - # use 'npm pack' here to obtain the tarball, as 'npm' commands may - # be redirected to a local Verdaccio. - # - # Rather than introducing another level of indirection to work around - # that, just go to npmjs.com directly. - - # Strip off leading 'v' - version=${1#v} - - out="${temp_dir}/.repo.tar.gz" - - curl -Ssf -L -o ${out} "https://registry.npmjs.org/aws-cdk/-/aws-cdk-${version}.tgz" - tar -zxf ${out} -C ${temp_dir} -} - -# this allows injecting different versions to be treated as the baseline -# of the regression. usually this would just be the latest published version, -# but this can even be a branch name during development and testing. -VERSION_UNDER_TEST=${VERSION_UNDER_TEST:-$(get_latest_published_version)} - -trap cleanup INT EXIT - -echo "Downloading aws-cdk repo version ${VERSION_UNDER_TEST}" -download_repo ${VERSION_UNDER_TEST} - -# remove '/' that is prevelant in our branch names but causes -# bad behvaior when using it as directory names. -sanitized_version=$(sed 's/\//-/g' <<< "${VERSION_UNDER_TEST}") - -# Test must be created in the same directory here because the script files liberally -# include files from '..' and they have to exist. -integ_under_test=${integdir}/cli-backwards-tests-${sanitized_version} -rm -rf ${integ_under_test} -echo "Copying integration tests of version ${VERSION_UNDER_TEST} to ${integ_under_test} (dont worry, its gitignored)" -cp -r ${temp_dir}/package/test/integ/cli ${integ_under_test} - -patch_dir="${integdir}/cli-regression-patches/${VERSION_UNDER_TEST}" -# delete possibly stale junit.xml file -rm -f ${integ_under_test}/junit.xml -if [[ -d "$patch_dir" ]]; then - echo "Hotpatching the tests with files from $patch_dir" >&2 - cp -r "$patch_dir"/* ${integ_under_test} -fi - -echo "Running integration tests of version ${VERSION_UNDER_TEST} from ${integ_under_test}" -set -x +source ${integdir}/test-cli-regression.bash -VERSION_UNDER_TEST=${VERSION_UNDER_TEST} ${integ_under_test}/test.sh "$@" +run_regression_against_framework_version CANDIDATE_VERSION diff --git a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh index 6d0133ca06108..8b670eb7b793d 100755 --- a/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh +++ b/packages/aws-cdk/test/integ/test-cli-regression-against-latest-release.sh @@ -1,8 +1,11 @@ #!/bin/bash +# +# Run our integration tests in regression mode against the +# previous version of the framework, relative to the version being packed now. +# set -euo pipefail integdir=$(cd $(dirname $0) && pwd) -# run the regular regression test but pass the env variable that will -# eventually instruct our runners and wrappers to install the framework -# from npmjs.org rather then using the local code. -USE_PUBLISHED_FRAMEWORK_VERSION=True ${integdir}/test-cli-regression-against-current-code.sh "$@" +source ${integdir}/test-cli-regression.bash + +run_regression_against_framework_version PREVIOUS_VERSION diff --git a/packages/aws-cdk/test/integ/test-cli-regression.bash b/packages/aws-cdk/test/integ/test-cli-regression.bash new file mode 100644 index 0000000000000..12cbe81791d65 --- /dev/null +++ b/packages/aws-cdk/test/integ/test-cli-regression.bash @@ -0,0 +1,77 @@ +#!/bin/bash +# +# Helper functions for CLI regression tests. +# +set -euo pipefail +integdir=$(cd $(dirname $0) && pwd) + +# Run our integration tests in regression mode. +# +# 1. Figure out what was the previous (relative to the current candidate) version we published. +# 2. Download the integration tests artifact from that version. +# 2. Copy its integration tests directory ((test/integ/cli)) here. +# 3. Run the integration tests from the copied directory. +# +# Positional Arugments: +# +# 1) Framework version identifier. Which version of the framework should the tests run against. Options are: +# +# - CANDIDATE_VERSION: Use the candidate code, i.e the one being built right now. +# - PREVIOUS_VERSION: Use the previous version code, i.e the published version prior to CANDIDATE_VERSION. +# +function run_regression_against_framework_version() { + + TEST_RUNNER=${TEST_RUNNER:-""} + CANDIDATE_VERSION=${CANDIDATE_VERSION:-""} + + if [ "${TEST_RUNNER}" != "dist" ]; then + echo "Unsupported runner: ${TEST_RUNNER}. Regression tests can only run with the 'dist' runner" + exit 1 + fi + + SUPPORTED_FRAMEWORK_VERSION_IDENTIFIERS=("CANDIDATE_VERSION PREVIOUS_VERSION") + FRAMEWORK_VERSION_IDENTIFIER=$1 + if [[ ! " ${SUPPORTED_FRAMEWORK_VERSION_IDENTIFIERS[@]} " =~ " ${FRAMEWORK_VERSION_IDENTIFIER} " ]]; then + echo "Unsupported framework version identifier. Should be one of ${SUPPORTED_FRAMEWORK_VERSION_IDENTIFIERS}" + exit 1 + fi + + echo "Fetching previous version for candidate: ${CANDIDATE_VERSION}" + + # we need to explicitly install these deps because this script is executed + # int the test phase, which means the cwd is the packaged dist directory, + # so it doesn't have the dependencies installed from the installation of the package.json + # in the build phase. maybe we should just run npm install on the package.json again? + npm install @octokit/rest@^18.0.6 semver@^7.3.2 make-runnable@^1.3.8 + PREVIOUS_VERSION=$(node ${integdir}/github-helpers.js fetchPreviousVersion ${CANDIDATE_VERSION}) + + echo "Previous version is: ${PREVIOUS_VERSION}" + + temp_dir=$(mktemp -d) + integ_under_test=${integdir}/cli-backwards-tests-${PREVIOUS_VERSION} + + pushd ${temp_dir} + + echo "Downloading aws-cdk ${PREVIOUS_VERSION} tarball from npm" + npm pack aws-cdk@${PREVIOUS_VERSION} + tar -zxvf aws-cdk-${PREVIOUS_VERSION}.tgz + + rm -rf ${integ_under_test} + + echo "Copying integration tests of version ${PREVIOUS_VERSION} to ${integ_under_test} (dont worry, its gitignored)" + cp -r ${temp_dir}/package/test/integ/cli "${integ_under_test}" + + patch_dir="${integdir}/cli-regression-patches/v${PREVIOUS_VERSION}" + # delete possibly stale junit.xml file + rm -f ${integ_under_test}/junit.xml + if [[ -d "$patch_dir" ]]; then + echo "Hotpatching the tests with files from $patch_dir" >&2 + cp -r "$patch_dir"/* ${integ_under_test} + fi + + popd + + # the framework version to use is determined by the caller as the first argument. + # its a variable name indirection. + FRAMEWORK_VERSION=${!FRAMEWORK_VERSION_IDENTIFIER} ${integ_under_test}/test.sh +} diff --git a/packages/awslint/.npmignore b/packages/awslint/.npmignore index dd1dbf2be01be..7cb354bd2b155 100644 --- a/packages/awslint/.npmignore +++ b/packages/awslint/.npmignore @@ -8,4 +8,5 @@ dist *.tsbuildinfo tsconfig.json .eslintrc.js -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/awslint/package.json b/packages/awslint/package.json index b7344c170348d..e419d165e53a5 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -21,7 +21,7 @@ "colors": "^1.4.0", "fs-extra": "^9.0.1", "jsii-reflect": "^1.13.0", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "devDependencies": { "@types/fs-extra": "^8.1.1", diff --git a/packages/cdk-assets/.npmignore b/packages/cdk-assets/.npmignore index de7dfbff2926f..45b8808bdd7ac 100644 --- a/packages/cdk-assets/.npmignore +++ b/packages/cdk-assets/.npmignore @@ -26,4 +26,5 @@ jest.config.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index bd25264df1acd..66d479dcfd3a6 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -35,10 +35,10 @@ "@types/jest": "^26.0.14", "@types/jszip": "^3.4.1", "@types/mock-fs": "^4.10.0", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "@types/yargs": "^15.0.7", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "jszip": "^3.5.0", "mock-fs": "^4.13.0", "pkglint": "0.0.0" @@ -49,7 +49,7 @@ "archiver": "^5.0.2", "aws-sdk": "^2.739.0", "glob": "^7.1.6", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 16469f7b1b5e1..9d30566e8d12b 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@types/jest": "^26.0.14", "@types/yaml": "1.9.7", - "jest": "^26.4.2" + "jest": "^26.6.0" }, "keywords": [ "aws", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 51a6fbe5eaff8..eb62f163f0f83 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -62,6 +62,7 @@ "@aws-cdk/aws-cloudtrail": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-cloudwatch-actions": "0.0.0", + "@aws-cdk/aws-codeartifact": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", @@ -172,6 +173,7 @@ "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/aws-stepfunctions-tasks": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", + "@aws-cdk/aws-timestream": "0.0.0", "@aws-cdk/aws-transfer": "0.0.0", "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", @@ -188,16 +190,16 @@ "constructs": "^3.0.4", "fs-extra": "^9.0.1", "jsii-reflect": "^1.13.0", - "jsonschema": "^1.2.7", + "jsonschema": "^1.2.10", "yaml": "1.10.0", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "devDependencies": { "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.14", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.7", - "jest": "^26.4.2", + "jest": "^26.6.0", "jsii": "^1.13.0" }, "keywords": [ diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index 69a486c67530d..2147d4a000dcf 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -1092,20 +1092,7 @@ Object { Object { "Action": "dynamodb:ListStreams", "Effect": "Allow", - "Resource": Object { - "Fn::Join": Array [ - "", - Array [ - Object { - "Fn::GetAtt": Array [ - "TableCD117FA1", - "Arn", - ], - }, - "/stream/*", - ], - ], - }, + "Resource": "*", }, Object { "Action": Array [ @@ -1750,6 +1737,7 @@ Object { "codebuild:CreateReport", "codebuild:UpdateReport", "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages", ], "Effect": "Allow", "Resource": Object { diff --git a/packages/monocdk/.eslintrc.js b/packages/monocdk/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/monocdk/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/monocdk-experiment/.gitignore b/packages/monocdk/.gitignore similarity index 100% rename from packages/monocdk-experiment/.gitignore rename to packages/monocdk/.gitignore diff --git a/packages/monocdk-experiment/.npmignore b/packages/monocdk/.npmignore similarity index 93% rename from packages/monocdk-experiment/.npmignore rename to packages/monocdk/.npmignore index b5bc540300d0f..eb24dc7eb2308 100644 --- a/packages/monocdk-experiment/.npmignore +++ b/packages/monocdk/.npmignore @@ -23,4 +23,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts **/cdk.out -junit.xml \ No newline at end of file +junit.xml +test/ \ No newline at end of file diff --git a/packages/monocdk/CONTRIBUTING.md b/packages/monocdk/CONTRIBUTING.md new file mode 100644 index 0000000000000..5ba861d6f841c --- /dev/null +++ b/packages/monocdk/CONTRIBUTING.md @@ -0,0 +1,8 @@ +## monocdk + +To build `monocdk` outside of the AWS CDK build process, execute: + +``` +$ ./scripts/gen.sh +$ lerna run build --scope monocdk +``` \ No newline at end of file diff --git a/packages/monocdk/LICENSE b/packages/monocdk/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/monocdk/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/monocdk/NOTICE b/packages/monocdk/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/monocdk/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/monocdk-experiment/README.md b/packages/monocdk/README.md similarity index 80% rename from packages/monocdk-experiment/README.md rename to packages/monocdk/README.md index a82945b85886e..fe98c56b654b1 100644 --- a/packages/monocdk-experiment/README.md +++ b/packages/monocdk/README.md @@ -11,9 +11,9 @@ An __experiment__ to bundle all of the CDK into a single module. ## Usage ### Installation -To try out `monocdk-experiment` replace all references to CDK Construct +To try out `monocdk` replace all references to CDK Construct Libraries (most `@aws-cdk/*` packages) in your `package.json` file with a single -entrey referring to `monocdk-experiment`. +entrey referring to `monocdk`. You also need to add a reference to the `constructs` library, according to the kind of project you are developing: @@ -27,7 +27,7 @@ kind of project you are developing: You can use a classic import to get access to each service namespaces: ```ts -import { core, aws_s3 as s3 } from 'monocdk-experiment'; +import { core, aws_s3 as s3 } from 'monocdk'; const app = new core.App(); const stack = new core.Stack(app, 'MonoCDK-Stack'); @@ -40,8 +40,8 @@ new s3.Bucket(stack, 'TestBucket'); Alternatively, you can use "barrel" imports: ```ts -import { App, Stack } from 'monocdk-experiment'; -import { Bucket } from 'monocdk-experiment/aws-s3'; +import { App, Stack } from 'monocdk'; +import { Bucket } from 'monocdk/aws-s3'; const app = new App(); const stack = new Stack(app, 'MonoCDK-Stack'); diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk/package.json similarity index 96% rename from packages/monocdk-experiment/package.json rename to packages/monocdk/package.json index da8000b78cf3d..d9877a713d5da 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk/package.json @@ -1,5 +1,5 @@ { - "name": "monocdk-experiment", + "name": "monocdk", "version": "0.0.0", "description": "An experiment to bundle the entire CDK into a single module", "main": "lib/index.js", @@ -7,7 +7,7 @@ "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git", - "directory": "packages/monocdk-experiment" + "directory": "packages/monocdk" }, "stability": "experimental", "maturity": "developer-preview", @@ -31,10 +31,7 @@ "cdk-build": { "eslint": { "disable": true - }, - "pre": [ - "npm run gen" - ] + } }, "pkglint": { "exclude": [ @@ -52,7 +49,7 @@ "targets": { "dotnet": { "namespace": "Amazon.CDK", - "packageId": "Amazon.CDK.MonoCDK.Experiment", + "packageId": "Amazon.CDK.MonoCDK", "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", "versionSuffix": "-devpreview", "signAssembly": true, @@ -62,13 +59,13 @@ "package": "software.amazon.awscdk.core", "maven": { "groupId": "software.amazon.awscdk", - "artifactId": "monocdk-experiment", + "artifactId": "monocdk", "versionSuffix": ".DEVPREVIEW" } }, "python": { - "distName": "monocdk.experiment", - "module": "monocdk_experiment" + "distName": "monocdk", + "module": "monocdk" } }, "projectReferences": false @@ -90,7 +87,7 @@ "dependencies": { "case": "1.6.3", "fs-extra": "^9.0.1", - "jsonschema": "^1.2.7", + "jsonschema": "^1.2.10", "minimatch": "^3.0.4", "semver": "^7.3.2", "yaml": "1.10.0" @@ -131,6 +128,7 @@ "@aws-cdk/aws-cloudtrail": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-cloudwatch-actions": "0.0.0", + "@aws-cdk/aws-codeartifact": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codedeploy": "0.0.0", @@ -241,6 +239,7 @@ "@aws-cdk/aws-stepfunctions": "0.0.0", "@aws-cdk/aws-stepfunctions-tasks": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", + "@aws-cdk/aws-timestream": "0.0.0", "@aws-cdk/aws-transfer": "0.0.0", "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", @@ -255,7 +254,7 @@ "@aws-cdk/region-info": "0.0.0", "@aws-cdk/yaml-cfn": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "cdk-build-tools": "0.0.0", "constructs": "^3.0.4", "fs-extra": "^9.0.1", diff --git a/scripts/bump-candidate.sh b/scripts/bump-candidate.sh new file mode 100755 index 0000000000000..a88122308e6a1 --- /dev/null +++ b/scripts/bump-candidate.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# -------------------------------------------------------------------------------------------------- +# +# This script is intended to be used in our master pipeline as a way of incrementing the version number +# so that it doesnt colide with any published version. This is needed because our integration tests launch +# a verdaccio instance that serves local tarballs, and if those tarballs have the same version as +# already published modules, it messes things up. +# +# It does so by using a pre-release rc tag, making it so that locally packed versions will always be +# suffixed with '-rc', distinguishing it from publisehd modules. +# +# If you need to run integration tests locally against the distribution tarballs, you should run this +# script locally as well before building and packing the repository. +# +# This script only increments the version number in the version files, it does not generate a changelog. +# +# -------------------------------------------------------------------------------------------------- +set -euo pipefail +version=${1:-minor} + +echo "Starting candidate ${version} version bump" + +npx standard-version --release-as ${version} --prerelease=rc --skip.commit --skip.changelog diff --git a/scripts/check-prerequisites.sh b/scripts/check-prerequisites.sh index 12e85ae3789f1..e3dac7fee9f52 100755 --- a/scripts/check-prerequisites.sh +++ b/scripts/check-prerequisites.sh @@ -144,19 +144,6 @@ else wrong_version fi -# [Ruby >= 2.5.1, < 3.0] -app="ruby" -app_min="2.5.1" -check_which $app $app_min -app_v=$(${app} --version) -echo -e "Checking $app version... \c" -if [ $(echo $app_v | grep -c -E "2\.[56789]\.[0-9].*") -eq 1 ] -then - echo "Ok" -else - wrong_version -fi - # [Docker >= 19.03] app="docker" app_min="19.03.0" diff --git a/scripts/gen.sh b/scripts/gen.sh new file mode 100755 index 0000000000000..d3acf1e403904 --- /dev/null +++ b/scripts/gen.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +export PATH=$(npm bin):$PATH +export NODE_OPTIONS="--max-old-space-size=4096 ${NODE_OPTIONS:-}" + +echo "=============================================================================================" +echo "installing..." +yarn install --frozen-lockfile --network-timeout 1000000 + +fail() { + echo "❌ Last command failed. Scroll up to see errors in log (search for '!!!!!!!!')." + exit 1 +} + +echo "=============================================================================================" +echo "building required build tools..." +time lerna run --stream build --scope cfn2ts --scope ubergen --include-dependencies || fail + +echo "=============================================================================================" +echo "executing gen..." +time lerna run --stream gen || fail \ No newline at end of file diff --git a/scripts/merge-forward.sh b/scripts/merge-forward.sh new file mode 100755 index 0000000000000..92d67a85da250 --- /dev/null +++ b/scripts/merge-forward.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Forward merge 'master' branch into 'v2-main' branch + +set -exo pipefail + +git fetch --all +git checkout -B v2-main origin/v2-main +git merge origin/master --no-edit diff --git a/tools/cdk-build-tools/bin/cdk-build.ts b/tools/cdk-build-tools/bin/cdk-build.ts index 96039a2d5b6ea..d745de50aa2be 100644 --- a/tools/cdk-build-tools/bin/cdk-build.ts +++ b/tools/cdk-build-tools/bin/cdk-build.ts @@ -2,7 +2,7 @@ import * as yargs from 'yargs'; import { compileCurrentPackage } from '../lib/compile'; import { lintCurrentPackage } from '../lib/lint'; import { shell } from '../lib/os'; -import { cdkBuildOptions, currentPackageJson, CompilerOverrides } from '../lib/package-info'; +import { cdkBuildOptions, CompilerOverrides, currentPackageJson, genScript } from '../lib/package-info'; import { Timers } from '../lib/timer'; async function main() { @@ -24,22 +24,24 @@ async function main() { desc: 'Specify a different eslint executable', defaultDescription: 'eslint provided by node dependencies', }) + .option('gen', { + type: 'boolean', + desc: 'execute gen script', + default: true, + }) .argv; const options = cdkBuildOptions(); const env = options.env; if (options.pre) { - await shell(options.pre, { timers, env }); + const commands = options.pre.join(' && '); + await shell([commands], { timers, env }); } - // See if we need to call cfn2ts - if (options.cloudformation) { - if (typeof options.cloudformation === 'string') { - // There can be multiple scopes, ensuring it's always an array. - options.cloudformation = [options.cloudformation]; - } - await shell(['cfn2ts', ...options.cloudformation.map(scope => `--scope=${scope}`)], { timers, env }); + const gen = genScript(); + if (args.gen && gen) { + await shell([gen], { timers, env }); } const overrides: CompilerOverrides = { eslint: args.eslint, jsii: args.jsii, tsc: args.tsc }; diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index cf5b657d9a470..9e12b86b80580 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -89,6 +89,14 @@ export function packageCompiler(compilers: CompilerOverrides): string[] { } } +/** + * Return the command defined in scripts.gen if exists + */ +export function genScript(): string | undefined { + return currentPackageJson().scripts?.gen; +} + + export interface CDKBuildOptions { /** * What CloudFormation scope to generate resources for, if any diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index dee0bc8c47031..be921ac90d11a 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -49,14 +49,14 @@ "eslint-import-resolver-typescript": "^2.3.0", "eslint-plugin-import": "^2.22.1", "fs-extra": "^9.0.1", - "jest": "^26.4.2", + "jest": "^26.6.0", "jsii": "^1.13.0", "jsii-pacmak": "^1.13.0", "nodeunit": "^0.11.3", "nyc": "^15.1.0", "ts-jest": "^26.4.1", "typescript": "~3.9.7", - "yargs": "^16.0.3", + "yargs": "^16.1.0", "yarn-cling": "0.0.0" }, "keywords": [ diff --git a/tools/cdk-integ-tools/package.json b/tools/cdk-integ-tools/package.json index 7053652ad7dd4..33fbc7e557f79 100644 --- a/tools/cdk-integ-tools/package.json +++ b/tools/cdk-integ-tools/package.json @@ -40,7 +40,7 @@ "@aws-cdk/assert": "0.0.0", "aws-cdk": "0.0.0", "fs-extra": "^9.0.1", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "keywords": [ "aws", diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index a29fd4f2de6d2..27ace1c44318a 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -33,14 +33,14 @@ "codemaker": "^1.13.0", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.0.1", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "devDependencies": { "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.14", "@types/yargs": "^15.0.7", "cdk-build-tools": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0" }, "keywords": [ diff --git a/tools/eslint-plugin-cdk/.gitignore b/tools/eslint-plugin-cdk/.gitignore index f3984b8c7bb62..a29d60ebc38a3 100644 --- a/tools/eslint-plugin-cdk/.gitignore +++ b/tools/eslint-plugin-cdk/.gitignore @@ -11,4 +11,6 @@ dist coverage nyc.config.js !.eslintrc.js -!eslintrc.js \ No newline at end of file +!eslintrc.js + +.test-output/ diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index 4615b43cd6e63..a3510b480ec80 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -15,9 +15,9 @@ "@types/eslint": "^7.2.3", "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.14", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "eslint-plugin-rulesdir": "^0.1.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "typescript": "~3.9.7" }, "dependencies": { diff --git a/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts b/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts index c0d1d4b3ec35b..c2272cfd39353 100644 --- a/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts +++ b/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts @@ -1,7 +1,6 @@ import { ESLint } from 'eslint'; import * as fs from 'fs-extra'; import * as path from 'path'; -import * as os from 'os'; const linter = new ESLint({ overrideConfigFile: path.join(__dirname, 'eslintrc.js'), @@ -11,7 +10,8 @@ const linter = new ESLint({ fix: true, }); -const outputDir = fs.mkdtempSync(os.tmpdir()) +const outputDir = path.join(process.cwd(), '.test-output'); +fs.mkdirpSync(outputDir); const fixturesDir = path.join(__dirname, 'fixtures', 'no-core-construct'); describe('no-core-construct', () => { @@ -41,4 +41,4 @@ async function lintAndFix(file: string) { return r; })); return newPath; -} \ No newline at end of file +} diff --git a/tools/nodeunit-shim/package.json b/tools/nodeunit-shim/package.json index ebec2519fd49a..1af5c92bdcecb 100644 --- a/tools/nodeunit-shim/package.json +++ b/tools/nodeunit-shim/package.json @@ -13,11 +13,11 @@ }, "devDependencies": { "@types/jest": "^26.0.14", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "typescript": "~3.9.7" }, "dependencies": { - "jest": "^26.4.2" + "jest": "^26.6.0" }, "keywords": [], "author": "", diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 77fce1a03d423..25d59e434169e 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -482,7 +482,7 @@ export class JSIIProjectReferences extends ValidationRule { this.name, pkg, 'jsii.projectReferences', - pkg.json.name !== 'monocdk-experiment' && pkg.json.name !== 'aws-cdk-lib', + pkg.json.name !== 'monocdk' && pkg.json.name !== 'aws-cdk-lib', ); } } @@ -574,6 +574,21 @@ export class NoTsBuildInfo extends ValidationRule { } } +export class NoTestsInNpmPackage extends ValidationRule { + public readonly name = 'npmignore/test'; + + public validate(pkg: PackageJson): void { + // skip private packages + if (pkg.json.private) { return; } + + // Skip the CLI package, as its 'test' subdirectory is used at runtime. + if (pkg.packageName === 'aws-cdk') { return; } + + // Exclude 'test/' directories from being packaged + fileShouldContain(this.name, pkg, '.npmignore', 'test/'); + } +} + export class NoTsConfig extends ValidationRule { public readonly name = 'npmignore/tsconfig'; @@ -1447,4 +1462,4 @@ function toRegExp(str: string): RegExp { function readBannerFile(file: string): string { return fs.readFileSync(path.join(__dirname, 'banners', file), { encoding: 'utf-8' }); -} +} \ No newline at end of file diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index d83de33dfdd46..5f3b0cb82b8b9 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -39,7 +39,7 @@ "@types/semver": "^7.3.4", "@types/yargs": "^15.0.7", "eslint-plugin-cdk": "0.0.0", - "jest": "^26.4.2", + "jest": "^26.6.0", "typescript": "~3.9.7" }, "dependencies": { @@ -48,6 +48,6 @@ "fs-extra": "^9.0.1", "glob": "^7.1.6", "semver": "^7.3.2", - "yargs": "^16.0.3" + "yargs": "^16.1.0" } } diff --git a/tools/pkgtools/package.json b/tools/pkgtools/package.json index 2c910e2bee71e..545966b08e07e 100644 --- a/tools/pkgtools/package.json +++ b/tools/pkgtools/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "fs-extra": "^9.0.1", - "yargs": "^16.0.3" + "yargs": "^16.1.0" }, "keywords": [ "aws", diff --git a/tools/ubergen/bin/ubergen.ts b/tools/ubergen/bin/ubergen.ts index 2500c05377c27..3026eb04c8430 100644 --- a/tools/ubergen/bin/ubergen.ts +++ b/tools/ubergen/bin/ubergen.ts @@ -1,17 +1,21 @@ import * as console from 'console'; +import * as os from 'os'; import * as path from 'path'; import * as process from 'process'; import * as fs from 'fs-extra'; import * as ts from 'typescript'; const LIB_ROOT = path.resolve(process.cwd(), 'lib'); +const ROOT_PATH = findWorkspacePath(); async function main() { + console.log(`🌴 workspace root path is: ${ROOT_PATH}`); const libraries = await findLibrariesToPackage(); const packageJson = await verifyDependencies(libraries); await prepareSourceFiles(libraries, packageJson); } + main().then( () => process.exit(0), (err) => { @@ -54,12 +58,32 @@ interface PackageJson { readonly [key: string]: unknown; } +/** + * Find the workspace root path. Walk up the directory tree until you find lerna.json + */ +function findWorkspacePath(): string { + + return _findRootPath(process.cwd()); + + function _findRootPath(part: string): string { + if (process.cwd() === os.homedir()) { + throw new Error('couldn\'t find a \'lerna.json\' file when walking up the directory tree, are you in a aws-cdk project?'); + } + + if (fs.existsSync(path.resolve(part, 'lerna.json'))) { + return part; + } + + return _findRootPath(path.resolve(part, '..')); + } +} + async function findLibrariesToPackage(): Promise { console.log('🔍 Discovering libraries that need packaging...'); const result = new Array(); + const librariesRoot = path.resolve(ROOT_PATH, 'packages', '@aws-cdk'); - const librariesRoot = path.resolve(process.cwd(), '..', '..', 'packages', '@aws-cdk'); for (const dir of await fs.readdir(librariesRoot)) { const packageJson = await fs.readJson(path.resolve(librariesRoot, dir, 'package.json')); @@ -73,7 +97,6 @@ async function findLibrariesToPackage(): Promise { console.log(`\t⚠️ Skipping (not jsii-enabled): ${packageJson.name}`); continue; } - result.push({ packageJson, root: path.join(librariesRoot, dir), @@ -121,8 +144,7 @@ async function verifyDependencies(libraries: readonly LibraryReference[]): Promi [library.packageJson.name]: library.packageJson.version, }); } - - const workspacePath = path.resolve(process.cwd(), '..', '..', 'package.json'); + const workspacePath = path.resolve(ROOT_PATH, 'package.json'); const workspace = await fs.readJson(workspacePath); let workspaceChanged = false; diff --git a/tools/ubergen/package.json b/tools/ubergen/package.json index 8e2e2e0eaa5a9..4698e84f54300 100644 --- a/tools/ubergen/package.json +++ b/tools/ubergen/package.json @@ -12,8 +12,8 @@ "ubergen": "bin/ubergen" }, "scripts": { - "build": "tsc -b && chmod +x bin/ubergen && eslint . --ext=.ts", - "watch": "tsc -b -w", + "build": "cdk-build", + "watch": "cdk-watch", "pkglint": "pkglint -f", "test": "echo success", "build+test+package": "npm run build+test", diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index c13cbd2017aa4..8edb7635a8f74 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -39,9 +39,9 @@ }, "devDependencies": { "@types/jest": "^26.0.14", - "@types/node": "^10.17.35", + "@types/node": "^10.17.40", "@types/yarnpkg__lockfile": "^1.1.4", - "jest": "^26.4.2", + "jest": "^26.6.0", "pkglint": "0.0.0", "typescript": "~3.9.7" }, diff --git a/yarn.lock b/yarn.lock index 03e7e46a0c496..78bcb29265411 100644 --- a/yarn.lock +++ b/yarn.lock @@ -927,7 +927,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.0.0", "@babel/template@^7.10.4", "@babel/template@^7.2.2", "@babel/template@^7.3.3", "@babel/template@^7.4.0": +"@babel/template@^7.10.4", "@babel/template@^7.3.3", "@babel/template@^7.4.0": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== @@ -1084,199 +1084,93 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.5.0.tgz#770800799d510f37329c508a9edd0b7b447d9abb" - integrity sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-message-util "^25.5.0" - jest-util "^25.5.0" - slash "^3.0.0" - -"@jest/console@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856" - integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w== +"@jest/console@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.0.tgz#fd4a4733df3c50260aefb227414296aee96e682f" + integrity sha512-ArGcZWAEYMWmWnc/QvxLDvFmGRPvmHeulhS7FUUAlUGR5vS/SqMfArsGaYmIFEThSotCMnEihwx1h62I1eg5lg== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.3.0" - jest-util "^26.3.0" - slash "^3.0.0" - -"@jest/core@^25.5.4": - version "25.5.4" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.5.4.tgz#3ef7412f7339210f003cdf36646bbca786efe7b4" - integrity sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA== - dependencies: - "@jest/console" "^25.5.0" - "@jest/reporters" "^25.5.1" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - ansi-escapes "^4.2.1" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^25.5.0" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-resolve-dependencies "^25.5.4" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - jest-watcher "^25.5.0" - micromatch "^4.0.2" - p-each-series "^2.1.0" - realpath-native "^2.0.0" - rimraf "^3.0.0" + jest-message-util "^26.6.0" + jest-util "^26.6.0" slash "^3.0.0" - strip-ansi "^6.0.0" -"@jest/core@^26.4.2": - version "26.4.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc" - integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg== +"@jest/core@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.0.tgz#04dd3e046e9ebbe06a4f330e05a67f21f7bb314a" + integrity sha512-7wbunxosnC5zXjxrEtTQSblFjRVOT8qz1eSytw8riEeWgegy3ct91NLPEP440CDuWrmW3cOLcEGxIf9q2u6O9Q== dependencies: - "@jest/console" "^26.3.0" - "@jest/reporters" "^26.4.1" - "@jest/test-result" "^26.3.0" - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.0" + "@jest/reporters" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^26.3.0" - jest-config "^26.4.2" - jest-haste-map "^26.3.0" - jest-message-util "^26.3.0" + jest-changed-files "^26.6.0" + jest-config "^26.6.0" + jest-haste-map "^26.6.0" + jest-message-util "^26.6.0" jest-regex-util "^26.0.0" - jest-resolve "^26.4.0" - jest-resolve-dependencies "^26.4.2" - jest-runner "^26.4.2" - jest-runtime "^26.4.2" - jest-snapshot "^26.4.2" - jest-util "^26.3.0" - jest-validate "^26.4.2" - jest-watcher "^26.3.0" + jest-resolve "^26.6.0" + jest-resolve-dependencies "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" + jest-watcher "^26.6.0" micromatch "^4.0.2" p-each-series "^2.1.0" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.5.0.tgz#aa33b0c21a716c65686638e7ef816c0e3a0c7b37" - integrity sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA== - dependencies: - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - -"@jest/environment@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0" - integrity sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA== +"@jest/environment@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.0.tgz#695ee24cbf110456272caa9debbbf7e01afb2f78" + integrity sha512-l+5MSdiC4rUUrz8xPdj0TwHBwuoqMcAbFnsYDTn5FkenJl8b+lvC5NdJl1tVICGHWnx0fnjdd1luRZ7u3U4xyg== dependencies: - "@jest/fake-timers" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" - jest-mock "^26.3.0" - -"@jest/fake-timers@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" - integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== - dependencies: - "@jest/types" "^25.5.0" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - lolex "^5.0.0" + jest-mock "^26.6.0" -"@jest/fake-timers@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a" - integrity sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A== +"@jest/fake-timers@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.0.tgz#5b4cc83fab91029963c53e6e2716f02544323b22" + integrity sha512-7VQpjChrwlwvGNysS10lDBLOVLxMvMtpx0Xo6aIotzNVyojYk0NN0CR8R4T6h/eu7Zva/LB3P71jqwGdtADoag== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" "@sinonjs/fake-timers" "^6.0.1" "@types/node" "*" - jest-message-util "^26.3.0" - jest-mock "^26.3.0" - jest-util "^26.3.0" - -"@jest/globals@^25.5.2": - version "25.5.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-25.5.2.tgz#5e45e9de8d228716af3257eeb3991cc2e162ca88" - integrity sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA== - dependencies: - "@jest/environment" "^25.5.0" - "@jest/types" "^25.5.0" - expect "^25.5.0" + jest-message-util "^26.6.0" + jest-mock "^26.6.0" + jest-util "^26.6.0" -"@jest/globals@^26.4.2": - version "26.4.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a" - integrity sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow== +"@jest/globals@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.0.tgz#da2f58d17105b6a7531ee3c8724acb5f233400e2" + integrity sha512-rs3a/a8Lq8FgTx11SxbqIU2bDjsFU2PApl2oK2oUVlo84RSF76afFm2nLojW93AGssr715GHUwhq5b6mpCI5BQ== dependencies: - "@jest/environment" "^26.3.0" - "@jest/types" "^26.3.0" - expect "^26.4.2" - -"@jest/reporters@^25.5.1": - version "25.5.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.5.1.tgz#cb686bcc680f664c2dbaf7ed873e93aa6811538b" - integrity sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^25.5.1" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-worker "^25.5.0" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^3.1.0" - terminal-link "^2.0.0" - v8-to-istanbul "^4.1.3" - optionalDependencies: - node-notifier "^6.0.0" + "@jest/environment" "^26.6.0" + "@jest/types" "^26.6.0" + expect "^26.6.0" -"@jest/reporters@^26.4.1": - version "26.4.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795" - integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ== +"@jest/reporters@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.0.tgz#2a8d631ad3b19a722fd0fae58ce9fa25e8aac1cf" + integrity sha512-PXbvHhdci5Rj1VFloolgLb+0kkdtzswhG8MzVENKJRI3O1ndwr52G6E/2QupjwrRcYnApZOelFf4nNpf5+SDxA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.3.0" - "@jest/test-result" "^26.3.0" - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" @@ -1287,115 +1181,63 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^26.3.0" - jest-resolve "^26.4.0" - jest-util "^26.3.0" - jest-worker "^26.3.0" + jest-haste-map "^26.6.0" + jest-resolve "^26.6.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^5.0.1" + v8-to-istanbul "^6.0.1" optionalDependencies: node-notifier "^8.0.0" -"@jest/source-map@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.5.0.tgz#df5c20d6050aa292c2c6d3f0d2c7606af315bd1b" - integrity sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.4" - source-map "^0.6.0" - -"@jest/source-map@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9" - integrity sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ== +"@jest/source-map@^26.5.0": + version "26.5.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367" + integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" -"@jest/test-result@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.5.0.tgz#139a043230cdeffe9ba2d8341b27f2efc77ce87c" - integrity sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A== - dependencies: - "@jest/console" "^25.5.0" - "@jest/types" "^25.5.0" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-result@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb" - integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg== +"@jest/test-result@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.0.tgz#79705c8a57165777af5ef1d45c65dcc4a5965c11" + integrity sha512-LV6X1ry+sKjseQsIFz3e6XAZYxwidvmeJFnVF08fq98q08dF1mJYI0lDq/LmH/jas+R4s0pwnNGiz1hfC4ZUBw== dependencies: - "@jest/console" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.0" + "@jest/types" "^26.6.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^25.5.4": - version "25.5.4" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz#9b4e685b36954c38d0f052e596d28161bdc8b737" - integrity sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA== +"@jest/test-sequencer@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.0.tgz#a9dbc6545b1c59e7f375b05466e172126609906d" + integrity sha512-rWPTMa+8rejvePZnJmnKkmKWh0qILFDPpN0qbSif+KNGvFxqqDGafMo4P2Y8+I9XWrZQBeXL9IxPL4ZzDgRlbw== dependencies: - "@jest/test-result" "^25.5.0" - graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" - -"@jest/test-sequencer@^26.4.2": - version "26.4.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba" - integrity sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog== - dependencies: - "@jest/test-result" "^26.3.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.3.0" - jest-runner "^26.4.2" - jest-runtime "^26.4.2" - -"@jest/transform@^25.5.1": - version "25.5.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.5.1.tgz#0469ddc17699dd2bf985db55fa0fb9309f5c2db3" - integrity sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^25.5.0" - babel-plugin-istanbul "^6.0.0" - chalk "^3.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" + "@jest/test-result" "^26.6.0" graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-regex-util "^25.2.6" - jest-util "^25.5.0" - micromatch "^4.0.2" - pirates "^4.0.1" - realpath-native "^2.0.0" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" + jest-haste-map "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" -"@jest/transform@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55" - integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A== +"@jest/transform@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.0.tgz#1a6b95d0c7f9b4f96dd3aab9d28422a9e5e4043e" + integrity sha512-NUNA1NMCyVV9g5NIQF1jzW7QutQhB/HAocteCiUyH0VhmLXnGMTfPYQu1G6IjPk+k1SWdh2PD+Zs1vMqbavWzg== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" babel-plugin-istanbul "^6.0.0" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^26.3.0" + jest-haste-map "^26.6.0" jest-regex-util "^26.0.0" - jest-util "^26.3.0" + jest-util "^26.6.0" micromatch "^4.0.2" pirates "^4.0.1" slash "^3.0.0" @@ -1423,6 +1265,17 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jest/types@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.0.tgz#2c045f231bfd79d52514cda3fbc93ef46157fa6a" + integrity sha512-8pDeq/JVyAYw7jBGU83v8RMYAkdrRxLG3BGnAJuqaQAUd6GWBmND2uyl+awI88+hit48suLoLjNFtR+ZXxWaYg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@jsii/spec@^1.13.0": version "1.13.0" resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.13.0.tgz#3389e047065b6b7f7fda238a90d58ac577c811bf" @@ -2156,6 +2009,18 @@ dependencies: "@octokit/types" "^5.0.0" +"@octokit/core@^3.0.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.2.tgz#c937d5f9621b764573068fcd2e5defcc872fd9cc" + integrity sha512-AInOFULmwOa7+NFi9F8DlDkm5qtZVmDQayi7TUgChE3yeIGPq0Y+6cAEXPexQ3Ea+uZy66hKEazR7DJyU+4wfw== + dependencies: + "@octokit/auth-token" "^2.4.0" + "@octokit/graphql" "^4.3.1" + "@octokit/request" "^5.4.0" + "@octokit/types" "^5.0.0" + before-after-hook "^2.1.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^6.0.1": version "6.0.6" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.6.tgz#4f09f2b468976b444742a1d5069f6fa45826d999" @@ -2165,6 +2030,15 @@ is-plain-object "^5.0.0" universal-user-agent "^6.0.0" +"@octokit/graphql@^4.3.1": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.6.tgz#708143ba15cf7c1879ed6188266e7f270be805d4" + integrity sha512-Rry+unqKTa3svswT2ZAuqenpLrzJd+JTv89LTeVa5UM/5OX8o4KTkPL7/1ABq4f/ZkELb0XEK/2IEoYwykcLXg== + dependencies: + "@octokit/request" "^5.3.0" + "@octokit/types" "^5.0.0" + universal-user-agent "^6.0.0" + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -2177,6 +2051,13 @@ dependencies: "@octokit/types" "^2.0.1" +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.4.0.tgz#92f951ddc8a1cd505353fa07650752ca25ed7e93" + integrity sha512-YT6Klz3LLH6/nNgi0pheJnUmTFW4kVnxGft+v8Itc41IIcjl7y1C8TatmKQBbCSuTSNFXO5pCENnqg6sjwpJhg== + dependencies: + "@octokit/types" "^5.5.0" + "@octokit/plugin-request-log@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" @@ -2190,6 +2071,14 @@ "@octokit/types" "^2.0.1" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz#c5a0691b3aba5d8b4ef5dffd6af3649608f167ba" + integrity sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw== + dependencies: + "@octokit/types" "^5.5.0" + deprecation "^2.3.1" + "@octokit/request-error@^1.0.2": version "1.2.1" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" @@ -2208,7 +2097,7 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.2.0": +"@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.4.0": version "5.4.9" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.9.tgz#0a46f11b82351b3416d3157261ad9b1558c43365" integrity sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA== @@ -2244,6 +2133,16 @@ once "^1.4.0" universal-user-agent "^4.0.0" +"@octokit/rest@^18.0.6": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.6.tgz#76c274f1a68f40741a131768ef483f041e7b98b6" + integrity sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w== + dependencies: + "@octokit/core" "^3.0.0" + "@octokit/plugin-paginate-rest" "^2.2.0" + "@octokit/plugin-request-log" "^1.0.0" + "@octokit/plugin-rest-endpoint-methods" "4.2.0" + "@octokit/types@^2.0.0", "@octokit/types@^2.0.1": version "2.16.2" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" @@ -2251,132 +2150,137 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== dependencies: "@types/node" ">= 8" -"@parcel/babel-ast-utils@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-beta.1.tgz#e545b5db02d95d32f321e5e12137175935c57615" - integrity sha512-0Jod0KZ2gCghymjP7ThI/2DfZQt9S2yq1wTvBwyJ46Ij3lIZVmXBVWs4x8O0uvKMUjS5zrnSFgurimFORLmDbQ== +"@parcel/babel-ast-utils@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-nightly.2050.tgz#5b03d8b2c0c67ac596276aac0d0d67227213539a" + integrity sha512-kyQ7JOaqk5YIY02+bbK8jXUpdONtUyxroITT6cnZ4wT6KgYmlB3HqhdluxcB5D07XdhsoyC+lS8hidHXG85ttQ== dependencies: "@babel/generator" "^7.0.0" "@babel/parser" "^7.0.0" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/babel-preset-env@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/babel-preset-env/-/babel-preset-env-2.0.0-beta.1.tgz#562837d4c28c271a0e30bc5970ef63965ddc82dc" - integrity sha512-/TK/xE9Rfy6weu0eWifnGCntCt4i7DoFCKgigdSsHbuRA4w7BfnwS0GIOAQ6gORRXIf4lM8cGsnp5jRP2PhwLw== +"@parcel/babel-preset-env@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/babel-preset-env/-/babel-preset-env-2.0.0-nightly.428.tgz#de2a2223adbf7265fe993d696c21266796a788de" + integrity sha512-Rf6UApd24x/cisNUT+H/Yn02XPmuQEZc0tTWdmPGS2lOoREJ/XUUOFhEU9maffVUM+VgqUelkfwTs5m7wHTCbw== dependencies: "@babel/preset-env" "^7.4.0" semver "^5.4.1" -"@parcel/babylon-walk@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.0-beta.1.tgz#feada450c819c67e3552c184c6b30f914a7f54d0" - integrity sha512-FWZErHc2Q62lrWxfMoRl1mej8HCr3tXvzeVgMfv2cbiOjaZIrN+8khD0AcRyesnm5JipgT8KQoJvl8ZdU9DFAw== +"@parcel/babylon-walk@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.0-nightly.2050.tgz#dc353a5d5d19de40c7c389be9e1670dd32dd5b2e" + integrity sha512-s+boLOs106tl740NFcL3Jx+YRHII8+X4a4CBo+jeDXURTbTamYvfaEagM9qiAhPgRG7+oxPzPvmU//pPGfOKPg== dependencies: "@babel/types" "^7.0.0" lodash.clone "^4.5.0" -"@parcel/bundler-default@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.0-beta.1.tgz#66a6762d6bb1fd8c8c9d13177f5fabe1fbf5a196" - integrity sha512-ksRBQcZ4OQwZ+pWl28V/G5z6JR/fZUSdXSJwyVJKCUQhKLp0qVZ0emYnZFLg+24ITemRosPH9tNMyQwzNWugsw== +"@parcel/bundler-default@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.0-nightly.428.tgz#9a75e48965d3051dd97c97bc76d46346a7d09fc6" + integrity sha512-CF4cFxoPsZaWEFcVGL4u2VHdPeanaBExquqoqQl9oaG4FqGP4B95MaSjs76XtdbCERAO5Tl5MuoC9j4VRUzYMQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/cache@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.0-beta.1.tgz#4a69d58f2acdff050256e2f23518d219cd29a089" - integrity sha512-o8GMPcrVH31uctXQGGX6O28T7Bm4dqM0DbJRCDtxixGQosKyN9OlAA84390XOti2Fjr1Av/U1ALmbdavQsdcYA== +"@parcel/cache@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.0-nightly.428.tgz#daaecd0cd061d8728deb78b948f09ce90f3c1543" + integrity sha512-67n0FyNPvanlQL6zlOhGWFcuVDOVgtkiuROhdrzW1in/MT/XwM1fYDSgpGtIZ4Megjp1aWlv58PBnC3b+qyU+w== dependencies: - "@parcel/logger" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/codeframe@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.0-beta.1.tgz#5c1195131b653562d2362f4056767694a1a6f736" - integrity sha512-HyFjYSPysYumT/T3JdZN8y0DXhXLMpmlg94rADR+Wf5Wp1YMTuIHlE1gkfbjIj1m2PxJWe4UMbniGwy3KQh9zQ== +"@parcel/codeframe@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.0-nightly.428.tgz#68687330a76f098b6cfba3b3cc1e166c382098a9" + integrity sha512-a9e8nqoTKKcnlqhwNCqRgCqxn6ZMdVcVnxCWDGF4Fb74VzLXXbIbLre9lumb2EeD5msWZvnzlCJmnRwI84KUDw== dependencies: chalk "^2.4.2" emphasize "^2.1.0" slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/config-default@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.0-beta.1.tgz#ddea69c182ee1105e4682161cf0541d81680b66d" - integrity sha512-VEJYkiF+RNaB28OPmk1sCsOapWPisu5MXV+8N7T5ERa7xOOaD2SbD5/JnOjuUGAYKALk8UBsTCG+3ukMQ7Fiuw== - dependencies: - "@parcel/bundler-default" "2.0.0-beta.1" - "@parcel/namer-default" "2.0.0-beta.1" - "@parcel/optimizer-cssnano" "2.0.0-beta.1" - "@parcel/optimizer-data-url" "2.0.0-beta.1" - "@parcel/optimizer-htmlnano" "2.0.0-beta.1" - "@parcel/optimizer-terser" "2.0.0-beta.1" - "@parcel/packager-css" "2.0.0-beta.1" - "@parcel/packager-html" "2.0.0-beta.1" - "@parcel/packager-js" "2.0.0-beta.1" - "@parcel/packager-raw" "2.0.0-beta.1" - "@parcel/packager-raw-url" "2.0.0-beta.1" - "@parcel/packager-ts" "2.0.0-beta.1" - "@parcel/reporter-bundle-analyzer" "2.0.0-beta.1" - "@parcel/reporter-bundle-buddy" "2.0.0-beta.1" - "@parcel/reporter-cli" "2.0.0-beta.1" - "@parcel/reporter-dev-server" "2.0.0-beta.1" - "@parcel/resolver-default" "2.0.0-beta.1" - "@parcel/runtime-browser-hmr" "2.0.0-beta.1" - "@parcel/runtime-js" "2.0.0-beta.1" - "@parcel/runtime-react-refresh" "2.0.0-beta.1" - "@parcel/transformer-babel" "2.0.0-beta.1" - "@parcel/transformer-coffeescript" "2.0.0-beta.1" - "@parcel/transformer-css" "2.0.0-beta.1" - "@parcel/transformer-graphql" "2.0.0-beta.1" - "@parcel/transformer-html" "2.0.0-beta.1" - "@parcel/transformer-inline-string" "2.0.0-beta.1" - "@parcel/transformer-js" "2.0.0-beta.1" - "@parcel/transformer-json" "2.0.0-beta.1" - "@parcel/transformer-jsonld" "2.0.0-beta.1" - "@parcel/transformer-less" "2.0.0-beta.1" - "@parcel/transformer-mdx" "2.0.0-beta.1" - "@parcel/transformer-postcss" "2.0.0-beta.1" - "@parcel/transformer-posthtml" "2.0.0-beta.1" - "@parcel/transformer-pug" "2.0.0-beta.1" - "@parcel/transformer-raw" "2.0.0-beta.1" - "@parcel/transformer-react-refresh-babel" "2.0.0-beta.1" - "@parcel/transformer-react-refresh-wrap" "2.0.0-beta.1" - "@parcel/transformer-sass" "2.0.0-beta.1" - "@parcel/transformer-stylus" "2.0.0-beta.1" - "@parcel/transformer-sugarss" "2.0.0-beta.1" - "@parcel/transformer-toml" "2.0.0-beta.1" - "@parcel/transformer-typescript-types" "2.0.0-beta.1" - "@parcel/transformer-yaml" "2.0.0-beta.1" - -"@parcel/core@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.0-beta.1.tgz#d1cf4352a954c6dbe2b31543f937902ba6cf3848" - integrity sha512-1KKjhL2H7z8qJNgY2M3CreYqbKopJT6ImlJk54ynIrLuJsnqKuA80FHYBiAtnSZEA65mvAJ3YLReFhg9slzbFw== - dependencies: - "@parcel/cache" "2.0.0-beta.1" - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/events" "2.0.0-beta.1" - "@parcel/fs" "2.0.0-beta.1" - "@parcel/logger" "2.0.0-beta.1" - "@parcel/package-manager" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/types" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" - "@parcel/workers" "2.0.0-beta.1" +"@parcel/config-default@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.0-nightly.428.tgz#111e40ed342ced5b38bbcf65e91e33c66be23491" + integrity sha512-0KAba2rXoFAhl+o3fOnAdBn01rMzkfCx2unR2ivLd6/dMvektwAap0/ExH+MyMa+qPzbs5JI6YkSglfsTVhxtw== + dependencies: + "@parcel/bundler-default" "2.0.0-nightly.428+5f72d6bb" + "@parcel/namer-default" "2.0.0-nightly.428+5f72d6bb" + "@parcel/optimizer-cssnano" "2.0.0-nightly.428+5f72d6bb" + "@parcel/optimizer-data-url" "2.0.0-nightly.428+5f72d6bb" + "@parcel/optimizer-htmlnano" "2.0.0-nightly.428+5f72d6bb" + "@parcel/optimizer-terser" "2.0.0-nightly.428+5f72d6bb" + "@parcel/packager-css" "2.0.0-nightly.428+5f72d6bb" + "@parcel/packager-html" "2.0.0-nightly.428+5f72d6bb" + "@parcel/packager-js" "2.0.0-nightly.428+5f72d6bb" + "@parcel/packager-raw" "2.0.0-nightly.428+5f72d6bb" + "@parcel/packager-raw-url" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/packager-ts" "2.0.0-nightly.428+5f72d6bb" + "@parcel/reporter-bundle-analyzer" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/reporter-bundle-buddy" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/reporter-cli" "2.0.0-nightly.428+5f72d6bb" + "@parcel/reporter-dev-server" "2.0.0-nightly.428+5f72d6bb" + "@parcel/resolver-default" "2.0.0-nightly.428+5f72d6bb" + "@parcel/runtime-browser-hmr" "2.0.0-nightly.428+5f72d6bb" + "@parcel/runtime-js" "2.0.0-nightly.428+5f72d6bb" + "@parcel/runtime-react-refresh" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-babel" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-coffeescript" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-css" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-glsl" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/transformer-graphql" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-html" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-image" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/transformer-inline-string" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-js" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-json" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-jsonld" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/transformer-less" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-mdx" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/transformer-postcss" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-posthtml" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-pug" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-raw" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-react-refresh-babel" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-react-refresh-wrap" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-sass" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-stylus" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-sugarss" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-toml" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-typescript-types" "2.0.0-nightly.428+5f72d6bb" + "@parcel/transformer-vue" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/transformer-yaml" "2.0.0-nightly.428+5f72d6bb" + +"@parcel/core@2.0.0-nightly.426+5f72d6bb": + version "2.0.0-nightly.426" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.0-nightly.426.tgz#2b652e74ecc073831d1fa5da7f97b8e13c220972" + integrity sha512-KqndVF5Fd4gf6Zf2gK6sY0qogS6OiyO6uF1oYL6+gqcFZKRNRi/9dd3lDverdAq7brt18bCCVXICy6dYme3DyQ== + dependencies: + "@parcel/cache" "2.0.0-nightly.428+5f72d6bb" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/events" "2.0.0-nightly.428+5f72d6bb" + "@parcel/fs" "2.0.0-nightly.428+5f72d6bb" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/package-manager" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/types" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + "@parcel/workers" "2.0.0-nightly.428+5f72d6bb" abortcontroller-polyfill "^1.1.9" + base-x "^3.0.8" browserslist "^4.6.6" clone "^2.1.1" dotenv "^7.0.0" @@ -2385,73 +2289,74 @@ json5 "^1.0.1" micromatch "^4.0.2" nullthrows "^1.1.1" + querystring "^0.2.0" semver "^5.4.1" -"@parcel/diagnostic@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.0-beta.1.tgz#0616cf75fd8e294643fe13b204ad1e60b42bb66d" - integrity sha512-LNfe2MgbKiqXnEws1QT9tANeg6iw+u6hNnuO2ZjRdnAIF/LqslD/6RKryE1yD1lh71ezACwLscji0CLuys6mbg== +"@parcel/diagnostic@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.0-nightly.428.tgz#40d618bddd08724c8aa845c8a8092fdf5b871bb2" + integrity sha512-UxtwtCFsdVKQzmcAooQkm1Wwtd27165/wB1UQrkXYx5RQpSptEhd8LPBqgFZPMzUBLbBp1izwxCQmmekG4ignw== dependencies: json-source-map "^0.6.1" nullthrows "^1.1.1" -"@parcel/events@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-beta.1.tgz#8f7c2276dab1938f6d47d2d2a64a1fde0b58b7eb" - integrity sha512-m//K2aHYnr4tSONlUmS0HhNQtmhYjCUJ+dv85mfLEqLlQVmsLBwxlcqBS5M1ONlaGPlI87xUKY8uBTg8kY1q4g== +"@parcel/events@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-nightly.428.tgz#f1e5852f6050027f843de9a61a7fdf12dbc45a98" + integrity sha512-PProq1xqik+5hemAOJi+bRqRkRVHsGQhg86KJkt9+4ojP0HAwmsOQrpULO0S0WPF8lLGaGNVs4xo8O3ej6JBWg== -"@parcel/fs-write-stream-atomic@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.0-beta.1.tgz#158fb2cb4bf56342bce71841794616909bc50300" - integrity sha512-ZL3da/3jhMvu6ZwZBmxEiGXOLq+qvVvvx6DVv+4JQV0EMAFp7gvD4CH0QKLCgKBbjd1wqEBPyW3CCEo5tGtAHA== +"@parcel/fs-write-stream-atomic@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.0-nightly.2050.tgz#0b647b977ce4959951599904044525bf4048ce70" + integrity sha512-f808N3PzbQceq0dkdQ7CZ1u32S0E8SLPeUOXlIPk8TSrBiq1UUA59nWJXNGCVTJ6yTUmKoFsgzM91xHWuQTB+Q== dependencies: graceful-fs "^4.1.2" iferr "^1.0.2" imurmurhash "^0.1.4" readable-stream "1 || 2" -"@parcel/fs@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.0-beta.1.tgz#bbe3ab36736cca7399804fa38e5daa553f9ec723" - integrity sha512-THLgnN+eaxfa4s0T3KIuOB1N4Lg5lwlz2Y1Y69vWujZh3B3PHbNDlJ4n0O0m+vgYZoJZ43X+qxId0yeHUh6p0w== +"@parcel/fs@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.0-nightly.428.tgz#7cfb856764e8496486f5e2093d4dc9c1182f14ab" + integrity sha512-rTIqQJKI7qXDfZcuCbEhI3/OJuNCulfZs4j8wYbSRDJVIh7gmdNwkJQhYzft2NlH5MkbusbpFxBus7UiiJNv+g== dependencies: - "@parcel/fs-write-stream-atomic" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" - "@parcel/watcher" "^2.0.0-alpha.5" - "@parcel/workers" "2.0.0-beta.1" + "@parcel/fs-write-stream-atomic" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + "@parcel/watcher" "2.0.0-alpha.8" + "@parcel/workers" "2.0.0-nightly.428+5f72d6bb" mkdirp "^0.5.1" ncp "^2.0.0" nullthrows "^1.1.1" rimraf "^2.6.2" -"@parcel/logger@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.0-beta.1.tgz#a987e863db6d5ba02092894c2ef92bce74d9ea23" - integrity sha512-PMhAs1vCPSDKt977w0cMNCEHjBTy94Khc1Pr07/YXwiijxzDBxPS9JmV8dBbryTheRy/EyUhJDXES3brQwiiqw== +"@parcel/logger@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.0-nightly.428.tgz#9cd9e5f70254c1e52eb78087e70046545fe2f506" + integrity sha512-cKljUUzvyo3ZXPQ5VRoOJ578h/LUrXpigYjQf2Fk3HnbKokdjljkEIyJCe/tlPLqoP4fAnh38+HfoJor24qoqQ== dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/events" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/events" "2.0.0-nightly.428+5f72d6bb" -"@parcel/markdown-ansi@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.0-beta.1.tgz#3dc3a805a475f560328cdbb2ef3ea892e468905a" - integrity sha512-J1r4m7LczK+X26p/tjA+9VP0g4vzYNUlUyFvIHt7dcdSVEjLM2ICUQZSIzlRHXlVQ+ciMxOFUYtDBCzAzXiLfQ== +"@parcel/markdown-ansi@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.0-nightly.428.tgz#b577193f5cdacd302030793d10bcffffe61debf0" + integrity sha512-Nj9Zl4ik/i056KJ8meYDlfBvZNOnneA5AyOCvqlC4Ulk5EiOtEhC1oowbO/T2eDYCI4bmRxzb1Sf0dQ6y89mng== dependencies: chalk "^2.4.2" -"@parcel/namer-default@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.0-beta.1.tgz#f8c018648f3991e537462fa7f73925cac19b3276" - integrity sha512-b3In/s++ykB/EL2CLAFWEx0/GUaQ0TMXAfpLfd9wCmqsJq1MQxW437aujF5bIDNHjEE7+rnGk4XP+IO9uViWpQ== +"@parcel/namer-default@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.0-nightly.428.tgz#f2eb5b3307717d0b041bd6cddaef3987b9f5d717" + integrity sha512-ZIyw48pQaMWvsIzugg7VUl8osrdMEsnJsC9K+mqr/UZj9jxDhy151WEdUu/OSQUXUnD1W0eULEksYCTUGVPo+w== dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/node-libs-browser@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.0-beta.1.tgz#7004d99e2e76b238c362ca1262f30c74e3f36c84" - integrity sha512-U9pS9KwhTluA9atSzU4X5173BXRER7BRqPSSAEBrfAbCeIfzAG1d1+bhyMZs3SBSt6afWU5HBy//wB8OWBqcKw== +"@parcel/node-libs-browser@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.0-nightly.2050.tgz#fb4700beece5c36d7495590646b0f3541d0228ad" + integrity sha512-Vv/1AUmSAANKgkpBhoIK0V0KcBl6WbEuPqqjbbZ0HIrKjgyMq4kfyDpprvfNmnCVEcildFYSSLWNhfYCMjD/eA== dependencies: assert "^2.0.0" browserify-zlib "^0.2.0" @@ -2476,70 +2381,71 @@ util "^0.12.3" vm-browserify "^1.1.2" -"@parcel/node-resolver-core@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.0-beta.1.tgz#012234b691fe2a913210544b2be538875cd16614" - integrity sha512-A2Eu+TEnh90Q+iisNfKmaHTPbIR/eshRUqze8PFvSTKv+n9ejNOJ0GCb+x/PFqeqavJRXUBFc60F2pAp88+Bjw== +"@parcel/node-resolver-core@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.0-nightly.2050.tgz#0bf66076f65949e73e94e80fe5bec377682bfbaa" + integrity sha512-MREk4sjv2KmxTWfZbW8FMU4mKAwkKd/VkCqFzpcgBQzedrGx19g+HwdcgnDvwON5NRHZp1qEFmRYeHHQWbiK3A== dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/node-libs-browser" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/node-libs-browser" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" micromatch "^3.0.4" nullthrows "^1.1.1" + querystring "^0.2.0" -"@parcel/optimizer-cssnano@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.0-beta.1.tgz#a233d16e8330c2cf17fc897b9feb5265ccddccaf" - integrity sha512-f7Yz7kWAVvhRmT/QXNFrlR//pE7D1+G4iWYKstUFWJP0SsoquQBUsb3NI6vltUvydN6htcCRHGX9GhAnHRpaFw== +"@parcel/optimizer-cssnano@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.0-nightly.428.tgz#8e539d0a1e1717a7f90fb85ad319e6ba07434bac" + integrity sha512-kPUAsPbQwH9NO3jUOfJh3RH8KV/N3rdt5ZAgIW+yhbl4mkUk+tMXE1pXXbN1F44saQNR/GKn+Z0i98x3sF8XKQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" cssnano "^4.1.10" - postcss "^7.0.5" + postcss "^8.0.5" -"@parcel/optimizer-data-url@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-data-url/-/optimizer-data-url-2.0.0-beta.1.tgz#17491b755e0a66492888be2ab0f774519502107e" - integrity sha512-H+oMRbBsYXm0GEpzwkaNO3VMxmt50yIg9IE3dyAkXDIZ4kOpaDxYJkGuuR51L5BzQSbEeXwipzoqd2LoLJ8O6w== +"@parcel/optimizer-data-url@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-data-url/-/optimizer-data-url-2.0.0-nightly.428.tgz#bb4d4bc6970e94ddcbb21fe74c58fc1f5f503fcd" + integrity sha512-WB1PbSlej//iVaoM7w+sRo7rHrRNNemiZysy8amXfzDkQWYHKmQc7/9iQtr9qf3TGrR50IZTcDWViyNfp8mC+A== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" isbinaryfile "^4.0.2" mime "^2.4.4" -"@parcel/optimizer-htmlnano@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.0-beta.1.tgz#8716b21983dcaa87165c1d89b822f8e918805822" - integrity sha512-Mz8gkvOd6pyazQFmOjGugz6Q8ydxqjeA6PyrE21/cTj8CtmLCs4TCfEzLcU79qHyr8a+KVx4roTCUwTGv+J/7Q== +"@parcel/optimizer-htmlnano@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.0-nightly.428.tgz#bd71fd60408c4d2f22e7d936874bec01cba070c9" + integrity sha512-h+f0Oj+MUBcedHK31vMEB2tBbNOGGVN/tNSjkmpeURr4GDR3VletHpd3uDJwp3M2t5Hi5x8tRF+PKAA/XPfCfQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" htmlnano "^0.2.2" nullthrows "^1.1.1" posthtml "^0.11.3" -"@parcel/optimizer-terser@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.0-beta.1.tgz#0f5c9e95e3a56675763d532db3dc470b12aedbe5" - integrity sha512-S6uu4Q4L8NV+TQes35dQmHqcA6NaRmQSI0vl75IGSaTJnvnq/M3fvrTUd6Xp4dOt/eLh4UJEH7F01TbO1KFZwg== +"@parcel/optimizer-terser@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.0-nightly.428.tgz#7e058b63320f6ec631b46f04896d2fc73bfbca1c" + integrity sha512-jdJt+kqTVx3QVwoZ1ghDSLxkLaklGqeRzCcDNeMx8SnXgnG4nohqvl4UeG5SRdj1V1+2LSVt/zW43XIY5wpp7g== dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" - terser "^4.3.0" - -"@parcel/package-manager@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.0-beta.1.tgz#7da4b67df8c91e2e5284c5f0a0bf36cf220dc644" - integrity sha512-3i7YXkR0TjJ7RcDpLV5ki5PLsBcdux9BILctC3LLQbNrW5kZjwztEOCnaHJbcQ3VaHaLu6znU8y24EPoyBOPMA== - dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/fs" "2.0.0-beta.1" - "@parcel/logger" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" - "@parcel/workers" "2.0.0-beta.1" + terser "^5.2.0" + +"@parcel/package-manager@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.0-nightly.428.tgz#9a47a91cb604120f8a154e1108309d0384ebedb7" + integrity sha512-M2poHMzbQ1nlxfrJD1C23Fh8YUBh8RB73HMXEpKrPAEn+S5AIMxxdc6oeJbpTpUIEiyGm3MkCt8nOk/la+StgA== + dependencies: + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/fs" "2.0.0-nightly.428+5f72d6bb" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + "@parcel/workers" "2.0.0-nightly.428+5f72d6bb" command-exists "^1.2.6" cross-spawn "^6.0.4" nullthrows "^1.1.1" @@ -2547,91 +2453,91 @@ semver "^5.4.1" split2 "^3.1.1" -"@parcel/packager-css@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.0-beta.1.tgz#374bcf0af5736694bb35a11137152e2829610326" - integrity sha512-oi69yNnSZTuQ8y8GuHOFgZ9qQ5oBktNuOEhOoHd7Y15Srior/SwPjx0/UypBweEiKRGApipJtrgo0XQvr5x9aQ== +"@parcel/packager-css@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.0-nightly.428.tgz#15461eadba085a6144caa6416a9757396a4eeedc" + integrity sha512-O5lfvpPuPUzXih32+bRm+6ZcTygUq4K9rHcVr3OY9neIGu7exYJtKB0ysoG6B8fZo7dt3jVytBZHnSHyZMTNvQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/packager-html@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.0-beta.1.tgz#c10d341702b4957f17b4c9e30af1a8362a9fd947" - integrity sha512-ixuLWNpFGNYCKHbrhx2mY/aal1ORJyjaOozCLx2jL5888JeCw34jCWZcXSej+hc50KICr/RVd/WrA5v6YvkBMA== +"@parcel/packager-html@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.0-nightly.428.tgz#ab66dafada49cf5397981657ec51258b95801b09" + integrity sha512-4ZTfPK5o4VWui0U4QM3rU4+qgSouNeweahN2lyTaXML4LLHb80i02/VgSA/cE/6kOfo7mNMv+jnnNgrLBYQ91g== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/types" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/types" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" posthtml "^0.11.3" -"@parcel/packager-js@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.0-beta.1.tgz#4ddf768fe1f3b3ae9dea10291d3d563734a45d90" - integrity sha512-4sWVYv/uPDokftaMquZa1f70wE5O85bcRklcs0kUGPjWNf3uaCG/tS6h9USD2GDbcdjY1xDNoClu6EUTM1AvhA== +"@parcel/packager-js@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.0-nightly.428.tgz#baa2288d9346013ef128ef0dfdfafc43fe6eb3db" + integrity sha512-vbiFYwbcCXCU6VJfgcndx6eB9G1cUaOXaDsdE8eOviuzEJxxSAzhtraxDX5BUDxwZUBiqsjfm+AKKvwoYWnbUQ== dependencies: "@babel/traverse" "^7.2.3" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/scope-hoisting" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/scope-hoisting" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/packager-raw-url@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw-url/-/packager-raw-url-2.0.0-beta.1.tgz#fb3378bf28f77cc84a27efbf0b7d191a4d06c354" - integrity sha512-elXRSp0ig1Q4y2xMpp3AGPVPFHOB0dW/qT3FbXJwBQhUtjOJrfOtQUlT59l0xP57pADS2aihgnQx0Vl13lL7FQ== +"@parcel/packager-raw-url@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw-url/-/packager-raw-url-2.0.0-nightly.2050.tgz#c1ae7df401e3ac2be1d7f275fecf97d075685191" + integrity sha512-UBXtPeEpmtpYmCCw36HkNRVKzltQWceRzeM4WFHAF+30Zn96PlT+EzY8K1gtl2xyQ7yqa9lAm2jkEnuC6HeoPQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/packager-raw@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.0-beta.1.tgz#3e3c574e6134a94d77ead5a850151716986b645c" - integrity sha512-Os8m1DuPfGqjcR4avbZbJ79Rz2bvGsHx49BJ8HtJj0YnpZbnox7z5njn4PlpWhr2XAJmNnctrQ8ES0Qvrop/AQ== +"@parcel/packager-raw@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.0-nightly.428.tgz#6ce37a7a3b70b59a3ea19274a20231a802107730" + integrity sha512-pzWComnfjMqLKKP4qL3Fr5AQyxAACIS3Ms0tjSzVJgRf6s3Ef9IHfGZmWoB8EEICdSi914zrGlTA/06dWpX6hA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/packager-ts@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.0.0-beta.1.tgz#20e5f8449ff60ccee3999741cad6bd33d96bdfb9" - integrity sha512-x/sOSvIVV6z7ltG1XFuJ/Da72HAHkRxNLCzE2ggEnuB9kzOWXAlIHKeeGB2Ql3lV2OOMtoQoFg4BM1nBLJzFCg== +"@parcel/packager-ts@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.0.0-nightly.428.tgz#c2989bfe3774da9305a485ce6af7bfd5594bc700" + integrity sha512-tmFJUgImvnrHBdSiGnzJzs92D/mHyCkamSyhphz2ZOa1xMaQbw3V4Hj50RXnmjNTZPyspDDkFk2TfMjISgDSIw== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/plugin@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.0-beta.1.tgz#b19f571f59a4f979cc1310d3e0d073fbd9988bd2" - integrity sha512-ik8kwNTJ7wfPRWHQK5NrUat3WGOZazIv8v1Lbgew6XuVTc333FitvBn6mxpPWp2N3+GWWrVGmBG8m7MI6kB8zw== +"@parcel/plugin@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.0-nightly.428.tgz#0a8b3c0bcff4b4233cb85e4110f819acca291a77" + integrity sha512-rbHyE1zBOWM62BtoDnjDgXrdWDsdRDK9NdHY/IJdILnrrqbrRamY6x9xkOMuvrIBOkN7DmnfbFi2P8yUyaqr8Q== dependencies: - "@parcel/types" "2.0.0-beta.1" + "@parcel/types" "2.0.0-nightly.428+5f72d6bb" -"@parcel/reporter-bundle-analyzer@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.0.0-beta.1.tgz#98b39a6cc56792d48e83e7ec88b176adb133e600" - integrity sha512-igUEsMSgyIiAEvmjdIydcmYpTFUKQB5EAtbMEYvRWlCSEGR08EZqaY58SIiTaTUv4maQuAi0k7E3QQSDeBtQ/w== +"@parcel/reporter-bundle-analyzer@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.0.0-nightly.2050.tgz#bedabd5a0a631a98fe4152f7fd2093a766ff1e95" + integrity sha512-lRBWNQZXu71kvM07Rf4+1EDMWPCsV5+eowOTkPxT1eWGt+HPFmmxtb7JAUyHKcdId4IXEcKkWAssvX2+TlEPUQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/reporter-bundle-buddy@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-buddy/-/reporter-bundle-buddy-2.0.0-beta.1.tgz#7f01ab7d37dc214380e25a1d8572f6074fe3fa49" - integrity sha512-pW7SJFKGkkjOOYYcE20BgleC1DRswM04fZ82S9NS0idnEsJ1Z+z/QCww24J1Vfx5FGEQDVOl4siHNILm04kvdg== +"@parcel/reporter-bundle-buddy@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-buddy/-/reporter-bundle-buddy-2.0.0-nightly.2050.tgz#bf75aea295515a82ba97d7a731e1bd9a5fe42a39" + integrity sha512-RkI3qbE76jWYxRFMp8y1VSnDKRypZ6WqVQp3FEjJgk6Uf/XfecG6S9fg7Lxm4JeSJhJ1+bsRTl8RoqV9Gx5XGQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/reporter-cli@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.0-beta.1.tgz#ba0effb57f1ac397275e16e76b51f29d8f2bd179" - integrity sha512-KmyfFhClXghMxZXbS8tkoXZZqVfuLzlUDwedMAN+Jr9h8vSZYaiqjq88mNdrLIubqLepbNG7jUcXPu/Y3ynz6Q== +"@parcel/reporter-cli@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.0-nightly.428.tgz#54d65cf83840e0ae22e5fbd5df3565c7fd575fb3" + integrity sha512-AVIZnI6olE64AkdZe9XLyOYBQUxgmH58N6oyIljn8COVpeLN7C2pNiJMlaXODxat4yqJg8LR8dt7vA0RMOcM6Q== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/types" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/types" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" chalk "^3.0.0" filesize "^3.6.0" nullthrows "^1.1.1" @@ -2640,81 +2546,82 @@ strip-ansi "^6.0.0" term-size "^2.1.1" -"@parcel/reporter-dev-server@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.0-beta.1.tgz#6040b4d2da7d65a238775f51a39ec6f5d7ac5ddf" - integrity sha512-84u4lfwAqGrIrbuyTYq88PckSe1mKP+f+D5aQDObWBqDy1gSokM+yKnQQHoddQAhviyTY3tJU/SEvKSp0Dog9Q== +"@parcel/reporter-dev-server@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.0-nightly.428.tgz#c77becc777bfe72d2a155136df0432e5b41742b2" + integrity sha512-ipr9XFKXWrQ6lWlkUeLYgCHcaxNbREb/Uh17t+n7E5vD7kQvvW7ptDi1MB1IqJAzn/y4a4OTLwJV2ZKnwEN9Iw== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" connect "^3.7.0" ejs "^2.6.1" http-proxy-middleware "^0.19.1" - mime "^2.4.4" nullthrows "^1.1.1" + serve-handler "^6.0.0" ws "^6.2.0" -"@parcel/resolver-default@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.0-beta.1.tgz#eb3349339a18c5d8aa3c01d8813d14640e7f45b7" - integrity sha512-BnnHY83oqOIk3oK7QsJbB2QdtSIWVy948JI7bhfFC0+8zWMLifE28KTZQD1JGxICOre75DK1JRN26l8F1gtuiw== +"@parcel/resolver-default@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.0-nightly.428.tgz#ef3adc63137dd23f53b5d230cff32fa732c88098" + integrity sha512-ay0HkjAZxtBEbSYTF65wCfxilDE0LkVeuwgapG8Pd1/FCw2zU4dA/s7MozRmb6DQ4cU2hZL+qbNgLOyQ0cSNuQ== dependencies: - "@parcel/node-resolver-core" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/node-resolver-core" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/runtime-browser-hmr@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.0-beta.1.tgz#6c0e85e13c10cc1f0d91c4d0ae8e45bf954f1ed7" - integrity sha512-5JQGDLwqAGk0kNqlgKPA99mPN8srpabu/2drpxSBKWXDA71v47JRYcGx8gQsvdZGgj3+rrkI2nyC8UlYAM+m+w== +"@parcel/runtime-browser-hmr@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.0-nightly.428.tgz#df69813cdf1f8ca05ed9ae9a4b3de0f41f255b5c" + integrity sha512-Sa2gGxveh3qjv6zCGY5l9yOgPjrqZrSzw0vISdtsgnItqW5o64Y2ZAtLS+nb1vH7KwEuhm9/O2tcye0JqeOEvQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/runtime-js@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.0-beta.1.tgz#12a370afad5333b03d5b237ee96c74553e95a743" - integrity sha512-1EHrOoNzCdksj4QfLuIr5rC8rO8FsVpQdl4ZLggMsxQV67wFiG9j1KwM/+Jmpd6dsmdkSW+b/NAC5kPXnE1EfQ== +"@parcel/runtime-js@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.0-nightly.428.tgz#e71798bd93144d9b4ec9665d23a7a68b88922340" + integrity sha512-26kopuJ1l3KvIc5Br+ctotsZ3BMqKlVHc4GvH9YilYGehqEaF1GDEX9Xjd2zBaLfQuFmHSM+dhFCkfz8ZVjQlA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/runtime-react-refresh@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.0-beta.1.tgz#eeb6c0179149b69e51b29cb5f02e59d93706aca6" - integrity sha512-d7lShNcgyKAdrDwAqNzVeOeQ7MuqtElw4YPxHAmTrJnsr8k+scPmK7IiUSPqItuvLIngpS1IoLY1SuEAQAY/QA== +"@parcel/runtime-react-refresh@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.0-nightly.428.tgz#f5f4b9c0d71c7a9835fe2c0f559215c09593f43d" + integrity sha512-CSA7HRbQrpSZK7ulZpKrhW/yN97TTxGUeqoYHKZ34xK+lPCNGNNBnQMrBoKxJ2xEE1nEiK8uYJjm3/3W2qAItA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" react-refresh "^0.6.0" -"@parcel/scope-hoisting@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/scope-hoisting/-/scope-hoisting-2.0.0-beta.1.tgz#7013c831c2880491c000325c1d6233c76088c5fc" - integrity sha512-U0ZcejwG6vMj+cLHScQ7lhPWaaXgNk+xGPu6adWNQVbRB3PqJMv6jIhBwgR2afWzNwu1a3vg1aRO9DH/jpxGdg== +"@parcel/scope-hoisting@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/scope-hoisting/-/scope-hoisting-2.0.0-nightly.428.tgz#b2a6d2d1eeaf3042b8c6a6ccb015b024488c0e27" + integrity sha512-31F7KlPhSteVSTT5+3ZAB3rFYSEtwsKv6qnn+0GBpMN2lZROXzSuo016OOwirCqw9mFwnbHf0/tm5qmP7tlaLQ== dependencies: "@babel/generator" "^7.3.3" "@babel/parser" "^7.0.0" - "@babel/template" "^7.2.2" + "@babel/template" "^7.4.0" "@babel/traverse" "^7.2.3" "@babel/types" "^7.3.3" - "@parcel/babylon-walk" "2.0.0-beta.1" - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/babel-ast-utils" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/babylon-walk" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/source-map@2.0.0-alpha.4.13": - version "2.0.0-alpha.4.13" - resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.0.0-alpha.4.13.tgz#2b262be6e99a15e9de3f6656df27c0b0c4809b83" - integrity sha512-MnIfYmaRqnwK8kRRG9Osu1DkJGl2bVSfJs9SqauJ9N4dOdAXAFGeeDtzXnHvx02DO+HwK7YjsbhBpSvvgZcWHg== +"@parcel/source-map@2.0.0-alpha.4.16": + version "2.0.0-alpha.4.16" + resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.0.0-alpha.4.16.tgz#d311b82f352300a829d76ab6a9f9d0c63ed9490c" + integrity sha512-yA1ulttYIbmZVzwCO1EipQVqBqZAoiFK/yAhum/00nQfqBilz+1LdahKSZnQ0LSqk42o1qAULAb5UHo73/rRDA== dependencies: - node-addon-api "^2.0.0" + node-addon-api "^3.0.0" node-gyp-build "^4.2.2" -"@parcel/transformer-babel@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.0-beta.1.tgz#9233185441391258d664c9241352f66a2395452e" - integrity sha512-nP8TsjhYYvL5NjRX6WNZFXjcY66fwrPthslnycNldoHpmRDoRO1PFjqx1zzJJEUc1S0r6M2kTQhDuh4dXhANCA== +"@parcel/transformer-babel@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.0-nightly.428.tgz#7e051b1cf177766d788a0faea3c4f85cf236129d" + integrity sha512-KASGFzx0m0tjqJqxyXEHc6ool4eTrBdhIJcfcOwBSoNSVDAloJxqqqgROSWL3bJmOraj85ToDJKPhBwXVYvBUA== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.0.0" @@ -2724,271 +2631,296 @@ "@babel/preset-env" "^7.0.0" "@babel/preset-react" "^7.0.0" "@babel/traverse" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-beta.1" - "@parcel/babel-preset-env" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/babel-ast-utils" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/babel-preset-env" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" browserslist "^4.6.6" core-js "^3.2.1" nullthrows "^1.1.1" semver "^5.7.0" -"@parcel/transformer-coffeescript@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-coffeescript/-/transformer-coffeescript-2.0.0-beta.1.tgz#b399b95620e71d62738fb510f68454ce0962a614" - integrity sha512-EP0+RAGrMIGoIG3g3VdjAIkMCfFj8gnVp8e8n61nzrnqE7pmF8KuGtGAYz6U8yQb39a1OF41sEg1huvOUT3nKw== +"@parcel/transformer-coffeescript@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-coffeescript/-/transformer-coffeescript-2.0.0-nightly.428.tgz#09fdc807bc06eababdbd8bd791bf42f6dfeff7bb" + integrity sha512-pEr51sfPjHqcuOFhLwT26+T4QWcbiUFz12521N5C+yJnpDYEOH5j+ojVaKjLefp62n2PSZKUzsobTB+TxHhJrQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" coffeescript "^2.0.3" nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-css@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.0-beta.1.tgz#68899a329695dde4115ab44a1a7c08d38c99157d" - integrity sha512-+vRvTWeQNxzRfsv9A9gGXYt6p6NU6LcyLvByIMzwDhqsMq1wsIEmw+YPM/2TzqsKI/f+B0jkTNCMI2dQobFX0g== +"@parcel/transformer-css@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.0-nightly.428.tgz#2173056e77387825f86ee0028df6d569f47111c6" + integrity sha512-IkIaoSxnfwz2XPUujqQr15buj/YnD4vWVY3BZKyDQ6wElOKXag9K/zSpYsDZk7S28ZKaDM12XyOhFKZl1SBdXA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" - postcss "^7.0.5" - postcss-value-parser "^3.3.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + postcss "^8.0.5" + postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-graphql@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-graphql/-/transformer-graphql-2.0.0-beta.1.tgz#7c1dc7718c9dc33ce65c3f0e36a9306f85e8e395" - integrity sha512-IYuSUHadoEn5plZx78fZr8ViGdU2GqbY0opkGP21i1Z0TiB4h0ON9VMbEgnABAGeR1b+9rly/8e9/isOC7jcow== +"@parcel/transformer-glsl@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/transformer-glsl/-/transformer-glsl-2.0.0-nightly.2050.tgz#2d5bacb53d8c8e8016bec0322943bb97ac9d691a" + integrity sha512-G3ROXTtkg8Ta5GakogQ/y2HMcc3xLcrhm4cKkDYBSSNnfnlqNVbB4CzErEv3VNLiJATQgnH//Aq2V3aZ1ui8mQ== + dependencies: + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + +"@parcel/transformer-graphql@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-graphql/-/transformer-graphql-2.0.0-nightly.428.tgz#66e756dc5826d79732917d9e8bbecae29763a702" + integrity sha512-ILBirKHDsbWMSiUCMKs25XBoCkjI9ZZPakpHANwVVzK46scOqyCZtD8uDrNW2IqBecoviHH9e1E2ke50AphWow== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-html@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.0-beta.1.tgz#e30e9f08add69afc9c82d9e748ae554dd6701074" - integrity sha512-YkwNLKsMrNzyA4u1I8OvTxhYAfkAayP+rDGxcIuTOmv4IVZGoRCWMtYqUl40xdIVE0QmJpbkAuQtG7Vn3xOlHw== +"@parcel/transformer-html@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.0-nightly.428.tgz#407875dfa6ccd3fb12ca0f24e0b1c4c5c3669b15" + integrity sha512-o0/w/+NLcPPUF7//HoTF/VQaALh+4YjCdhor8dCJgiVqX1tL7HEWgZ41TV6YrK9CF8eOIMp9iJLEtC7blEs9Eg== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" posthtml "^0.11.3" posthtml-parser "^0.4.1" posthtml-render "^1.1.5" semver "^5.4.1" -"@parcel/transformer-inline-string@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.0.0-beta.1.tgz#306deba7dff779c7e450136839f51064c6cb1672" - integrity sha512-XfhPAXWvrWQSOwHGQU3koP6vs85mcjyKllVGcC24/Io2nZhzACw6br0fzZL2gSVZeEt+/TtfxVu7uA44S8tA8g== +"@parcel/transformer-image@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.0.0-nightly.2050.tgz#f57424c4016b46a4e92d8355af3821c49e07ab09" + integrity sha512-TLyxN4G86POpHW/VdGXloLwM3eu0wRfJMagjNue3nDl0aKGSvpT2OJZEpyhUui7C7owytM15s23+DHI3miwjpg== + dependencies: + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + +"@parcel/transformer-inline-string@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.0.0-nightly.428.tgz#8979079900022f74512a5fc6eb3278d8900a5fd6" + integrity sha512-DVwKaFobajhNDA9D7fewXoMtxwdgIUpZ6nK3m8BgzWHoH6zvDkkxaRqUfaodDtz+O46K8a80bwPeQsjqmVZyaA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-js@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.0-beta.1.tgz#350e41f2536d55eb69cc8628b918c5a31d1ce70c" - integrity sha512-60rrUF+ApahxKc1eQizyo7umvOhIRe3sxRIYub61qRPan2S7aU7PbScZ4bboh3alQYLztTNQh2VazM85oyiQSw== +"@parcel/transformer-js@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.0-nightly.428.tgz#d086557b4aa7589de1f0bc2105f89e40042a8291" + integrity sha512-RmCOay6oEGV0v7bjg7BC9l1St0f3WVBtTZUP8WNVWbfVv3zSNod5gTMaLI+4j1EDbzcLTm99d4XY6+mxK2+EFQ== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.0.0" "@babel/parser" "^7.0.0" "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/template" "^7.0.0" + "@babel/template" "^7.4.0" "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-beta.1" - "@parcel/babylon-walk" "2.0.0-beta.1" - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/scope-hoisting" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/babel-ast-utils" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/babylon-walk" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/scope-hoisting" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + micromatch "^4.0.2" nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-json@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.0-beta.1.tgz#c1519d3ca166d223d185fca1fab9fc32fd4e618f" - integrity sha512-v39TaEWgJnoOqOQGtZgZBAq3dihufqeSm9+szIxzm4FiwzOzzlyo9QiLSlCfjDbuWHFvQkU5AJKSlhdS2qbN3Q== +"@parcel/transformer-json@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.0-nightly.428.tgz#f00ae358c6f6b0a1ac1676fcea189f45350ab447" + integrity sha512-6zt/xB4Abe3HW4EXboBN4eAMnX9AZ+s4ffMoezGVHVxlHClKQ4Hf7OkdHef/KZnu33JNjeC81BofSYVDMPD2xA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" json5 "^2.1.0" -"@parcel/transformer-jsonld@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-jsonld/-/transformer-jsonld-2.0.0-beta.1.tgz#ccff5e330ba82a137e4166e8538b9f5dd921c778" - integrity sha512-rf1n90XjrYDCD4jZ3+z+acQm4hznOe2l2Os3aVLCs1jvf6TAXMOtkHN1ABh63GAON5LJsary3YdFNrullDRTFw== +"@parcel/transformer-jsonld@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/transformer-jsonld/-/transformer-jsonld-2.0.0-nightly.2050.tgz#8f877bf2abe86794fceb451df31ed3fee33ec62c" + integrity sha512-GG89Hfz4Nf9N10cJdBF2rrCJ1MSnTvVX+WRP379KxUHJh540O8xSVxcBnHnt5ByOD0JWB0Qkk0KyQQOe7K7WhA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/types" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/types" "2.0.0-nightly.428+5f72d6bb" json5 "^2.1.2" -"@parcel/transformer-less@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-less/-/transformer-less-2.0.0-beta.1.tgz#09e58220404781f406e9241a1ea859f19a91adc5" - integrity sha512-jKi29fKEJW4R1e0Rwo2nTfEeIA11vdMs3tHOhkq1O3lfkmK9UF98uFlweLyfF8Mm7XznEqKL7aP58drJ8UqVuQ== +"@parcel/transformer-less@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-less/-/transformer-less-2.0.0-nightly.428.tgz#436159b13cba102d114eff58c4ad082b5322ce87" + integrity sha512-gSrGzckTQi7b+g8FrdsJiysgHSSzeEPA7RGE0MDjHllvcjpTSTvkIquS2ImMhTeID6b4DlxSo12JkGUAsQf15g== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" -"@parcel/transformer-mdx@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-mdx/-/transformer-mdx-2.0.0-beta.1.tgz#139057e1acedb2b6eeb5472e60f3853d20ab4760" - integrity sha512-M4Jjws7n5at/AVMiITz6KIMZMzlF7WHuhfQ+YzAqXS2Z+f0mJ/c2/XNOSVrKbcjn+9aKoSYqwq6vmJ6wUSaBXQ== +"@parcel/transformer-mdx@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/transformer-mdx/-/transformer-mdx-2.0.0-nightly.2050.tgz#b03c7a0f4c9b8bb76743b06600a78a9224cf25d6" + integrity sha512-SWvTLYNy+ol3iHtOySeXq/nDMseTClIaX5yBrzvXlKbWt2voOhQjFJiUmxk6/mUqxRvBzwRr+0t5SnnKDWpGUg== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-postcss@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.0-beta.1.tgz#15f8802730d6d2ba21ef044119d6394657e248ab" - integrity sha512-RLJcK5rsNv7wzehZfqR9w4lBXF+OKeza57HJ7KGph6bBtwTsLedlioQakU7uXFWprbZE6NUa2ZDIxUhWmP9kQA== +"@parcel/transformer-postcss@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.0-nightly.428.tgz#02b4c8cccbb672a074dabc07be54e700b2257fa0" + integrity sha512-fYt5mAGMjWzG0Nucgy8M6qP5B9lIBsKwgpZM/ZlE3vP4AX2+CVtswYh8kDz44tQ4KLtxdCs0r2TJkVZ+HzW2vA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" css-modules-loader-core "^1.1.0" nullthrows "^1.1.1" - postcss "^7.0.5" - postcss-value-parser "^3.3.1" + postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-posthtml@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.0-beta.1.tgz#db1b1b5b96203225cb05916b6091c3bb69e3ef2c" - integrity sha512-dUbB3dB57qtjqmf00VeZIQRNnbEVOLCGchTIQXOoa1Ys0YqcEHNOAP7yU/njykQtxIBNp41CRXkvLl+4M6egyQ== +"@parcel/transformer-posthtml@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.0-nightly.428.tgz#1c5e845aab7201b6832215b6e44b793a74daf7f3" + integrity sha512-o2a02kFYexpKHR+1eMZE8bcmoMC7scKlHhFkaYD5LObdfAkQSu/ymFK692UUj6nuBCaqHu5DJjBqSoDVDH8pTw== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" posthtml "^0.11.3" posthtml-parser "^0.4.1" posthtml-render "^1.1.5" semver "^5.4.1" -"@parcel/transformer-pug@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-pug/-/transformer-pug-2.0.0-beta.1.tgz#45ffdb02d17fa3bbfcbb8acf07a28d30734d8175" - integrity sha512-o6jXARg1HuEetLdKRn6EuJ5YGFbql3mAtK+D/Tj5/DDg1RIlBK4Cih3Gi/lyCjHF7a7Woc0mKP09cA95va5ZUA== +"@parcel/transformer-pug@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-pug/-/transformer-pug-2.0.0-nightly.428.tgz#498d7332aa43725ed7c4003714666dd3e198a0c1" + integrity sha512-6b8qI6NfAyp9nac4Ffd6WgwUhV+SdmqAin1AUGvrPpxLP52Vd8ZyvTcPC7E2yHzoQ/wBU+WqMcvGL6i5x2efwQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-raw@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.0-beta.1.tgz#7a46974e08b1a45bb633e3e14cdceafd53f37c08" - integrity sha512-SLqd5KBH7k8Gh25MMUTdR6tdWWWTVKuQNF76uOzysIpdbQX/ix7v12wmyeSOxivrc1eDUtILkLUH0e9HIxWVvw== +"@parcel/transformer-raw@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.0-nightly.428.tgz#58dcb4f563869512cc148783aec3ba527208d610" + integrity sha512-m0OQ1ck0/hAfylpbJqtuIQTCDA2Jy7Y86SKV5WgSYKR1jjfelE4BJWWCGtl4I4mtK2/VpMvCQZKd6Kob4AJOog== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-react-refresh-babel@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-babel/-/transformer-react-refresh-babel-2.0.0-beta.1.tgz#ed327591e50d5e2f4b29c8b3b74af1774caecfdb" - integrity sha512-DlsjjujHGgmSEsazN9Ng/0Q7dMSATB7kxAe7CADE+Y8cf8kBmSeeuFokbnxTGg3TBeGMjeeHcOhXwXwNeHkEQQ== +"@parcel/transformer-react-refresh-babel@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-babel/-/transformer-react-refresh-babel-2.0.0-nightly.428.tgz#25d5af01e55bef51edf83ad229e5e5f5970aa991" + integrity sha512-uqPx62jUjgt98Jq1UJcA6RuI3F46HAiF4DxuAJQNyQjGAtx4WZeZP/8iH1K1hpgyRGB8fBJwYxVtuwFCfxATlQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" react-refresh "^0.6.0" -"@parcel/transformer-react-refresh-wrap@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.0-beta.1.tgz#e10dcf29e4859a4423a272133fc3f4f5f5d1d963" - integrity sha512-PJiCdXGQgd3Z2zVs1KLie9sU5kagtMMWNVHomJ9QeG4tnvoG/J4P4klJRLO218GqBnB/s4u8vNH0QbGuQO98HQ== +"@parcel/transformer-react-refresh-wrap@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.0-nightly.428.tgz#beb9c3243dcb18cab717d75019bcb9c99f62f3b9" + integrity sha512-2iarFS+oVuLsu4yW8EbOkfl7SYRy4krL/Bqv6jRHYQf1YEG4fcStZusfkEcR6IwV4XDBHE4NkuTxhAKwFbGqAw== dependencies: "@babel/generator" "^7.0.0" "@babel/parser" "^7.0.0" - "@babel/template" "^7.0.0" + "@babel/template" "^7.4.0" "@babel/types" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/babel-ast-utils" "2.0.0-nightly.2050+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" react-refresh "^0.6.0" semver "^5.4.1" -"@parcel/transformer-sass@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-sass/-/transformer-sass-2.0.0-beta.1.tgz#848888573ee10f131ac2a1e8495b503236f9dda6" - integrity sha512-E5sPsmtmvqsB90YEbDD7VFQ5o6+t97YQD35aIZnsZHkuI9MhFEaVHROtzeq3n3nuZ6tjyr6rv2SseZj1yculzg== +"@parcel/transformer-sass@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-sass/-/transformer-sass-2.0.0-nightly.428.tgz#576bf41e99da779c2324179337e30a22da5e0bf9" + integrity sha512-W2AMLKI3OHvLrbCv1XJoeCA1KLmsAgx5UNWYL36P0vgS66DCy/JfqggtJ/8Oq3kXOzVfmjONwGlyn9O+0/2ZuA== dependencies: - "@parcel/fs" "2.0.0-beta.1" - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/fs" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-stylus@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-stylus/-/transformer-stylus-2.0.0-beta.1.tgz#5ee55a080cad210f3d7ba0c086caba4c85821b93" - integrity sha512-mTh4f59XVxM7vHRP183Cp+0bVOZ0/eyOWQrFBKBt27ZbJvLy2ynxFed/goYpjz8QT+ZJsjmPYViKLxrSdHaQTQ== +"@parcel/transformer-stylus@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-stylus/-/transformer-stylus-2.0.0-nightly.428.tgz#bdfdee966c1ffa8d5205d0df7b5630dc1259a672" + integrity sha512-E6dlNK55W4DosspdCdq8ggn2PEOZpt58OvpYJH4iPEeGCVMrdqFPDTcHDQ1kyosFF70RwpCqx7w9xLdFibD2sA== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-sugarss@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-sugarss/-/transformer-sugarss-2.0.0-beta.1.tgz#61e8b20a83bf0746c5a28c942cb588a7e1980fe5" - integrity sha512-73B7XDAZg0qymhlOEYhI4o8vdUbrm0ZUl/mqzXn13WsgstTtkwJPS8bX/QbyEnFq4xg/ktyezhmhNYLd+GAzjA== +"@parcel/transformer-sugarss@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-sugarss/-/transformer-sugarss-2.0.0-nightly.428.tgz#cc286659ac0892c2b1b9376d82847911b74c5ed2" + integrity sha512-llP6x3caV63EADeZethqEDAwM7VTdglkPLmvKFM57PQbxJ1sLDFzuWb9CY5ajd7gcFs9a2c4wRTz87EdL/Y8pg== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - postcss "^7.0.5" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + postcss "^8.0.5" -"@parcel/transformer-toml@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-toml/-/transformer-toml-2.0.0-beta.1.tgz#6dc28dfb0e47be6bf9ea387e19034b8b3d4290dd" - integrity sha512-EHVS83evOcLJB2wvxQkZCWNjOAYaKg9qfnkbtvEztzx228KcQWg4IPiJgB6+dcBC5fuMBYIw0xZ24y3/3ZkPfg== +"@parcel/transformer-toml@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-toml/-/transformer-toml-2.0.0-nightly.428.tgz#c92cab9c5b75e23264377ead3e4ef05673339299" + integrity sha512-jAjP/76eF4RV0Sl6fOMXt3OjqIBAyQiaSSqPT59bHD65XPakPTvxGSII05jmqUqSFCtn+Qy7SMF2SktIJKZi7g== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/transformer-typescript-types@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.0.0-beta.1.tgz#6275cefb72e248e93619d03692952fc5841476d3" - integrity sha512-QVCe66C14zAsss71wso32OHo74aquVDyYfqO0a/+E62bTA2O8tLaeJ9dwMHgVRgw0tBobh/j6AKaOTeQFLqobA== +"@parcel/transformer-typescript-types@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.0.0-nightly.428.tgz#1121f5702fbf3563f785be393da252aa7e3a75ba" + integrity sha512-WMmmZB8fNwQ/wj9A6Z6OYEkIXCpbF8h73Ie6q2MUPpz6mgRtLhL3IfbCulpoHH3GvvJ/qo7IWynOqrYajHNuqw== dependencies: - "@parcel/plugin" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" - "@parcel/ts-utils" "2.0.0-beta.1" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/ts-utils" "2.0.0-nightly.428+5f72d6bb" nullthrows "^1.1.1" -"@parcel/transformer-yaml@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-yaml/-/transformer-yaml-2.0.0-beta.1.tgz#f725c1d6d6b3f1e9fec77b6ee1fe6768fc3c3a53" - integrity sha512-/pe59NOi6rtbE+YQEnOdxgNf5zMd5SXHnawICGa1Gn9rXDFswiKzD16O2meCy0VGF6dPsQdzL+k0PJlQd9/TDA== +"@parcel/transformer-vue@2.0.0-nightly.2050+5f72d6bb": + version "2.0.0-nightly.2050" + resolved "https://registry.yarnpkg.com/@parcel/transformer-vue/-/transformer-vue-2.0.0-nightly.2050.tgz#7ac51e3921f1dfc2bf62be794e059d384c0a4107" + integrity sha512-qPYRvLHgbmB6heNIVwz8r2yfzaTpgIIKAXyHt66SFmA63l0iAt0HzRdmrRUKRSywinplrXwkEejY7W7PUtIAuQ== dependencies: - "@parcel/plugin" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" + nullthrows "^1.1.1" + semver "^5.4.1" + +"@parcel/transformer-yaml@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/transformer-yaml/-/transformer-yaml-2.0.0-nightly.428.tgz#ab429adc33b8fef46f7b835fd85085f1e7958169" + integrity sha512-OnKyVu09nNxjCVC+Ltz44dX1Wobd6vPVUMOizzumxr/mffKF56qyJH5nZr5K3WFmjQPt3dZTALewE6K+/hmMHw== + dependencies: + "@parcel/plugin" "2.0.0-nightly.428+5f72d6bb" -"@parcel/ts-utils@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.0.0-beta.1.tgz#bd2da0134e994ec5058acf60383122956178df6b" - integrity sha512-lSw4fWL/PLsJruTmScQRVvtw2g75jsd55W08GuMdkr9iGIOXxTRZ4m4rdDBVsiS6Cgq2/l2M3zPnTiPX2do2kw== +"@parcel/ts-utils@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.0.0-nightly.428.tgz#aac135f27bf5108b5c3efd5f187de409c524ba9e" + integrity sha512-tiEGEvo5vnlnhxaRpECaJGMPv2h2WrDZLwmoJmd7dA8bRoi3J33b34XqcM1X0vOKAoiorA3Ty1eVZidhk8Wa5A== dependencies: nullthrows "^1.1.1" -"@parcel/types@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-beta.1.tgz#c5581aa754c342a1e5ef382f2bf20555923b672a" - integrity sha512-2aB5MoEyNUXb34IG1YVRiC5HTHgqWAX8/mM+JGAUit/kTb7eRVtnQtOhQHWyGyLqkEwbLrLo3s272sp5LNK90A== +"@parcel/types@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-nightly.428.tgz#2de9d12da3a389ee7c192e6f1778d1e40982ab4f" + integrity sha512-EccPH9PldxURL7FVd4Qk6wRXMPbw+9FLbK22hg6a7SKlioqKbM0Wi3esWvuArLTsLqKb1tYPskygPRqIzEfqmQ== -"@parcel/utils@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.0-beta.1.tgz#885fc328aaab049ef6ccebdb71236f4f013377ee" - integrity sha512-o+qTRMqe6QjdseYKZBGTOjnxRt4bIOEfH5ey9Ohl5fRGIfuWH7Tsq3fUkf/McvoAQyR6eo1vp7OrPBUQeQ6wxw== +"@parcel/utils@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.0-nightly.428.tgz#d791d9e7534db8ef99e189cbee4162505c33f0ab" + integrity sha512-uCcBVUYqhld0822F+4Yw3dnWuHsNbNsK6iIvbZbKCh7L+MlQkmCvlwYE0H0ew1u3HxjQj+Dlz/lfP7iFDj+Yvw== dependencies: "@iarna/toml" "^2.2.0" - "@parcel/codeframe" "2.0.0-beta.1" - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/logger" "2.0.0-beta.1" - "@parcel/markdown-ansi" "2.0.0-beta.1" - "@parcel/source-map" "2.0.0-alpha.4.13" + "@parcel/codeframe" "2.0.0-nightly.428+5f72d6bb" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/markdown-ansi" "2.0.0-nightly.428+5f72d6bb" + "@parcel/source-map" "2.0.0-alpha.4.16" ansi-html "^0.0.7" chalk "^2.4.2" clone "^2.1.1" fast-glob "3.1.1" + fastest-levenshtein "^1.0.8" is-glob "^4.0.0" is-url "^1.2.2" - js-levenshtein "^1.1.6" json5 "^1.0.1" micromatch "^4.0.2" - node-forge "^0.8.1" + node-forge "^0.10.0" nullthrows "^1.1.1" open "^7.0.3" resolve "^1.12.0" - serialize-to-js "^3.0.1" - terser "^3.7.3" -"@parcel/watcher@^2.0.0-alpha.5": +"@parcel/watcher@2.0.0-alpha.8": version "2.0.0-alpha.8" resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.0-alpha.8.tgz#7aee9504b87eebc73c794c611a6673e58b81e0b1" integrity sha512-9aQu1SFkR6t1UYo3Mj1Vg39/Scaa9i4xGZnZ5Ug/qLyVzHmdjyKDyAbsbUDAd1O2e+MUhr5GI1w1FzBI6J31Jw== @@ -2997,14 +2929,14 @@ node-addon-api "^3.0.0" node-gyp-build "^4.2.1" -"@parcel/workers@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.0-beta.1.tgz#9a57544479862ce886c1b8280bc31c7bc6b467ab" - integrity sha512-8ycDNpBM9WoRVsHB+QXA/Y4I3z18vOQVFY+/vIcLNDIr61zKETwPjOmr5+6wSa6cMWaZBXYUjm2heJ6bPi1QXg== +"@parcel/workers@2.0.0-nightly.428+5f72d6bb": + version "2.0.0-nightly.428" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.0-nightly.428.tgz#227023c9725bb31e495f752f35a317e5428873be" + integrity sha512-dWQNLnJ9TcgbXDQEQcudCAZj2n0IXeBmaYq/yaCSYJ2JfT68W+LyTZS5GleKo41UfudqecnFnLQFrbqHTWreyw== dependencies: - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/logger" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" chrome-trace-event "^1.0.2" nullthrows "^1.1.1" @@ -3082,7 +3014,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03" integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A== @@ -3220,10 +3152,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== -"@types/node@^10.17.35": - version "10.17.35" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.35.tgz#58058f29b870e6ae57b20e4f6e928f02b7129f56" - integrity sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA== +"@types/node@^10.17.40": + version "10.17.40" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.40.tgz#8a50e47daff15fd4a89dc56f5221b3729e506be6" + integrity sha512-3hZT2z2/531A5pc8hYhn1gU5Qb1SIRSgMLQ6zuHA5xtt16lWAxUGprtr8lJuc9zNJMXEIIBWfSnzqBP/4mglpA== "@types/nodeunit@^0.0.31": version "0.0.31" @@ -3240,11 +3172,6 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^1.19.0": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" - integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== - "@types/prettier@^2.0.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.1.tgz#be148756d5480a84cde100324c03a86ae5739fb5" @@ -3284,10 +3211,10 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== -"@types/stack-utils@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" - integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== "@types/string-width@^4.0.1": version "4.0.1" @@ -3449,7 +3376,7 @@ abortcontroller-polyfill@^1.1.9: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz#2c562f530869abbcf88d949a2b60d1d402e87a7c" integrity sha512-O6Xk757Jb4o0LMzMOMdWvxpHWrQzruYBaUruFaIOfAQRnWFxfdXYobw12jrVHGtoXk6WiiyYzc0QWN9aL62HQA== -acorn-globals@^4.3.0, acorn-globals@^4.3.2: +acorn-globals@^4.3.0: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== @@ -3485,7 +3412,7 @@ acorn@^6.0.1, acorn@^6.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.1, acorn@^7.4.0: version "7.4.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== @@ -3622,11 +3549,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" @@ -3894,7 +3816,7 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.596.0, aws-sdk@^2.637.0, aws-sdk@^2.739.0: +aws-sdk@^2.637.0, aws-sdk@^2.739.0: version "2.764.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.764.0.tgz#587bf954645bbcb706c0ce985e7befeb94b15427" integrity sha512-0asgRwAI3WxUyOjlx2fL7pPHEZajFbAVRerE2h0xX649123PwdhIiJ2HM7lWwn/f+mX7IagYjOCn9dIyvCHBVQ== @@ -3926,30 +3848,16 @@ axios@^0.19.0: dependencies: follow-redirects "1.5.10" -babel-jest@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853" - integrity sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ== - dependencies: - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" - -babel-jest@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463" - integrity sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g== +babel-jest@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.0.tgz#eca57ac8af99d6e06047e595b1faf0b5adf8a7bb" + integrity sha512-JI66yILI7stzjHccAoQtRKcUwJrJb4oMIxLTirL3GdAjGpaUBQSjZDFi9LsPkN4gftsS4R2AThAJwOjJxadwbg== dependencies: - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.3.0" + babel-preset-jest "^26.5.0" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -3972,26 +3880,17 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz#129c80ba5c7fc75baf3a45b93e2e372d57ca2677" - integrity sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-jest-hoist@^26.2.0: - version "26.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd" - integrity sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA== +babel-plugin-jest-hoist@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385" + integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-preset-current-node-syntax@^0.1.2, babel-preset-current-node-syntax@^0.1.3: +babel-preset-current-node-syntax@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da" integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ== @@ -4008,20 +3907,12 @@ babel-preset-current-node-syntax@^0.1.2, babel-preset-current-node-syntax@^0.1.3 "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -babel-preset-jest@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz#c1d7f191829487a907764c65307faa0e66590b49" - integrity sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw== - dependencies: - babel-plugin-jest-hoist "^25.5.0" - babel-preset-current-node-syntax "^0.1.2" - -babel-preset-jest@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776" - integrity sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw== +babel-preset-jest@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7" + integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA== dependencies: - babel-plugin-jest-hoist "^26.2.0" + babel-plugin-jest-hoist "^26.5.0" babel-preset-current-node-syntax "^0.1.3" balanced-match@^1.0.0: @@ -4029,6 +3920,13 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-x@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" + integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -4054,7 +3952,7 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -before-after-hook@^2.0.0: +before-after-hook@^2.0.0, before-after-hook@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== @@ -4073,7 +3971,7 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: +bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -4134,13 +4032,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-resolve@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== - dependencies: - resolve "1.1.7" - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4283,6 +4174,11 @@ byte-size@^5.0.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191" integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw== +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -4456,6 +4352,22 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cdk8s-plus@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/cdk8s-plus/-/cdk8s-plus-0.29.0.tgz#c30dc2aca0338f473b2572e92268946adc014ad9" + integrity sha512-++8E+uk7cdLiqZgpb/95goNd8pSHBtjWEqe/Fe7JCrUOCfKEBma60PvTHZaSMGRx52bkd1vevyQYiCmcAz4Aww== + dependencies: + minimatch "^3.0.4" + +cdk8s@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/cdk8s/-/cdk8s-0.32.0.tgz#d977e977c1eee589d74a302675aa0f296cbc19d8" + integrity sha512-FUIULXEKPjXA+9DLzCUpIcOpBrF2B3Dl0jxxjPbwScrTnVqAY2+WetUbcnpogsVCbGl5gWTdO3nI2eBEaST6Bg== + dependencies: + follow-redirects "^1.11.0" + json-stable-stringify "^1.0.1" + yaml "2.0.0-1" + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -4609,10 +4521,10 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" - integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== +cliui@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.3.tgz#ef180f26c8d9bff3927ee52428bfec2090427981" + integrity sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" @@ -4728,6 +4640,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -4874,6 +4791,11 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + conventional-changelog-angular@^5.0.11, conventional-changelog-angular@^5.0.3: version "5.0.11" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz#99a3ca16e4a5305e0c2c2fae3ef74fd7631fc3fb" @@ -5419,7 +5341,7 @@ cssom@0.3.x, cssom@^0.3.4, cssom@~0.3.6: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssom@^0.4.1, cssom@^0.4.4: +cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== @@ -5431,7 +5353,7 @@ cssstyle@^1.1.1: dependencies: cssom "0.3.x" -cssstyle@^2.0.0, cssstyle@^2.2.0: +cssstyle@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== @@ -5723,10 +5645,10 @@ diff-sequences@^25.2.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== -diff-sequences@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2" - integrity sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig== +diff-sequences@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" + integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== diff@^4.0.1, diff@^4.0.2: version "4.0.2" @@ -5854,21 +5776,11 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -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@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== -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" @@ -6101,10 +6013,10 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -escalade@^3.0.2, escalade@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" - integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== +escalade@^3.1.0, escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-html@~1.0.3: version "1.0.3" @@ -6126,7 +6038,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@1.x.x, escodegen@^1.11.0, escodegen@^1.11.1, escodegen@^1.14.1: +escodegen@1.x.x, escodegen@^1.11.0, escodegen@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -6138,11 +6050,6 @@ escodegen@1.x.x, escodegen@^1.11.0, escodegen@^1.11.1, escodegen@^1.14.1: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^14.1.1: - 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.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -6170,14 +6077,6 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - eslint-plugin-import@^2.22.1: version "2.22.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" @@ -6197,33 +6096,11 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-node@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== - dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - 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-rulesdir@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.1.0.tgz#ad144d7e98464fda82963eff3fab331aecb2bf08" integrity sha1-rRRNfphGT9qClj7/P6szGuyyvwg= -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, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -6396,22 +6273,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" - integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^4.0.0, execa@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" @@ -6445,28 +6306,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-25.5.0.tgz#f07f848712a2813bb59167da3fb828ca21f58bba" - integrity sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA== - dependencies: - "@jest/types" "^25.5.0" - ansi-styles "^4.0.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" - -expect@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1" - integrity sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA== +expect@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.0.tgz#f48861317f62bb9f1248eaab7ae9e50a9a5a8339" + integrity sha512-EzhbZ1tbwcaa5Ok39BI11flIMeIUSlg1QsnXOrleaMvltwHsvIQPBtL710l+ma+qDFLUgktCXK4YuQzmHdm7cg== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" ansi-styles "^4.0.0" jest-get-type "^26.3.0" - jest-matcher-utils "^26.4.2" - jest-message-util "^26.3.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" jest-regex-util "^26.0.0" extend-shallow@^2.0.1: @@ -6596,6 +6445,18 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= + dependencies: + punycode "^1.3.2" + +fastest-levenshtein@^1.0.8: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastparse@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -6773,7 +6634,7 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.11.0: version "1.13.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== @@ -7531,7 +7392,7 @@ 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, ignore@^5.1.4: +ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -8216,113 +8077,59 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c" - integrity sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw== - dependencies: - "@jest/types" "^25.5.0" - execa "^3.2.0" - throat "^5.0.0" - -jest-changed-files@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1" - integrity sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g== +jest-changed-files@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.0.tgz#63b04aa261b5733c6ade96b7dd24784d12d8bb2d" + integrity sha512-k8PZzlp3cRWDe0fDc/pYs+c4w36+hiWXe1PpW/pW1UJmu1TNTAcQfZUrVYleij+uEqlY6z4mPv7Iff3kY0o5SQ== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" execa "^4.0.0" throat "^5.0.0" -jest-cli@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.5.4.tgz#b9f1a84d1301a92c5c217684cb79840831db9f0d" - integrity sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw== - dependencies: - "@jest/core" "^25.5.4" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^25.5.4" - jest-util "^25.5.0" - jest-validate "^25.5.0" - prompts "^2.0.1" - realpath-native "^2.0.0" - yargs "^15.3.1" - -jest-cli@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da" - integrity sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw== +jest-cli@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.0.tgz#dc3ae34fd5937310493ed07dc79c5ffba2bf6671" + integrity sha512-lJAMZGpmML+y3Kfln6L5DGRTfKGQ+n1JDM1RQstojSLUhe/EaXWR8vmcx70v4CyJKvFZs7c/0QDkPX5ra/aDew== dependencies: - "@jest/core" "^26.4.2" - "@jest/test-result" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/core" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^26.4.2" - jest-util "^26.3.0" - jest-validate "^26.4.2" + jest-config "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" prompts "^2.0.1" - yargs "^15.3.1" - -jest-config@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.5.4.tgz#38e2057b3f976ef7309b2b2c8dcd2a708a67f02c" - integrity sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.5.4" - "@jest/types" "^25.5.0" - babel-jest "^25.5.1" - chalk "^3.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^25.5.0" - jest-environment-node "^25.5.0" - jest-get-type "^25.2.6" - jest-jasmine2 "^25.5.4" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - micromatch "^4.0.2" - pretty-format "^25.5.0" - realpath-native "^2.0.0" + yargs "^15.4.1" -jest-config@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558" - integrity sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A== +jest-config@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.0.tgz#cb879a37002f881edb66d673fd40b6704595de89" + integrity sha512-RCR1Kf7MGJ5waVCvrj/k3nCAJKquWZlzs8rkskzj0KlG392hNBOaYd5FQ4cCac08j6pwfIDOwNvMcy0/FqguJg== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.4.2" - "@jest/types" "^26.3.0" - babel-jest "^26.3.0" + "@jest/test-sequencer" "^26.6.0" + "@jest/types" "^26.6.0" + babel-jest "^26.6.0" chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^26.3.0" - jest-environment-node "^26.3.0" + jest-environment-jsdom "^26.6.0" + jest-environment-node "^26.6.0" jest-get-type "^26.3.0" - jest-jasmine2 "^26.4.2" + jest-jasmine2 "^26.6.0" jest-regex-util "^26.0.0" - jest-resolve "^26.4.0" - jest-util "^26.3.0" - jest-validate "^26.4.2" + jest-resolve "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" micromatch "^4.0.2" - pretty-format "^26.4.2" + pretty-format "^26.6.0" -jest-diff@^25.2.1, jest-diff@^25.5.0: +jest-diff@^25.2.1: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== @@ -8332,22 +8139,15 @@ jest-diff@^25.2.1, jest-diff@^25.5.0: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa" - integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ== +jest-diff@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.0.tgz#5e5bbbaf93ec5017fae2b3ef12fc895e29988379" + integrity sha512-IH09rKsdWY8YEY7ii2BHlSq59oXyF2pK3GoK+hOK9eD/x6009eNB5Jv1shLMKgxekodPzLlV7eZP1jPFQYds8w== dependencies: chalk "^4.0.0" - diff-sequences "^26.3.0" + diff-sequences "^26.5.0" jest-get-type "^26.3.0" - pretty-format "^26.4.2" - -jest-docblock@^25.3.0: - version "25.3.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef" - integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg== - dependencies: - detect-newline "^3.0.0" + pretty-format "^26.6.0" jest-docblock@^26.0.0: version "26.0.0" @@ -8356,76 +8156,41 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.5.0.tgz#0c3c2797e8225cb7bec7e4d249dcd96b934be516" - integrity sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-get-type "^25.2.6" - jest-util "^25.5.0" - pretty-format "^25.5.0" - -jest-each@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae" - integrity sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA== +jest-each@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.0.tgz#9e9d90a4fc5a79e1d99a008897038325a6c7fbbf" + integrity sha512-7LzSNwNviYnm4FWK46itIE03NqD/8O8/7tVQ5rwTdTNrmPMQoQ1Z7hEFQ1uzRReluOFislpurpnQ0QsclSiDkA== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" chalk "^4.0.0" jest-get-type "^26.3.0" - jest-util "^26.3.0" - pretty-format "^26.4.2" + jest-util "^26.6.0" + pretty-format "^26.6.0" -jest-environment-jsdom@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz#dcbe4da2ea997707997040ecf6e2560aec4e9834" - integrity sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A== +jest-environment-jsdom@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.0.tgz#2ce353fb82d27a9066bfea3ff2c27d9405076c69" + integrity sha512-bXO9IG7a3YlyiHxwfKF+OWoTA+GIw4FrD+Y0pb6CC+nKs5JuSRZmR2ovEX6PWo6KY42ka3JoZOp3KEnXiFPPCg== dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - jsdom "^15.2.1" - -jest-environment-jsdom@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c" - integrity sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA== - dependencies: - "@jest/environment" "^26.3.0" - "@jest/fake-timers" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" - jest-mock "^26.3.0" - jest-util "^26.3.0" - jsdom "^16.2.2" - -jest-environment-node@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.5.0.tgz#0f55270d94804902988e64adca37c6ce0f7d07a1" - integrity sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA== - dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - semver "^6.3.0" - -jest-environment-node@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849" - integrity sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw== - dependencies: - "@jest/environment" "^26.3.0" - "@jest/fake-timers" "^26.3.0" - "@jest/types" "^26.3.0" + jest-mock "^26.6.0" + jest-util "^26.6.0" + jsdom "^16.4.0" + +jest-environment-node@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.0.tgz#97f6e48085e67bda43b97f48e678ce78d760cd14" + integrity sha512-kWU6ZD1h6fs7sIl6ufuK0sXW/3d6WLaj48iow0NxhgU6eY89d9K+0MVmE0cRcVlh53yMyxTK6b+TnhLOnlGp/A== + dependencies: + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" - jest-mock "^26.3.0" - jest-util "^26.3.0" + jest-mock "^26.6.0" + jest-util "^26.6.0" jest-get-type@^25.2.6: version "25.2.6" @@ -8437,92 +8202,49 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943" - integrity sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ== +jest-haste-map@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.0.tgz#4cd392bc51109bd8e0f765b2d5afa746bebb5ce2" + integrity sha512-RpNqAGMR58uG9E9vWITorX2/R7he/tSbHWldX5upt1ymEcmCaXczqXxjqI6xOtRR8Ev6ZEYDfgSA5Fy7WHUL5w== dependencies: - "@jest/types" "^25.5.0" - "@types/graceful-fs" "^4.1.2" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-serializer "^25.5.0" - jest-util "^25.5.0" - jest-worker "^25.5.0" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - which "^2.0.2" - optionalDependencies: - fsevents "^2.1.2" - -jest-haste-map@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726" - integrity sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA== - dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" "@types/graceful-fs" "^4.1.2" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" jest-regex-util "^26.0.0" - jest-serializer "^26.3.0" - jest-util "^26.3.0" - jest-worker "^26.3.0" + jest-serializer "^26.5.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz#66ca8b328fb1a3c5364816f8958f6970a8526968" - integrity sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ== +jest-jasmine2@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.0.tgz#1b59e26aa56651bae3d4637965c8cd4d3851de6d" + integrity sha512-2E3c+0A9y2OIK5caw5qlcm3b4doaf8FSfXKTX3xqKTUJoR4zXh0xvERBNWxZP9xMNXEi/2Z3LVsZpR2hROgixA== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.5.0" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - co "^4.6.0" - expect "^25.5.0" - is-generator-fn "^2.0.0" - jest-each "^25.5.0" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - pretty-format "^25.5.0" - throat "^5.0.0" - -jest-jasmine2@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257" - integrity sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.3.0" - "@jest/source-map" "^26.3.0" - "@jest/test-result" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/environment" "^26.6.0" + "@jest/source-map" "^26.5.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.4.2" + expect "^26.6.0" is-generator-fn "^2.0.0" - jest-each "^26.4.2" - jest-matcher-utils "^26.4.2" - jest-message-util "^26.3.0" - jest-runtime "^26.4.2" - jest-snapshot "^26.4.2" - jest-util "^26.3.0" - pretty-format "^26.4.2" + jest-each "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" throat "^5.0.0" jest-junit@^11.1.0: @@ -8535,331 +8257,168 @@ jest-junit@^11.1.0: uuid "^3.3.3" xml "^1.0.1" -jest-leak-detector@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb" - integrity sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA== - dependencies: - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - -jest-leak-detector@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f" - integrity sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA== +jest-leak-detector@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.0.tgz#a211c4c7627743e8d87b392bf92502cd64275df3" + integrity sha512-3oMv34imWTl1/nwKnmE/DxYo3QqHnZeF3nO6UzldppkhW0Za7OY2DYyWiamqVzwdUrjhoQkY5g+aF6Oc3alYEQ== dependencies: jest-get-type "^26.3.0" - pretty-format "^26.4.2" - -jest-matcher-utils@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867" - integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw== - dependencies: - chalk "^3.0.0" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" + pretty-format "^26.6.0" -jest-matcher-utils@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06" - integrity sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q== +jest-matcher-utils@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.0.tgz#8f57d78353275bfa7a3ccea128c1030b347138e2" + integrity sha512-BUy/dQYb7ELGRazmK4ZVkbfPYCaNnrMtw1YljVhcKzWUxBM0xQ+bffrfnMLdRZp4wUUcT4ahaVnA3VWZtXWP9Q== dependencies: chalk "^4.0.0" - jest-diff "^26.4.2" + jest-diff "^26.6.0" jest-get-type "^26.3.0" - pretty-format "^26.4.2" + pretty-format "^26.6.0" -jest-message-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" - integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== +jest-message-util@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.0.tgz#c3499053022e05765f71b8c2535af63009e2d4be" + integrity sha512-WPAeS38Kza29f04I0iOIQrXeiebRXjmn6cFehzI7KKJOgT0NmqYAcLgjWnIAfKs5FBmEQgje1kXab0DaLKCl2w== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - slash "^3.0.0" - stack-utils "^1.0.1" - -jest-message-util@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a" - integrity sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.3.0" - "@types/stack-utils" "^1.0.1" + "@jest/types" "^26.6.0" + "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.2" slash "^3.0.0" stack-utils "^2.0.2" -jest-mock@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" - integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== +jest-mock@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.0.tgz#5d13a41f3662a98a55c7742ac67c482e232ded13" + integrity sha512-HsNmL8vVIn1rL1GWA21Drpy9Cl+7GImwbWz/0fkWHrUXVzuaG7rP0vwLtE+/n70Mt0U8nPkz8fxioi3SC0wqhw== dependencies: - "@jest/types" "^25.5.0" - -jest-mock@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba" - integrity sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q== - dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" "@types/node" "*" -jest-pnp-resolver@^1.2.1, jest-pnp-resolver@^1.2.2: +jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" - integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw== - jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz#85501f53957c8e3be446e863a74777b5a17397a7" - integrity sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw== - dependencies: - "@jest/types" "^25.5.0" - jest-regex-util "^25.2.6" - jest-snapshot "^25.5.1" - -jest-resolve-dependencies@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5" - integrity sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ== +jest-resolve-dependencies@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.0.tgz#05bfecc977a3a48929fc7d9876f03d93a16b7df0" + integrity sha512-4di+XUT7LwJJ8b8qFEEDQssC5+aeVjLhvRICCaS4alh/EVS9JCT1armfJ3pnSS8t4o6659WbMmKVo82H4LuUVw== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" jest-regex-util "^26.0.0" - jest-snapshot "^26.4.2" - -jest-resolve@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.5.1.tgz#0e6fbcfa7c26d2a5fe8f456088dc332a79266829" - integrity sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ== - dependencies: - "@jest/types" "^25.5.0" - browser-resolve "^1.11.3" - chalk "^3.0.0" - graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.1" - read-pkg-up "^7.0.1" - realpath-native "^2.0.0" - resolve "^1.17.0" - slash "^3.0.0" + jest-snapshot "^26.6.0" -jest-resolve@^26.4.0: - version "26.4.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7" - integrity sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg== +jest-resolve@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.0.tgz#070fe7159af87b03e50f52ea5e17ee95bbee40e1" + integrity sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" chalk "^4.0.0" graceful-fs "^4.2.4" jest-pnp-resolver "^1.2.2" - jest-util "^26.3.0" + jest-util "^26.6.0" read-pkg-up "^7.0.1" resolve "^1.17.0" slash "^3.0.0" -jest-runner@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.5.4.tgz#ffec5df3875da5f5c878ae6d0a17b8e4ecd7c71d" - integrity sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg== +jest-runner@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.0.tgz#465a76efc9ec12cfd83a2af3a6cfb695b13a3efe" + integrity sha512-QpeN6pje8PQvFgT+wYOlzeycKd67qAvSw5FgYBiX2cTW+QTiObTzv/k09qRvT09rcCntFxUhy9VB1mgNGFLYIA== dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-docblock "^25.3.0" - jest-haste-map "^25.5.1" - jest-jasmine2 "^25.5.4" - jest-leak-detector "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - jest-runtime "^25.5.4" - jest-util "^25.5.0" - jest-worker "^25.5.0" - source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runner@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853" - integrity sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g== - dependencies: - "@jest/console" "^26.3.0" - "@jest/environment" "^26.3.0" - "@jest/test-result" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.0" + "@jest/environment" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" chalk "^4.0.0" emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^26.4.2" + jest-config "^26.6.0" jest-docblock "^26.0.0" - jest-haste-map "^26.3.0" - jest-leak-detector "^26.4.2" - jest-message-util "^26.3.0" - jest-resolve "^26.4.0" - jest-runtime "^26.4.2" - jest-util "^26.3.0" - jest-worker "^26.3.0" + jest-haste-map "^26.6.0" + jest-leak-detector "^26.6.0" + jest-message-util "^26.6.0" + jest-resolve "^26.6.0" + jest-runtime "^26.6.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.5.4.tgz#dc981fe2cb2137abcd319e74ccae7f7eeffbfaab" - integrity sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ== - dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/globals" "^25.5.2" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - realpath-native "^2.0.0" - slash "^3.0.0" - strip-bom "^4.0.0" - yargs "^15.3.1" - -jest-runtime@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42" - integrity sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ== - dependencies: - "@jest/console" "^26.3.0" - "@jest/environment" "^26.3.0" - "@jest/fake-timers" "^26.3.0" - "@jest/globals" "^26.4.2" - "@jest/source-map" "^26.3.0" - "@jest/test-result" "^26.3.0" - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" +jest-runtime@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.0.tgz#90f80ea5eb0d97a1089120f582fb84bd36ca5491" + integrity sha512-JEz4YGnybFvtN4NLID6lsZf0bcd8jccwjWcG5TRE3fYVnxoX1egTthPjnC4btIwWJ6QaaHhtOQ/E3AGn8iClAw== + dependencies: + "@jest/console" "^26.6.0" + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/globals" "^26.6.0" + "@jest/source-map" "^26.5.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" "@types/yargs" "^15.0.0" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^26.4.2" - jest-haste-map "^26.3.0" - jest-message-util "^26.3.0" - jest-mock "^26.3.0" + jest-config "^26.6.0" + jest-haste-map "^26.6.0" + jest-message-util "^26.6.0" + jest-mock "^26.6.0" jest-regex-util "^26.0.0" - jest-resolve "^26.4.0" - jest-snapshot "^26.4.2" - jest-util "^26.3.0" - jest-validate "^26.4.2" + jest-resolve "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.3.1" - -jest-serializer@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b" - integrity sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA== - dependencies: - graceful-fs "^4.2.4" + yargs "^15.4.1" -jest-serializer@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef" - integrity sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow== +jest-serializer@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13" + integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA== dependencies: "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f" - integrity sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ== +jest-snapshot@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.0.tgz#457aa9c1761efc781ac9c02b021a0b21047c6a38" + integrity sha512-mcqJZeIZqxomvBcsaiIbiEe2g7K1UxnUpTwjMoHb+DX4uFGnuZoZ6m28YOYRyCfZsdU9mmq73rNBnEH2atTR4Q== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/prettier" "^1.19.0" - chalk "^3.0.0" - expect "^25.5.0" - graceful-fs "^4.2.4" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - make-dir "^3.0.0" - natural-compare "^1.4.0" - pretty-format "^25.5.0" - semver "^6.3.0" - -jest-snapshot@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6" - integrity sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" + "@types/babel__traverse" "^7.0.4" "@types/prettier" "^2.0.0" chalk "^4.0.0" - expect "^26.4.2" + expect "^26.6.0" graceful-fs "^4.2.4" - jest-diff "^26.4.2" + jest-diff "^26.6.0" jest-get-type "^26.3.0" - jest-haste-map "^26.3.0" - jest-matcher-utils "^26.4.2" - jest-message-util "^26.3.0" - jest-resolve "^26.4.0" + jest-haste-map "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-resolve "^26.6.0" natural-compare "^1.4.0" - pretty-format "^26.4.2" + pretty-format "^26.6.0" semver "^7.3.2" -jest-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" - integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - make-dir "^3.0.0" - -jest-util@^26.1.0, jest-util@^26.3.0: +jest-util@^26.1.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e" integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw== @@ -8871,89 +8430,60 @@ jest-util@^26.1.0, jest-util@^26.3.0: is-ci "^2.0.0" micromatch "^4.0.2" -jest-validate@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.5.0.tgz#fb4c93f332c2e4cf70151a628e58a35e459a413a" - integrity sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ== +jest-util@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.0.tgz#a81547f6d38738b505c5a594b37d911335dea60f" + integrity sha512-/cUGqcnKeZMjvTQLfJo65nBOEZ/k0RB/8usv2JpfYya05u0XvBmKkIH5o5c4nCh9DD61B1YQjMGGqh1Ha0aXdg== dependencies: - "@jest/types" "^25.5.0" - camelcase "^5.3.1" - chalk "^3.0.0" - jest-get-type "^25.2.6" - leven "^3.1.0" - pretty-format "^25.5.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" -jest-validate@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c" - integrity sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ== +jest-validate@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.0.tgz#b95e2076cca1a58b183e5bcce2bf43af52eebf10" + integrity sha512-FKHNqvh1Pgs4NWas56gsTPmjcIoGAAzSVUCK1+g8euzuCGbmdEr8LRTtOEFjd29uMZUk0PhzmzKGlHPe6j3UWw== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" camelcase "^6.0.0" chalk "^4.0.0" jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^26.4.2" + pretty-format "^26.6.0" -jest-watcher@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.5.0.tgz#d6110d101df98badebe435003956fd4a465e8456" - integrity sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q== +jest-watcher@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.0.tgz#06001c22831583a16f9ccb388ee33316a7f4200f" + integrity sha512-gw5BvcgPi0PKpMlNWQjUet5C5A4JOYrT7gexdP6+DR/f7mRm7wE0o1GqwPwcTsTwo0/FNf9c/kIDXTRaSAYwlw== dependencies: - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - ansi-escapes "^4.2.1" - chalk "^3.0.0" - jest-util "^25.5.0" - string-length "^3.1.0" - -jest-watcher@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08" - integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ== - dependencies: - "@jest/test-result" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.3.0" + jest-util "^26.6.0" string-length "^4.0.1" -jest-worker@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" - integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f" - integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw== +jest-worker@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" + integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" -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== - dependencies: - "@jest/core" "^25.5.4" - import-local "^3.0.2" - jest-cli "^25.5.4" - -jest@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312" - integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw== +jest@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25" + integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== dependencies: - "@jest/core" "^26.4.2" + "@jest/core" "^26.6.0" import-local "^3.0.2" - jest-cli "^26.4.2" + jest-cli "^26.6.0" jmespath@0.15.0: version "0.15.0" @@ -8965,11 +8495,6 @@ js-base64@^2.1.9: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== -js-levenshtein@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" - integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -9020,39 +8545,7 @@ jsdom@^14.1.0: ws "^6.1.2" xml-name-validator "^3.0.0" -jsdom@^15.2.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" - integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== - dependencies: - abab "^2.0.0" - acorn "^7.1.0" - acorn-globals "^4.3.2" - array-equal "^1.0.0" - cssom "^0.4.1" - cssstyle "^2.0.0" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.1" - html-encoding-sniffer "^1.0.2" - nwsapi "^2.2.0" - parse5 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.7" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^3.0.1" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^7.0.0" - xml-name-validator "^3.0.0" - -jsdom@^16.2.2: +jsdom@^16.4.0: version "16.4.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== @@ -9256,6 +8749,11 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= +jsonschema@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.10.tgz#38dc18b63839e8f07580df015e37d959f20d1eda" + integrity sha512-CoRSun5gmvgSYMHx5msttse19SnQpaHoPzIqULwE7B9KtR4Od1g70sBqeUriq5r8b9R3ptDc0o7WKpUDjUgLgg== + jsonschema@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.7.tgz#4e6d6dc4d83dc80707055ba22c00ec6152c0e6e9" @@ -9315,24 +8813,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" @@ -9404,6 +8884,14 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +line-column@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" + integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= + dependencies: + isarray "^1.0.0" + isobject "^2.0.0" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -9644,14 +9132,7 @@ log4js@^6.3.0: rfdc "^1.1.4" streamroller "^2.2.4" -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== - dependencies: - "@sinonjs/commons" "^1.7.0" - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -9738,6 +9219,14 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" +make-runnable@^1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/make-runnable/-/make-runnable-1.3.8.tgz#ce547757d776c1f369028dbd937731db9514fe6a" + integrity sha512-8lXEwzB1eLl1pGFx5JZxFbWac6v6bHfRrCn/xDO4Qk9H6DN0QqIsJOquGxz7NkZxedhh7uki1txsbgTci1W/Vw== + dependencies: + bluebird "^3.5.0" + yargs "^16.0.3" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -9915,6 +9404,18 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -9952,7 +9453,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@>=3.0, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@>=3.0, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -10117,6 +9618,11 @@ mz@^2.5.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nanoid@^3.1.12: + version "3.1.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" + integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -10185,11 +9691,6 @@ nock@^13.0.4: lodash.set "^4.3.2" propagate "^2.0.0" -node-addon-api@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" - integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== - node-addon-api@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" @@ -10209,10 +9710,10 @@ node-fetch@^2.5.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-forge@^0.8.1: - version "0.8.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" - integrity sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-gyp-build@^4.2.1, node-gyp-build@^4.2.2: version "4.2.3" @@ -10246,17 +9747,6 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" - integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== - dependencies: - growly "^1.3.0" - is-wsl "^2.1.1" - semver "^6.3.0" - shellwords "^0.1.1" - which "^1.3.1" - node-notifier@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" @@ -10503,7 +9993,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -10719,11 +10209,6 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -10878,22 +10363,22 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -parcel@2.0.0-beta.1: - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.0.0-beta.1.tgz#7b23be0217da198f0e8f0d14f0107d8d938074dd" - integrity sha512-Z2tzZoDws7q+99ihjpd3WX09BTao2miTk+S9URJXRJkVxxgoZSQJ+uwtxcosoDWwS6zv4RAXxtEMmiyMeaCuMQ== - dependencies: - "@parcel/config-default" "2.0.0-beta.1" - "@parcel/core" "2.0.0-beta.1" - "@parcel/diagnostic" "2.0.0-beta.1" - "@parcel/fs" "2.0.0-beta.1" - "@parcel/logger" "2.0.0-beta.1" - "@parcel/package-manager" "2.0.0-beta.1" - "@parcel/utils" "2.0.0-beta.1" +parcel@2.0.0-nightly.426: + version "2.0.0-nightly.426" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.0.0-nightly.426.tgz#9d5ae450adc1bc4174a5778c96042be342ab5657" + integrity sha512-hgEqIXAjG/ZQnImXxVqJeg1ZhlCfosrnUXSswRCG8XdNUs8oncumC3PbfvYWHNAGnX/h8y/7nU98u3hi9l1tFQ== + dependencies: + "@parcel/config-default" "2.0.0-nightly.428+5f72d6bb" + "@parcel/core" "2.0.0-nightly.426+5f72d6bb" + "@parcel/diagnostic" "2.0.0-nightly.428+5f72d6bb" + "@parcel/events" "2.0.0-nightly.428+5f72d6bb" + "@parcel/fs" "2.0.0-nightly.428+5f72d6bb" + "@parcel/logger" "2.0.0-nightly.428+5f72d6bb" + "@parcel/package-manager" "2.0.0-nightly.428+5f72d6bb" + "@parcel/utils" "2.0.0-nightly.428+5f72d6bb" chalk "^2.1.0" commander "^2.19.0" get-port "^4.2.0" - react "^16.7.0" v8-compile-cache "^2.0.0" parent-module@^1.0.0: @@ -11014,6 +10499,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -11029,6 +10519,11 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -11463,12 +10958,12 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1: +postcss-value-parser@^3.0.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.2: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -11500,7 +10995,7 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.17, postcss@^7.0.27, postcss@^7.0.5: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.17, postcss@^7.0.27: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== @@ -11509,6 +11004,16 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.17, postcss@^7.0.27, postcss@^7.0.5 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^8.0.5: + version "8.1.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.2.tgz#9731fcaa4f7b0bef47121821bdae9eeb609a324c" + integrity sha512-mToqEVFq8jF9TFhlIK4HhE34zknFJuNTgqtsr60vUvrWn+9TIYugCwiV1JZRxCuOrej2jjstun1bn4Bc7/1HkA== + dependencies: + colorette "^1.2.1" + line-column "^1.0.2" + nanoid "^3.1.12" + source-map "^0.6.1" + posthtml-parser@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" @@ -11564,12 +11069,12 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237" - integrity sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA== +pretty-format@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.0.tgz#1e1030e3c70e3ac1c568a5fd15627671ea159391" + integrity sha512-Uumr9URVB7bm6SbaByXtx+zGlS+0loDkFMHP0kHahMjmfCtmFY03iqd++5v3Ld6iB5TocVXlBN/T+DXMn9d4BA== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.0" ansi-regex "^5.0.0" ansi-styles "^4.0.0" react-is "^16.12.0" @@ -11632,15 +11137,6 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.6.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.8.1" - propagate@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" @@ -11743,7 +11239,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.4.1: +punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -11783,7 +11279,7 @@ querystring-es3@^0.2.1: resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0: +querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= @@ -11813,6 +11309,11 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + raw-body@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -11823,7 +11324,7 @@ raw-body@^2.2.0: iconv-lite "0.4.24" unpipe "1.0.0" -react-is@^16.12.0, react-is@^16.8.1: +react-is@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -11833,15 +11334,6 @@ react-refresh@^0.6.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.6.0.tgz#81971b8f3c8c05aaa6ce87491ae41b396133f896" integrity sha512-Wv48N+GFt6Azvtl/LMvzNW9hvEyJdRQ48oVKIBAN7hjtvXXfxfVJXbPl/11SM1C/NIquIFXzzWCo6ZNH0I8I4g== -react@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - read-cmd-shim@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16" @@ -12002,11 +11494,6 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -realpath-native@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" - integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -12143,7 +11630,7 @@ request-promise-core@1.1.4: dependencies: lodash "^4.17.19" -request-promise-native@^1.0.5, request-promise-native@^1.0.7, request-promise-native@^1.0.8: +request-promise-native@^1.0.5, request-promise-native@^1.0.8: version "1.0.9" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== @@ -12227,12 +11714,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7: - version "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.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -12431,15 +11913,24 @@ semver@7.x, semver@^7.1.1, semver@^7.2.1, 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.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, 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== -serialize-to-js@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" - integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== +serve-handler@^6.0.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8" + integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.0.4" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -12682,7 +12173,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.10, source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.12: +source-map-support@^0.5.10, source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -12705,7 +12196,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -12825,7 +12316,7 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-utils@^1.0.1, stack-utils@^1.0.2: +stack-utils@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== @@ -12913,14 +12404,6 @@ string-argv@0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-length@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== - dependencies: - astral-regex "^1.0.0" - strip-ansi "^5.2.0" - string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -13346,16 +12829,7 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser@^3.7.3: - version "3.17.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" - integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== - dependencies: - commander "^2.19.0" - source-map "~0.6.1" - source-map-support "~0.5.10" - -terser@^4.3.0, terser@^4.8.0: +terser@^4.8.0: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== @@ -13364,6 +12838,15 @@ terser@^4.3.0, terser@^4.8.0: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.2.0: + version "5.3.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.7.tgz#798a4ae2e7ff67050c3e99fcc4e00725827d97e2" + integrity sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -13982,19 +13465,10 @@ v8-compile-cache@^2.0.0, v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8-to-istanbul@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" - integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - -v8-to-istanbul@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5" - integrity sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q== +v8-to-istanbul@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e" + integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -14015,11 +13489,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= - vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" @@ -14294,7 +13763,7 @@ ws@^6.1.2, ws@^6.2.0: dependencies: async-limiter "~1.0.0" -ws@^7.0.0, ws@^7.2.3: +ws@^7.2.3: version "7.3.1" resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== @@ -14352,10 +13821,10 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -y18n@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" - integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== +y18n@^5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.4.tgz#0ab2db89dd5873b5ec4682d8e703e833373ea897" + integrity sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ== yallist@^2.1.2: version "2.1.2" @@ -14377,15 +13846,20 @@ yaml@*, yaml@1.10.0, yaml@^1.10.0, yaml@^1.5.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@2.0.0-1: + version "2.0.0-1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + yapool@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yapool/-/yapool-1.0.0.tgz#f693f29a315b50d9a9da2646a7a6645c96985b6a" integrity sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o= -yargs-parser@20.x, yargs-parser@^20.0.0: - version "20.2.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605" - integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A== +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" + integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== yargs-parser@^13.0.0, yargs-parser@^13.1.2: version "13.1.2" @@ -14461,18 +13935,18 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" - integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== +yargs@^16.0.3, yargs@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.1.0.tgz#fc333fe4791660eace5a894b39d42f851cd48f2a" + integrity sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g== dependencies: - cliui "^7.0.0" - escalade "^3.0.2" + cliui "^7.0.2" + escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.0" - y18n "^5.0.1" - yargs-parser "^20.0.0" + y18n "^5.0.2" + yargs-parser "^20.2.2" yn@3.1.1: version "3.1.1"