diff --git a/.gitallowed b/.gitallowed index 43827f7ad99b7..abe8ccb913ca4 100644 --- a/.gitallowed +++ b/.gitallowed @@ -23,6 +23,8 @@ account: '856666278305' account: '840364872350' account: '422531588944' account: '924023996002' +account: '919366029133' #cn-north-1 +account: '919830735681' #cn-northwest-1 # The account IDs of password rotation applications of Serverless Application Repository # https://docs.aws.amazon.com/secretsmanager/latest/userguide/enable-rotation-rds.html diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000000..9d54ed7236ef7 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,47 @@ +# AWS CDK GitHub Actions + +These workflows and actions are configured in the AWS CDK GitHub repository. + +## Pull Request Triggered + +### Auto Approve +[auto-approve.yml](auto-approve.yml): Approves merging PRs with the +`pr/auto-approve` label. +Owner: Core CDK team + +### PR Linter +[pr-linter.yml](pr-linter.yml): Runs `tools/@aws-cdk-prlint` on each PR to +check for correctness. +Owner: Core CDK team + +### v2-main PR automation +[v2-pull-request.yml](v2-pull-request.yml): Runs `pkglint` on merge forward PRs +and commits the results. +Owner: Core CDK team + +### Label Assigner +[issue-label-assign.yml](issue-label-assign.yml): Github action for automatically adding labels and/or setting assignees when an Issue or PR is opened or edited based on user-defined Area +Owner: CDK support team + +## Issue Triggered + +### Closed Issue Message +[closed-issue-message.yml](closed-issue-message.yml): Adds a reminder message +to issues that are closed. +Owner: CDK support team + +### Label Assigner +[issue-label-assign.yml](issue-label-assign.yml): Github action for automatically adding labels and/or setting assignees when an Issue or PR is opened or edited based on user-defined Area +Owner: CDK support team + +## Scheduled Actions + +### Issue Lifecycle Handling +[close-stale-issues.yml](close-stale-issues.yml): Handles labeling issues and +PRs with `closing-soon`, `response-requested`, etc. +Owner: CDK support team + +### Yarn Upgrader +[yarn-upgrade.yml](yarn-upgrade.yml): Upgrades yarn dependencies and creates a +patch file for downloading. +Owner: Core CDK team \ No newline at end of file diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index dc3e7febfcf07..ed29d53382d1f 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -7,11 +7,7 @@ on: jobs: auto-approve: - if: > - contains(github.event.pull_request.labels.*.name, 'pr/auto-approve') && - (github.event.pull_request.user.login == 'aws-cdk-automation' - || github.event.pull_request.user.login == 'dependabot[bot]' - || github.event.pull_request.user.login == 'dependabot-preview[bot]') + if: contains(github.event.pull_request.labels.*.name, 'pr/auto-approve') runs-on: ubuntu-latest permissions: pull-requests: write diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml index 487a095e0c372..ee427df90ef79 100644 --- a/.github/workflows/close-stale-issues.yml +++ b/.github/workflows/close-stale-issues.yml @@ -4,7 +4,7 @@ name: "Close Stale Issues" on: workflow_dispatch: schedule: - - cron: "0 6 * * *" + - cron: "0 */4 * * *" jobs: cleanup: diff --git a/.github/workflows/cr-checklist.yml b/.github/workflows/cr-checklist.yml new file mode 100644 index 0000000000000..cdca37051a4d5 --- /dev/null +++ b/.github/workflows/cr-checklist.yml @@ -0,0 +1,19 @@ +name: "Custom resources checklist" +on: + pull_request: + types: [ opened ] + +jobs: + checklist_job: + runs-on: ubuntu-latest + name: Creates a checklist for PRs that contain changes to custom resources + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Dynamic checklist action + uses: vishalsinha21/dynamic-checklist@v1 + with: + mappingFile: './.github/workflows/cr-mapping.json' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/cr-mapping.json b/.github/workflows/cr-mapping.json new file mode 100644 index 0000000000000..85d00b6baf6f1 --- /dev/null +++ b/.github/workflows/cr-mapping.json @@ -0,0 +1,8 @@ +{ + "mappings": [ + { + "keywords": ["CustomResourceProvider", "CustomResource", "CloudFormationCustomResourceDeleteEvent", "CloudFormationCustomResourceUpdateEvent", "CloudFormationCustomResourceCreateEvent"], + "comment": "I have read the guidelines on the [important cases to handle](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/custom-resources#important-cases-to-handle) when implementing the custom resource." + } + ] +} diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index 1c9c5997781bd..c2f5743ffb04b 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -11,9 +11,10 @@ jobs: test: permissions: issues: write + pull-requests: write runs-on: ubuntu-latest steps: - - uses: peterwoodworth/issue-action@main + - uses: aws-github-ops/aws-issue-triage-manager@main with: github-token: "${{ secrets.GITHUB_TOKEN }}" excluded-expressions: "[CDK CLI Version|TypeScript|Java|Python]" @@ -23,6 +24,7 @@ jobs: {"area":"@aws-cdk/alexa-ask","keywords":["alexa-ask","alexa", "cfnskill"],"labels":["@aws-cdk/alexa-ask"],"assignees":["madeline-k"]}, {"area":"@aws-cdk/app-delivery","keywords":["app-delivery","PipelineDeployStackAction"],"labels":["@aws-cdk/app-delivery"],"assignees":["skinny85"]}, {"area":"@aws-cdk/assert","keywords":["assert"],"labels":["@aws-cdk/assert"],"assignees":["nija-at"]}, + {"area":"@aws-cdk/assertions","keywords":["assertions"],"labels":["@aws-cdk/assertions"],"assignees":["nija-at"]}, {"area":"@aws-cdk/assets","keywords":["assets","staging"],"labels":["@aws-cdk/assets"],"assignees":["eladb"]}, {"area":"@aws-cdk/aws-accessanalyzer","keywords":["aws-accessanalyzer","accessanalyzer","cfnanalyzer"],"labels":["@aws-cdk/aws-accessanalyzer"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-acmpca","keywords":["aws-acmpca","acmpca","certificateauthority"],"labels":["@aws-cdk/aws-acmpca"],"assignees":["skinny85"]}, @@ -55,10 +57,10 @@ jobs: {"area":"@aws-cdk/aws-certificatemanager","keywords":["aws-certificatemanager","certificate-manager","dnsvalidatedcertificate","acm"],"labels":["@aws-cdk/aws-certificatemanager"],"assignees":["njlynch"]}, {"area":"@aws-cdk/aws-chatbot","keywords":["aws-chatbot","chatbot","slackchannelconfiguration"],"labels":["@aws-cdk/aws-chatbot"],"assignees":["kaizen3031593"]}, {"area":"@aws-cdk/aws-cloud9","keywords":["aws-cloud9","cloud 9","ec2environment"],"labels":["@aws-cdk/aws-cloud9"],"assignees":["skinny85"]}, - {"area":"@aws-cdk/aws-cloudformation","keywords":["aws-cloudformation","nestedstack","customresource"],"labels":["@aws-cdk/aws-cloudformation"],"assignees":["rix0rrr"]}, + {"area":"@aws-cdk/aws-cloudformation","keywords":["aws-cloudformation","nestedstack"],"labels":["@aws-cdk/aws-cloudformation"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/aws-cloudfront","keywords":["aws-cloudfront","cloud front","cachepolicy","cloudfrontwebdistribution"],"labels":["@aws-cdk/aws-cloudfront"],"assignees":["njlynch"]}, {"area":"@aws-cdk/aws-cloudfront-origins","keywords":["aws-cloudfront-origins","cloudfront origins"],"labels":["@aws-cdk/aws-cloudfront-origins"],"assignees":["njlynch"]}, - {"area":"@aws-cdk/aws-cloudtrail","keywords":["aws-cloudtrail","cloud trail","trail"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["rix0rrr"]}, + {"area":"@aws-cdk/aws-cloudtrail","keywords":["aws-cloudtrail","cloud trail","trail"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["comcalvi"]}, {"area":"@aws-cdk/aws-cloudwatch","keywords":["aws-cloudwatch","cloud watch","compositealarm","dashboard"],"labels":["@aws-cdk/aws-cloudwatch"],"assignees":["madeline-k"]}, {"area":"@aws-cdk/aws-cloudwatch-actions","keywords":["aws-cloudwatch-actions","cloudwatch actions","applicationscalingaction","autoscalingaction","ec2action","snsaction"],"labels":["@aws-cdk/aws-cloudwatch-actions"],"assignees":["madeline-k"]}, {"area":"@aws-cdk/aws-codeartifact","keywords":["aws-codeartifact","code-artifact"],"labels":["@aws-cdk/aws-codeartifact"],"assignees":["njlynch"]}, @@ -67,8 +69,8 @@ jobs: {"area":"@aws-cdk/aws-codedeploy","keywords":["aws-codedeploy","code-deploy","customlambdadeploymentconfig","ecsapplication","lambdaapplication","lambdadeploymentgroup","serverapplication"],"labels":["@aws-cdk/aws-codedeploy"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-codeguruprofiler","keywords":["aws-codeguruprofiler","codeguru-profiler","profilinggroup"],"labels":["@aws-cdk/aws-codeguruprofiler"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-codegurureviewer","keywords":["aws-codegurureviewer","codeguru-reviewer"],"labels":["@aws-cdk/aws-codegurureviewer"],"assignees":["skinny85"]}, - {"area":"@aws-cdk/aws-codepipeline","keywords":["aws-codepipeline","code-pipeline","pipeline"],"labels":["@aws-cdk/aws-codepipeline"],"assignees":["skinny85"]}, - {"area":"@aws-cdk/aws-codepipeline-actions","keywords":["aws-codepipeline-actions","codepipeline actions","jenkinsprovider"],"labels":["@aws-cdk/aws-codepipeline-actions"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/aws-codepipeline","keywords":["aws-codepipeline","code-pipeline"],"labels":["@aws-cdk/aws-codepipeline"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/aws-codepipeline-actions","keywords":["aws-codepipeline-actions","codepipeline-actions","jenkinsprovider"],"labels":["@aws-cdk/aws-codepipeline-actions"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-codestar","keywords":["aws-codestar","codestar","githubrepository"],"labels":["@aws-cdk/aws-codestar"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-codestarconnections","keywords":["aws-codestarconnections","codestar-connections"],"labels":["@aws-cdk/aws-codestarconnections"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-codestarnotifications","keywords":["aws-codestarnotifications","codestar-notifications"],"labels":["@aws-cdk/aws-codestarnotifications"],"assignees":["skinny85"]}, @@ -118,12 +120,13 @@ jobs: {"area":"@aws-cdk/aws-glue","keywords":["aws-glue","glue"],"labels":["@aws-cdk/aws-glue"],"assignees":["kaizen3031593"]}, {"area":"@aws-cdk/aws-greengrass","keywords":["aws-greengrass","green-grass"],"labels":["@aws-cdk/aws-greengrass"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-greengrassv2","keywords":["(aws-greengrassv2)","(greengrassv2)"],"labels":["@aws-cdk/aws-greengrassv2"],"assignees":["skinny85"]}, - {"area":"@aws-cdk/aws-groundstation","keywords":["(aws-groundstation)","(groundstation)"],"labels":["@aws-cdk/aws-groundstation"],"assignees":["rix0rrr"]}, + {"area":"@aws-cdk/aws-groundstation","keywords":["(aws-groundstation)","(groundstation)"],"labels":["@aws-cdk/aws-groundstation"],"assignees":["comcalvi"]}, {"area":"@aws-cdk/aws-guardduty","keywords":["aws-guardduty","guard-duty"],"labels":["@aws-cdk/aws-guardduty"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/aws-iam","keywords":["aws-iam","iam","managedpolicy","policy","role"],"labels":["@aws-cdk/aws-iam"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/aws-imagebuilder","keywords":["aws-imagebuilder","imagebuilder"],"labels":["@aws-cdk/aws-imagebuilder"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-inspector","keywords":["aws-inspector","inspector"],"labels":["@aws-cdk/aws-inspector"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-iot","keywords":["internet-of-things","aws-iot","iot"],"labels":["@aws-cdk/aws-iot"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/aws-iot-actions","keywords":["aws-iot-actions","iot-actions"],"labels":["@aws-cdk/aws-iot-actions"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-iot1click","keywords":["aws-iot1click","iot1click"],"labels":["@aws-cdk/aws-iot1click"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-iotanalytics","keywords":["aws-iotanalytics","iotanalytics"],"labels":["@aws-cdk/aws-iotanalytics"],"assignees":["skinny85"]}, {"area":"@aws-cdk/aws-iotevents","keywords":["aws-iotevents","iotevents"],"labels":["@aws-cdk/aws-iotevents"],"assignees":["skinny85"]}, @@ -146,7 +149,7 @@ jobs: {"area":"@aws-cdk/aws-lambda-nodejs","keywords":["nodejsfunction","aws-lambda-nodejs","lambda-nodejs"],"labels":["@aws-cdk/aws-lambda-nodejs"],"assignees":["nija-at"]}, {"area":"@aws-cdk/aws-lambda-python","keywords":["aws-lambda-python","lambda-python","pythonfunction"],"labels":["@aws-cdk/aws-lambda-python"],"assignees":["nija-at"]}, {"area":"@aws-cdk/aws-licensemanager","keywords":["(aws-licensemanager)","(licensemanager)"],"labels":["@aws-cdk/aws-licensemanager"],"assignees":["njlynch"]}, - {"area":"@aws-cdk/aws-logs","keywords":["loggroup","aws-logs","logs","logretention"],"labels":["@aws-cdk/aws-logs"],"assignees":["rix0rrr"]}, + {"area":"@aws-cdk/aws-logs","keywords":["loggroup","aws-logs","logs","logretention"],"labels":["@aws-cdk/aws-logs"],"assignees":["comcalvi"]}, {"area":"@aws-cdk/aws-logs-destinations","keywords":["aws-logs-destinations","lambdadestination","kinesisdestination","logs-destinations"],"labels":["@aws-cdk/aws-logs-destinations"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/aws-lookoutmetrics","keywords":["(aws-lookoutmetrics)","(lookoutmetrics)"],"labels":["@aws-cdk/aws-lookoutmetrics"],"assignees":["comcalvi"]}, {"area":"@aws-cdk/aws-lookoutvision","keywords":["(aws-lookoutvision)","(lookoutvision)"],"labels":["@aws-cdk/aws-lookoutvision"],"assignees":["comcalvi"]}, @@ -215,7 +218,7 @@ jobs: {"area":"@aws-cdk/cfnspec","keywords":["cfn-spec"],"labels":["@aws-cdk/cfnspec"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/cloud-assembly-schema","keywords":["cloud-assembly-schema","manifest"],"labels":["@aws-cdk/cloud-assembly-schema"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/cloudformation-diff","keywords":["cloudformation-diff","cfn-diff"],"labels":["@aws-cdk/cloudformation-diff"],"assignees":["skinny85"]}, - {"area":"@aws-cdk/cloudformation-include","keywords":["cloudformation-include","cfn-include"],"labels":["@aws-cdk/cloudformation-include"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/cloudformation-include","keywords":["cloudformation-include","cfn-include"],"labels":["@aws-cdk/cloudformation-include"],"assignees":["comcalvi"]}, {"area":"@aws-cdk/core","keywords":["cross-account","nested stacks","core"],"labels":["@aws-cdk/core"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/custom-resources","keywords":["custom-resource","provider"],"labels":["@aws-cdk/custom-resources"],"assignees":["rix0rrr"]}, {"area":"@aws-cdk/cx-api","keywords":["cx-api","cloudartifact","cloudassembly"],"labels":["@aws-cdk/cx-api"],"assignees":["rix0rrr"]}, @@ -225,5 +228,8 @@ jobs: {"area":"@aws-cdk/region-info","keywords":["region-info","fact"],"labels":["@aws-cdk/region-info"],"assignees":["skinny85"]}, {"area":"aws-cdk-lib","keywords":["aws-cdk-lib","cdk-v2","v2","ubergen"],"labels":["aws-cdk-lib"],"assignees":["nija-at"]}, {"area":"monocdk","keywords":["monocdk","monocdk-experiment"],"labels":["monocdk"],"assignees":["nija-at"]}, - {"area":"@aws-cdk/yaml-cfn","keywords":["(aws-yaml-cfn)","(yaml-cfn)"],"labels":["@aws-cdk/aws-yaml-cfn"],"assignees":["skinny85"]} + {"area":"@aws-cdk/yaml-cfn","keywords":["(aws-yaml-cfn)","(yaml-cfn)"],"labels":["@aws-cdk/aws-yaml-cfn"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/aws-apprunner","keywords":["apprunner","aws-apprunner"],"labels":["@aws-cdk/aws-apprunner"],"assignees":["corymhall"]}, + {"area":"@aws-cdk/aws-lightsail","keywords":["lightsail","aws-lightsail"],"labels":["@aws-cdk/aws-lightsail"],"assignees":["corymhall"]}, + {"area":"@aws-cdk/aws-aps","keywords":["aps","aws-aps","prometheus"],"labels":["@aws-cdk/aws-aps"],"assignees":["corymhall"]} ] diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 0000000000000..9b597bde76917 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,18 @@ +# Apply various labels on PRs + +name: pr-labeler +on: + pull_request: + types: [ opened ] + +jobs: + auto-approve: + if: github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'dependabot-preview[bot]' + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + steps: + - run: gh pr edit ${{ github.event.pull_request.number }} --add-label "pr/auto-approve" -R ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-linter.yml b/.github/workflows/pr-linter.yml index d88d64d89537d..8231b94fa2319 100644 --- a/.github/workflows/pr-linter.yml +++ b/.github/workflows/pr-linter.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v2 - name: Install & Build prlint - run: cd tools/@aws-cdk/prlint && yarn install --frozen-lockfile && yarn build+test + run: yarn install --frozen-lockfile && cd tools/@aws-cdk/prlint && yarn build+test - name: Validate uses: ./tools/@aws-cdk/prlint diff --git a/.gitignore b/.gitignore index a2281906f6867..70bbad3393e00 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ yarn-error.log .nzm-* /.versionrc.json +RELEASE_NOTES.md diff --git a/.mergify.yml b/.mergify.yml index 97f3ce42a91be..49320bf2385ee 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,4 +1,8 @@ # See https://doc.mergify.io +queue_rules: + - name: default + conditions: + - status-success~=AWS CodeBuild us-east-1 pull_request_rules: - name: label core @@ -6,16 +10,15 @@ pull_request_rules: label: add: [ contribution/core ] conditions: - - author~=^(eladb|RomainMuller|garnaat|nija-at|skinny85|rix0rrr|NGL321|Jerry-AWS|MrArnoldPalmer|NetaNir|iliapolo|njlynch|ericzbeard|ccfife|fulghum|pkandasamy91|SoManyHs|uttarasridhar|otaviomacedo|BenChaimberg|madeline-k|BryanPan342|kaizen3031593|comcalvi)$ + - author~=^(eladb|RomainMuller|garnaat|nija-at|skinny85|rix0rrr|NGL321|Jerry-AWS|MrArnoldPalmer|NetaNir|iliapolo|njlynch|ericzbeard|ccfife|fulghum|pkandasamy91|SoManyHs|uttarasridhar|otaviomacedo|BenChaimberg|madeline-k|BryanPan342|kaizen3031593|comcalvi|Chriscbr|corymhall|peterwoodworth|ryparker)$ - -label~="contribution/core" - name: automatic merge actions: comment: message: Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)). - merge: - strict: smart + queue: + name: default method: squash - strict_method: merge commit_message: title+body conditions: - base!=release @@ -34,10 +37,9 @@ pull_request_rules: actions: comment: message: Thank you for contributing! Your pull request will be automatically updated and merged (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)). - merge: - strict: smart + queue: + name: default method: squash - strict_method: merge commit_message: title+body conditions: - base!=release @@ -57,11 +59,9 @@ pull_request_rules: actions: comment: message: Thank you for contributing! Your pull request will be automatically updated and merged without squashing (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)). - merge: - strict: smart - # Merge instead of squash + queue: + name: default method: merge - strict_method: merge commit_message: title+body conditions: - -title~=(WIP|wip) @@ -103,12 +103,10 @@ pull_request_rules: actions: comment: message: Thanks Dependabot! - merge: - # 'strict: false' disables Mergify keeping the branch up-to-date from master. - # It's not necessary: Dependabot will do that itself. - # It's not dangerous: GitHub branch protection settings prevent merging stale branches. - strict: false + queue: + name: default method: squash + commit_message: title+body conditions: - -title~=(WIP|wip) - -label~=(blocked|do-not-merge) diff --git a/.yarnrc b/.yarnrc index 591e9c3d57b96..019f345f39305 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1 +1,10 @@ --install.check-files true # install will verify file tree of packages for consistency + +# Use the npm registry instead of yarns mirror. +# npm treats registry.npmjs.org as a special value that means 'the current +# configured package' in package-lock and npm-shrinkwrap. if we use +# registry.yarnpkg.com in our shrinkwrap then users with a custom registry will +# be forced to registry.yarnpkg.com. +# https://github.com/npm/cli/issues/3783 +registry "https://registry.npmjs.org" +ignore-engines true # the 'engines' key for 'aws-cdk-lib' has specifies node14 as min while v1 will remain at node10 diff --git a/CHANGELOG.md b/CHANGELOG.md index 098c920bd1b54..92798df54dd59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,208 @@ 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.132.0](https://github.com/aws/aws-cdk/compare/v1.131.0...v1.132.0) (2021-11-09) + + +### Features + +* **apigatewayv2:** http api - mTLS support ([#17284](https://github.com/aws/aws-cdk/issues/17284)) ([54be156](https://github.com/aws/aws-cdk/commit/54be1567546ffd52e83fbe52531f901c0b6c29c9)), closes [#12559](https://github.com/aws/aws-cdk/issues/12559) +* **stepfunctions-tasks:** add `AutoTerminationPolicy` to `EmrCreateCluster` ([#16976](https://github.com/aws/aws-cdk/issues/16976)) ([27ad7d8](https://github.com/aws/aws-cdk/commit/27ad7d86824b6378d470cda7304e7ae89ebbebf4)) +* the assertions module is now stable! ([#17395](https://github.com/aws/aws-cdk/issues/17395)) ([ede5e22](https://github.com/aws/aws-cdk/commit/ede5e22da2e59218534c17c33a21cab98a3001a9)) +* **cfnspec:** cloudformation spec v47.0.0 ([#17392](https://github.com/aws/aws-cdk/issues/17392)) ([7100d43](https://github.com/aws/aws-cdk/commit/7100d43ba7b9e9ce74fb64b33403aa8eaee63255)) +* **lambda-nodejs:** custom asset hash ([#16412](https://github.com/aws/aws-cdk/issues/16412)) ([90da730](https://github.com/aws/aws-cdk/commit/90da730244513f9614604f6be3a77adbb6b17f79)), closes [#16157](https://github.com/aws/aws-cdk/issues/16157) + + +### Bug Fixes + +* **codecommit:** notifyOnPullRequestMerged method has a typo in its name ([#17348](https://github.com/aws/aws-cdk/issues/17348)) ([cac5726](https://github.com/aws/aws-cdk/commit/cac572620210a435f679cf7d7d9f8b6e733b340c)) +* **opensearch:** domain doesn't handle tokens in capacity configuration ([#17131](https://github.com/aws/aws-cdk/issues/17131)) ([2627939](https://github.com/aws/aws-cdk/commit/2627939108a2e979e385bf2942da1c05d48c678c)), closes [#15014](https://github.com/aws/aws-cdk/issues/15014) + +## [1.131.0](https://github.com/aws/aws-cdk/compare/v1.130.0...v1.131.0) (2021-11-07) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **apigatewayv2-authorizers:** `userPoolClient` property in `UserPoolAuthorizerProps` +is now renamed to `userPoolClients`. + +### Features + +* **apigatewayv2-authorizers:** http api - allow multiple user pool clients per HttpUserPoolAuthorizer ([#16903](https://github.com/aws/aws-cdk/issues/16903)) ([747eb7c](https://github.com/aws/aws-cdk/commit/747eb7cf5dba4514241103ffebc49e03261d25a9)), closes [#15431](https://github.com/aws/aws-cdk/issues/15431) +* **certificatemanager:** requesting private certificates issued by Private Certificate Authority ([#16315](https://github.com/aws/aws-cdk/issues/16315)) ([e26f5be](https://github.com/aws/aws-cdk/commit/e26f5befc2adedeb524fd263424c7920989b2288)), closes [#10076](https://github.com/aws/aws-cdk/issues/10076) +* **cfnspec:** cloudformation spec v46.0.0 ([#17223](https://github.com/aws/aws-cdk/issues/17223)) ([d9f7b58](https://github.com/aws/aws-cdk/commit/d9f7b58a91a625ffd9bc366767794a3101b0afeb)) +* **cfnspec:** cloudformation spec v46.0.0 ([#17334](https://github.com/aws/aws-cdk/issues/17334)) ([e0f1180](https://github.com/aws/aws-cdk/commit/e0f118046c4a0350bdd614fbff4b96ba7772402e)) +* **cfnspec:** cloudformation spec v47.0.0 ([#17350](https://github.com/aws/aws-cdk/issues/17350)) ([ea71b4e](https://github.com/aws/aws-cdk/commit/ea71b4ed7466d8799bde4fdd5adfed9fc8febb9c)), closes [#17290](https://github.com/aws/aws-cdk/issues/17290) [#17223](https://github.com/aws/aws-cdk/issues/17223) +* **cfnspec:** cloudformation spec v47.0.0 ([#17353](https://github.com/aws/aws-cdk/issues/17353)) ([7886607](https://github.com/aws/aws-cdk/commit/7886607528b0cb005fa1176803b2a45d3e948f48)) +* **cli:** added `build` field to cdk.json ([#17176](https://github.com/aws/aws-cdk/issues/17176)) ([57ad1e0](https://github.com/aws/aws-cdk/commit/57ad1e087edef653d672c1426b920b12962f0f0f)) +* **cli:** introduce the 'watch' command ([#17240](https://github.com/aws/aws-cdk/issues/17240)) ([0adc8b7](https://github.com/aws/aws-cdk/commit/0adc8b7e13011956929fc945e083f75edec16698)) +* **codepipeline:** add construct for registering custom Actions ([#17041](https://github.com/aws/aws-cdk/issues/17041)) ([c66ac89](https://github.com/aws/aws-cdk/commit/c66ac89f43d3d2cee2b5842c54dc00e14ccdd2f4)), closes [#17039](https://github.com/aws/aws-cdk/issues/17039) +* **docdb:** add the ability to exclude characters when generating passwords ([#17262](https://github.com/aws/aws-cdk/issues/17262)) ([135f7d3](https://github.com/aws/aws-cdk/commit/135f7d33db5e96c3af4a8691c13b419e7b14ceae)), closes [#15732](https://github.com/aws/aws-cdk/issues/15732) +* **ec2:** add c6i instances ([#17237](https://github.com/aws/aws-cdk/issues/17237)) ([25cea18](https://github.com/aws/aws-cdk/commit/25cea1807539a8d45f3f4ff8b775b3417387d6fe)) +* **ecs-service-extensions:** Target tracking policies for Service Extensions ([#17101](https://github.com/aws/aws-cdk/issues/17101)) ([6420b18](https://github.com/aws/aws-cdk/commit/6420b1817d4319924d11cfccb8b6a29d4a2d5008)) +* **eks:** expose FargateCluster's defaultProfile ([#17130](https://github.com/aws/aws-cdk/issues/17130)) ([e461601](https://github.com/aws/aws-cdk/commit/e4616010c1915206758be3bf4cd6da9f14d2101a)), closes [#16149](https://github.com/aws/aws-cdk/issues/16149) +* **iot:** allow setting `description` and `enabled` of TopicRule ([#17225](https://github.com/aws/aws-cdk/issues/17225)) ([a9aae09](https://github.com/aws/aws-cdk/commit/a9aae097daad475dd57bbf4842956327a6d5a220)) +* **iot:** allow setting `errorAction` of TopicRule ([#17287](https://github.com/aws/aws-cdk/issues/17287)) ([e412308](https://github.com/aws/aws-cdk/commit/e412308bc81ede16b079077cfa4774ceaa2fadeb)) +* **iot-actions:** Add the action to put CloudWatch Logs ([#17228](https://github.com/aws/aws-cdk/issues/17228)) ([a7c869e](https://github.com/aws/aws-cdk/commit/a7c869e6d57932389df572cd7f104a4c9ea8f8a5)) +* **lambda-nodejs:** add sourcesContent in BundlingOptions ([#17280](https://github.com/aws/aws-cdk/issues/17280)) ([ea56e69](https://github.com/aws/aws-cdk/commit/ea56e6925422ebb987dbd87952511f23832ac7b6)), closes [#17256](https://github.com/aws/aws-cdk/issues/17256) +* **logs:** add support for cloudwatch logs resource policy ([#17015](https://github.com/aws/aws-cdk/issues/17015)) ([e9a461d](https://github.com/aws/aws-cdk/commit/e9a461d6dcbad933fcb9d671a8c5b5ad8f5ece8d)), closes [#5343](https://github.com/aws/aws-cdk/issues/5343) +* **servicecatalog:** allow creating a CFN Product Version with CDK code ([#17144](https://github.com/aws/aws-cdk/issues/17144)) ([f8d0ef5](https://github.com/aws/aws-cdk/commit/f8d0ef550df07e43aeab35dde4406c92f7551ed0)) +* **synthetics:** add static cron method to schedule class ([#17250](https://github.com/aws/aws-cdk/issues/17250)) ([1ab9b26](https://github.com/aws/aws-cdk/commit/1ab9b265e9899ffcd093b3600d658c8a6519cc69)), closes [#16402](https://github.com/aws/aws-cdk/issues/16402) + + +### Bug Fixes + +* **aws-eks:** proxy support and allow assigning a security group to all cluster handler functions ([#17200](https://github.com/aws/aws-cdk/issues/17200)) ([7bbd10d](https://github.com/aws/aws-cdk/commit/7bbd10deb322daf8ef1504ceb84ad3c895f291ae)), closes [#12469](https://github.com/aws/aws-cdk/issues/12469) +* **cli:** `wmic not found` on modern Windows systems ([#17070](https://github.com/aws/aws-cdk/issues/17070)) ([332ce4d](https://github.com/aws/aws-cdk/commit/332ce4d9ae995bd1336fef13e2c7f9fc0c12f34d)), closes [#16419](https://github.com/aws/aws-cdk/issues/16419) +* **cli:** cdk ls --long outputs less-friendly stack IDs for nested assemblies ([#17263](https://github.com/aws/aws-cdk/issues/17263)) ([864c50e](https://github.com/aws/aws-cdk/commit/864c50ed2f3ae133af0cffd17ed77a6cf32ac6f4)), closes [#14379](https://github.com/aws/aws-cdk/issues/14379) +* **cli:** no longer disable rollback by default for hotswap deployments ([#17317](https://github.com/aws/aws-cdk/issues/17317)) ([e32b616](https://github.com/aws/aws-cdk/commit/e32b61652b5d01c44b05c2ac6d5fb1e99b50e059)), closes [#17267](https://github.com/aws/aws-cdk/issues/17267) +* **cognito:** ambiguous error message when same trigger is added twice ([#16917](https://github.com/aws/aws-cdk/issues/16917)) ([4ae78b0](https://github.com/aws/aws-cdk/commit/4ae78b07af20ea3ef049079ac5b892f9ee8476e5)) +* **ec2:** functions addIngressRule and addEgressRule detect unresolved tokens as duplicates ([#17221](https://github.com/aws/aws-cdk/issues/17221)) ([d4952c3](https://github.com/aws/aws-cdk/commit/d4952c3cbe12e7c8c27e1bca7f9d8536d93fd3cb)), closes [#17201](https://github.com/aws/aws-cdk/issues/17201) +* **lambda-nodejs:** yarn berry goes into immutable mode in CI ([#17086](https://github.com/aws/aws-cdk/issues/17086)) ([cc8dd69](https://github.com/aws/aws-cdk/commit/cc8dd694e6746b9c6fc4663775aaa3b68d19ef61)), closes [#17082](https://github.com/aws/aws-cdk/issues/17082) +* **pipelines:** `additionalInputs` not working ([#17279](https://github.com/aws/aws-cdk/issues/17279)) ([9e81dc7](https://github.com/aws/aws-cdk/commit/9e81dc731993a55fbc05c642ce96151f12ed69da)), closes [#17224](https://github.com/aws/aws-cdk/issues/17224) +* **s3:** enforce that fromBucketAttributes supplies a valid bucket name ([#16915](https://github.com/aws/aws-cdk/issues/16915)) ([30ac0cc](https://github.com/aws/aws-cdk/commit/30ac0cc2d95ef3fd79d0658428975ea675b6916f)) + + +### Reverts + +* "chore: activate 'rosetta infuse' feature ([#17191](https://github.com/aws/aws-cdk/issues/17191))" ([#17329](https://github.com/aws/aws-cdk/issues/17329)) ([c8cd515](https://github.com/aws/aws-cdk/commit/c8cd515b3984ce0d8bfbe2d19cd56d299785e78b)) + +## [1.130.0](https://github.com/aws/aws-cdk/compare/v1.129.0...v1.130.0) (2021-10-29) + + +### Features + +* **amplify:** Add support for custom headers in the App ([#17102](https://github.com/aws/aws-cdk/issues/17102)) ([9f3abd7](https://github.com/aws/aws-cdk/commit/9f3abd745c98a65e7314528f40d08ea2ecbe19a6)), closes [#17084](https://github.com/aws/aws-cdk/issues/17084) +* **aws-route53-targets:** Support for Elastic Beanstalk environment URLs ([#16305](https://github.com/aws/aws-cdk/issues/16305)) ([bc07cb0](https://github.com/aws/aws-cdk/commit/bc07cb0e383aa64280a9c7f8ac4870d296830cf7)) +* **cli:** deployment progress shows stack name ([#16604](https://github.com/aws/aws-cdk/issues/16604)) ([322cf10](https://github.com/aws/aws-cdk/commit/322cf10ef3257b9d20d898882a14de91110a0033)) +* **cloudfront:** add amplify managed cache policy ([#16880](https://github.com/aws/aws-cdk/issues/16880)) ([8d0c555](https://github.com/aws/aws-cdk/commit/8d0c555d048c07518c89e69951a1e9f21ba99bd7)) +* **codebuild:** add fromEcrRepository to LinuxGpuBuildImage ([#17170](https://github.com/aws/aws-cdk/issues/17170)) ([7585680](https://github.com/aws/aws-cdk/commit/758568007bf82a97ed6edba3ef4717735b224bf9)), closes [#16500](https://github.com/aws/aws-cdk/issues/16500) +* **core:** Docker tags can be prefixed ([#17028](https://github.com/aws/aws-cdk/issues/17028)) ([d298696](https://github.com/aws/aws-cdk/commit/d298696a7d8978296a34294484cea80f91ebe880)) +* **core:** subtract Durations ([#16734](https://github.com/aws/aws-cdk/issues/16734)) ([7a333b0](https://github.com/aws/aws-cdk/commit/7a333b018c9bb2430165177d3e65614cf1d66519)), closes [#16535](https://github.com/aws/aws-cdk/issues/16535) +* **ec2:** add c5ad instances ([#16428](https://github.com/aws/aws-cdk/issues/16428)) ([0318253](https://github.com/aws/aws-cdk/commit/0318253b423bb65ca7e6bf65411df767f2734296)) +* **ec2:** add region parameter for UserData via addS3DownloadCommand ([#16667](https://github.com/aws/aws-cdk/issues/16667)) ([691d377](https://github.com/aws/aws-cdk/commit/691d3771d32002b3cd4cb1221af92762b749e716)), closes [#8287](https://github.com/aws/aws-cdk/issues/8287) +* **ec2:** add vpcArn to IVpc and Vpc ([#16666](https://github.com/aws/aws-cdk/issues/16666)) ([7b31376](https://github.com/aws/aws-cdk/commit/7b31376e6349440f7b215d6e11c3dd900d50df34)), closes [#16493](https://github.com/aws/aws-cdk/issues/16493) +* **ec2:** add X2g instances (for RDS) ([#17081](https://github.com/aws/aws-cdk/issues/17081)) ([443a23e](https://github.com/aws/aws-cdk/commit/443a23e8c1e0de97f6ae05a3e451b0407165a447)), closes [/github.com/aws/aws-cdk/issues/16948#issuecomment-946254267](https://github.com/aws//github.com/aws/aws-cdk/issues/16948/issues/issuecomment-946254267) [#16948](https://github.com/aws/aws-cdk/issues/16948) +* **ec2:** include p4d instance class ([#17147](https://github.com/aws/aws-cdk/issues/17147)) ([6e13adc](https://github.com/aws/aws-cdk/commit/6e13adc281722a491c0708954d7ed637ad45033b)) +* **ec2:** look up VPC from different regions ([#16728](https://github.com/aws/aws-cdk/issues/16728)) ([f1e244b](https://github.com/aws/aws-cdk/commit/f1e244b331f95253030bae0525775683b5a350c4)), closes [#10208](https://github.com/aws/aws-cdk/issues/10208) +* **ec2:** VPC endpoint for AWS Xray ([#16788](https://github.com/aws/aws-cdk/issues/16788)) ([c24af54](https://github.com/aws/aws-cdk/commit/c24af54946d3668afa596dbf2a776b7cf21f8a99)), closes [#16306](https://github.com/aws/aws-cdk/issues/16306) +* **events:** DLQ support for EventBus target ([#16383](https://github.com/aws/aws-cdk/issues/16383)) ([dbb3f25](https://github.com/aws/aws-cdk/commit/dbb3f25904403bfc020a081e94270f5c16a7606f)), closes [#15954](https://github.com/aws/aws-cdk/issues/15954) +* **iot:** add the TopicRule L2 construct ([#16681](https://github.com/aws/aws-cdk/issues/16681)) ([86f85ce](https://github.com/aws/aws-cdk/commit/86f85ce10f78b86133f9dab9851e56d03fb28cc0)), closes [#16602](https://github.com/aws/aws-cdk/issues/16602) +* **iot:** allow setting Actions of TopicRule ([#17110](https://github.com/aws/aws-cdk/issues/17110)) ([0cabb9f](https://github.com/aws/aws-cdk/commit/0cabb9f2d2f50c03337cd6f35bf47fc54ada3a21)), closes [#16681](https://github.com/aws/aws-cdk/issues/16681) [/github.com/aws/aws-cdk/pull/16681#discussion_r733912215](https://github.com/aws//github.com/aws/aws-cdk/pull/16681/issues/discussion_r733912215) +* **iot:** create new aws-iot-actions module ([#17112](https://github.com/aws/aws-cdk/issues/17112)) ([06838e6](https://github.com/aws/aws-cdk/commit/06838e66db0c9a48e2aa7da1e7707fda335bb62c)), closes [#16681](https://github.com/aws/aws-cdk/issues/16681) [/github.com/aws/aws-cdk/pull/16681#discussion_r733912215](https://github.com/aws//github.com/aws/aws-cdk/pull/16681/issues/discussion_r733912215) +* **lambda-nodejs:** esbuild charset option ([#16726](https://github.com/aws/aws-cdk/issues/16726)) ([56033a2](https://github.com/aws/aws-cdk/commit/56033a2a6d4be0444694d9f88260c574a4fa1a1d)), closes [#16668](https://github.com/aws/aws-cdk/issues/16668) +* **lambda-nodejs:** typescript emitDecoratorMetadata support ([#16543](https://github.com/aws/aws-cdk/issues/16543)) ([55d3c50](https://github.com/aws/aws-cdk/commit/55d3c507707192d7aa5ea4a38ee0d1cb58f07e06)), closes [#13767](https://github.com/aws/aws-cdk/issues/13767) +* **rds:** support backtrackWindow in DatabaseCluster ([#17160](https://github.com/aws/aws-cdk/issues/17160)) ([fcd17e9](https://github.com/aws/aws-cdk/commit/fcd17e9c9a9e1b83a29c140d558f696c0290bfd7)), closes [#9369](https://github.com/aws/aws-cdk/issues/9369) [#9369](https://github.com/aws/aws-cdk/issues/9369) +* **route53:** Expose VpcEndpointServiceDomainName domain name as a property ([#16458](https://github.com/aws/aws-cdk/issues/16458)) ([e063fbd](https://github.com/aws/aws-cdk/commit/e063fbd3a31bdce046b2598e4a429c45d016f055)) +* **sns:** addSubscription returns the created Subscription ([#16785](https://github.com/aws/aws-cdk/issues/16785)) ([62f389e](https://github.com/aws/aws-cdk/commit/62f389ea0522cbaefca5ca17080228031d401ce6)) +* **synthetics:** add syn-nodejs-puppeteer-3.3 runtime ([#17132](https://github.com/aws/aws-cdk/issues/17132)) ([8343bec](https://github.com/aws/aws-cdk/commit/8343beccbee06f453b63387f54768b320fe01339)) + + +### Bug Fixes + +* **cli:** downgrade bootstrap stack error message needs a hint for new-style synthesis ([#16237](https://github.com/aws/aws-cdk/issues/16237)) ([e55301b](https://github.com/aws/aws-cdk/commit/e55301b635374a87822f78870981a9e06e13d99e)) +* **core:** `DefaultSynthesizer` deployments are never skipped ([#17099](https://github.com/aws/aws-cdk/issues/17099)) ([c74b012](https://github.com/aws/aws-cdk/commit/c74b0127af95f8e86b95a0be2f2c6cb30fab1103)), closes [#16959](https://github.com/aws/aws-cdk/issues/16959) +* **core:** SecretValue.secretsManager fails for tokenized secret-id ([#16230](https://github.com/aws/aws-cdk/issues/16230)) ([5831456](https://github.com/aws/aws-cdk/commit/5831456465fa44af96a268de56db0e3a8d3c2ea6)), closes [#16166](https://github.com/aws/aws-cdk/issues/16166) +* **custom-resources:** invalid service name leads to unhelpful error message ([#16718](https://github.com/aws/aws-cdk/issues/16718)) ([354686b](https://github.com/aws/aws-cdk/commit/354686b189377dd1daae7ba616e8fb62488d9855)), closes [#7312](https://github.com/aws/aws-cdk/issues/7312) +* **custom-resources:** Role Session Name can exceed maximum size ([#16680](https://github.com/aws/aws-cdk/issues/16680)) ([3617b70](https://github.com/aws/aws-cdk/commit/3617b70527516237955b8415fcfc8b58d3e23b3c)) +* **elasticloadbalancingv2:** always set stickiness ([#17111](https://github.com/aws/aws-cdk/issues/17111)) ([0a23953](https://github.com/aws/aws-cdk/commit/0a23953d92df070736f7d036cc2b24e68de4bf64)), closes [#16620](https://github.com/aws/aws-cdk/issues/16620) +* **lambda-event-sources:** dynamo batch size cannot be a CfnParameter ([#16540](https://github.com/aws/aws-cdk/issues/16540)) ([56974ac](https://github.com/aws/aws-cdk/commit/56974ac4152bc082470d56dd66e4ef7aad042815)), closes [#16221](https://github.com/aws/aws-cdk/issues/16221) +* **logs:** Apply tags to log retention Lambda ([#17029](https://github.com/aws/aws-cdk/issues/17029)) ([a6aaa64](https://github.com/aws/aws-cdk/commit/a6aaa64bf9779b984f20d18881b4f6e510ac091a)), closes [#15032](https://github.com/aws/aws-cdk/issues/15032) +* **rds:** using both Instance imports & exports for Postgres fails deployment ([#17060](https://github.com/aws/aws-cdk/issues/17060)) ([ab627c6](https://github.com/aws/aws-cdk/commit/ab627c69e9edac82b1fd07d2c9ee1b05f7dc3166)), closes [#16757](https://github.com/aws/aws-cdk/issues/16757) +* **redshift:** cluster uses key ARN instead of key ID ([#17108](https://github.com/aws/aws-cdk/issues/17108)) ([bdf30c6](https://github.com/aws/aws-cdk/commit/bdf30c696b04b26a8e41548839d5c4cf61d471cc)), closes [#17032](https://github.com/aws/aws-cdk/issues/17032) + +## [1.129.0](https://github.com/aws/aws-cdk/compare/v1.128.0...v1.129.0) (2021-10-21) + + +### Features + +* **aws-autoscaling:** add flag and aspect to require imdsv2 ([#16052](https://github.com/aws/aws-cdk/issues/16052)) ([ef7e20d](https://github.com/aws/aws-cdk/commit/ef7e20df08b4321f210bfc050afa42d7b4901931)) +* **codebuild:** add support for small ARM machine type ([#16635](https://github.com/aws/aws-cdk/issues/16635)) ([55fbc86](https://github.com/aws/aws-cdk/commit/55fbc866ef0195fdfc722206e4d69a1f4469cd40)), closes [#16633](https://github.com/aws/aws-cdk/issues/16633) +* **dynamodb:** add option to skip waiting for global replication to finish ([#16983](https://github.com/aws/aws-cdk/issues/16983)) ([254601f](https://github.com/aws/aws-cdk/commit/254601f477a4da309e81f5384140427f1b958bfd)), closes [#16611](https://github.com/aws/aws-cdk/issues/16611) +* **ec2:** add aspect to require imdsv2 ([#16051](https://github.com/aws/aws-cdk/issues/16051)) ([0947b21](https://github.com/aws/aws-cdk/commit/0947b21c1e3186042324820ec5ab433237246f58)) +* **eks:** configure serviceIpv4Cidr on the cluster ([#16957](https://github.com/aws/aws-cdk/issues/16957)) ([72102c7](https://github.com/aws/aws-cdk/commit/72102c750bfd6564cd51c1a5d8abc79b1ba1d3ce)), closes [#16541](https://github.com/aws/aws-cdk/issues/16541) +* **events:** Add DLQ support for SQS target ([#16916](https://github.com/aws/aws-cdk/issues/16916)) ([7fda903](https://github.com/aws/aws-cdk/commit/7fda90318e18b3a5d126b040e35a0146634d5f2d)), closes [#16417](https://github.com/aws/aws-cdk/issues/16417) +* **msk:** add Kafka version 2.8.1 ([#16881](https://github.com/aws/aws-cdk/issues/16881)) ([7db5c8c](https://github.com/aws/aws-cdk/commit/7db5c8cdafe7b9b22b6b40cb25ed8bd1946301f4)) +* **stepfunctions-tasks:** add `enableNetworkIsolation` property to `SageMakerCreateTrainingJobProps` ([#16792](https://github.com/aws/aws-cdk/issues/16792)) ([69ac520](https://github.com/aws/aws-cdk/commit/69ac520452b219bf242f2fbb4740f6b1b8b8790f)), closes [#16779](https://github.com/aws/aws-cdk/issues/16779) + + +### Bug Fixes + +* **apigatewayv2:** unable to retrieve domain url for default stage ([#16854](https://github.com/aws/aws-cdk/issues/16854)) ([c6db91e](https://github.com/aws/aws-cdk/commit/c6db91eee2cb658ce347c7ac6d6e3c95bc5977dc)), closes [#16638](https://github.com/aws/aws-cdk/issues/16638) +* **cfn-diff:** correctly handle Date strings in diff ([#16591](https://github.com/aws/aws-cdk/issues/16591)) ([86f2714](https://github.com/aws/aws-cdk/commit/86f2714613f06aaf2bcee27da2f66066c8e863d0)), closes [#16444](https://github.com/aws/aws-cdk/issues/16444) +* **ecs:** imported services don't have account & region set correctly ([#16997](https://github.com/aws/aws-cdk/issues/16997)) ([dc6f743](https://github.com/aws/aws-cdk/commit/dc6f7433f01b9bc2c8206fb03d72ab8404fe4f6a)), closes [#11199](https://github.com/aws/aws-cdk/issues/11199) [#11199](https://github.com/aws/aws-cdk/issues/11199) [#15944](https://github.com/aws/aws-cdk/issues/15944) +* **events:** PhysicalName.GENERATE_IF_NEEDED does not work for EventBus ([#17008](https://github.com/aws/aws-cdk/issues/17008)) ([707fa00](https://github.com/aws/aws-cdk/commit/707fa003a458039878a1ae5173b6665a84c1170b)), closes [#14337](https://github.com/aws/aws-cdk/issues/14337) +* **lambda:** docker image function fails when insightsVersion is specified ([#16781](https://github.com/aws/aws-cdk/issues/16781)) ([d0e15cc](https://github.com/aws/aws-cdk/commit/d0e15ccaca22c5e05b9186aa1a241e744d67c96a)), closes [#16642](https://github.com/aws/aws-cdk/issues/16642) +* **lambda-layer-node-proxy-agent:** Replace use of package.json with Dockerfile command `npm install [package]@[version]` ([#17078](https://github.com/aws/aws-cdk/issues/17078)) ([a129046](https://github.com/aws/aws-cdk/commit/a129046495a926561f94f5ce1f41c34b1df3afde)) +* **opensearch:** add validation to domainName property ([#17017](https://github.com/aws/aws-cdk/issues/17017)) ([3ec6832](https://github.com/aws/aws-cdk/commit/3ec683283e96159d588797bd46d33c82ff3076f1)), closes [#17016](https://github.com/aws/aws-cdk/issues/17016) +* **pipelines:** `additionalInputs` fails for deep directory ([#17074](https://github.com/aws/aws-cdk/issues/17074)) ([403d3ce](https://github.com/aws/aws-cdk/commit/403d3ce3bc0f4e30e9694e5c20743f0032009464)), closes [#16936](https://github.com/aws/aws-cdk/issues/16936) + +## [1.128.0](https://github.com/aws/aws-cdk/compare/v1.127.0...v1.128.0) (2021-10-14) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **assertions:** Starting this release, the `assertions` module will be +published to Maven with the name 'assertions' instead of +'cdk-assertions'. + +### Features + +* **apigatewayv2-integrations:** http api - support for request parameter mapping ([#15630](https://github.com/aws/aws-cdk/issues/15630)) ([0452aed](https://github.com/aws/aws-cdk/commit/0452aed2f00198e05bd65b1d20246f7de0b24e20)) +* **cli:** hotswap deployments for ECS Services ([#16864](https://github.com/aws/aws-cdk/issues/16864)) ([ad7288f](https://github.com/aws/aws-cdk/commit/ad7288f35a17fcfbecd7080e99ece4873fa99ad2)) +* **codepipeline:** add support for string user parameters to the Lambda invoke action ([#16946](https://github.com/aws/aws-cdk/issues/16946)) ([e19ea31](https://github.com/aws/aws-cdk/commit/e19ea31dbf62446edaf5131c75246098ab05da6e)), closes [#16776](https://github.com/aws/aws-cdk/issues/16776) +* **lambda:** docker platform for architecture ([#16858](https://github.com/aws/aws-cdk/issues/16858)) ([5c258a3](https://github.com/aws/aws-cdk/commit/5c258a30367a4922e404eb26e5aa076720846fbe)) +* **lambda-event-sources:** self managed kafka: support sasl/plain authentication ([#16712](https://github.com/aws/aws-cdk/issues/16712)) ([d4ad93f](https://github.com/aws/aws-cdk/commit/d4ad93f30877b26b851caa81d3a4a1d80df55164)) +* **stepfunctions-tasks:** AWS SDK service integrations ([#16746](https://github.com/aws/aws-cdk/issues/16746)) ([ae840ff](https://github.com/aws/aws-cdk/commit/ae840ff1abb8283a1290dae5859f5729a9cf72b1)), closes [#16780](https://github.com/aws/aws-cdk/issues/16780) + + +### Bug Fixes + +* **ecs:** add ASG capacity via Capacity Provider by not specifying machineImageType ([#16361](https://github.com/aws/aws-cdk/issues/16361)) ([93b3fdc](https://github.com/aws/aws-cdk/commit/93b3fdce80f0997d7b809f9ef7e3edd1e75e1f42)), closes [#16360](https://github.com/aws/aws-cdk/issues/16360) +* **servicecatalog:** Allow users to create multiple product versions from assets. ([#16914](https://github.com/aws/aws-cdk/issues/16914)) ([958d755](https://github.com/aws/aws-cdk/commit/958d755ff7acaf016e3f8969bf5ab07d4dc2977b)) +* **codebuild:** add build image AMAZON_LINUX_2_ARM_2 ([#16931](https://github.com/aws/aws-cdk/issues/16931)) ([370cb31](https://github.com/aws/aws-cdk/commit/370cb310cce3fccc5381d8d53130e21b266de868)), closes [#16930](https://github.com/aws/aws-cdk/issues/16930) +* **core:** asset hash is different between linux and windows ([#16945](https://github.com/aws/aws-cdk/issues/16945)) ([59950dd](https://github.com/aws/aws-cdk/commit/59950dd331635fb707aac819529614c0f3e47ee5)), closes [#14555](https://github.com/aws/aws-cdk/issues/14555) [#16928](https://github.com/aws/aws-cdk/issues/16928) +* **ecs-patterns:** minScalingCapacity cannot be set to 0 ([#16961](https://github.com/aws/aws-cdk/issues/16961)) ([589f284](https://github.com/aws/aws-cdk/commit/589f284acec8530aa9824b75a5daef4632e98985)), closes [#15632](https://github.com/aws/aws-cdk/issues/15632) [#14336](https://github.com/aws/aws-cdk/issues/14336) +* **ssm:** StringParameter accepts ParameterType.AWS_EC2_IMAGE_ID as type ([#16884](https://github.com/aws/aws-cdk/issues/16884)) ([2b353be](https://github.com/aws/aws-cdk/commit/2b353be5291cbcdc56a8863038eed4a5f2adc65f)), closes [#16806](https://github.com/aws/aws-cdk/issues/16806) +* use registry.npmjs.com to fix shinkwrap resolves ([#16607](https://github.com/aws/aws-cdk/issues/16607)) ([8f91531](https://github.com/aws/aws-cdk/commit/8f91531c3c25900316d40d5564450566a03e27ee)) + + +### Miscellaneous Chores + +* **assertions:** consistent naming in maven ([#16921](https://github.com/aws/aws-cdk/issues/16921)) ([0dcd9ec](https://github.com/aws/aws-cdk/commit/0dcd9eca3a1014c39f92d9e052b67974fc751af0)) + +## [1.127.0](https://github.com/aws/aws-cdk/compare/v1.126.0...v1.127.0) (2021-10-08) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **assertions:** `Match.absentProperty()` becomes `Match.absent()`, and its type changes from `string` to `Matcher`. + +### Features + +* **appsync:** Lambda Authorizer for AppSync GraphqlApi ([#16743](https://github.com/aws/aws-cdk/issues/16743)) ([bdbe8b6](https://github.com/aws/aws-cdk/commit/bdbe8b6cf6ab1ae261dddeb39576749e768183b3)), closes [#16380](https://github.com/aws/aws-cdk/issues/16380) +* **chatbot:** allow adding a sns topic in existing SlackChannel ([#16643](https://github.com/aws/aws-cdk/issues/16643)) ([d29a20b](https://github.com/aws/aws-cdk/commit/d29a20bece48829e5dddbf3fd9045a96f1440c02)), closes [#15588](https://github.com/aws/aws-cdk/issues/15588) +* **cfnspec:** cloudformation spec v43.0.0 ([#16748](https://github.com/aws/aws-cdk/issues/16748)) ([7c473a6](https://github.com/aws/aws-cdk/commit/7c473a6efa1f7e07799a96f649cb32f66d178e43)) +* **cli:** hotswap deployments for StepFunctions State Machines ([#16489](https://github.com/aws/aws-cdk/issues/16489)) ([c3417f6](https://github.com/aws/aws-cdk/commit/c3417f651e45170efd339960fbb0e4957bcbd3a3)) +* **ec2:** add X2gd instances ([#16810](https://github.com/aws/aws-cdk/issues/16810)) ([6d468d2](https://github.com/aws/aws-cdk/commit/6d468d2f742aad8bc9de6bfe9650c3cdccd30a32)), closes [#16794](https://github.com/aws/aws-cdk/issues/16794) +* **ecr-assets:** control docker image asset hash ([#16070](https://github.com/aws/aws-cdk/issues/16070)) ([13f67e7](https://github.com/aws/aws-cdk/commit/13f67e7dbcf2ca7a921e7ffb932f260c74005408)), closes [#15936](https://github.com/aws/aws-cdk/issues/15936) +* **elbv2:** support ALB target for NLB ([#16687](https://github.com/aws/aws-cdk/issues/16687)) ([27cc821](https://github.com/aws/aws-cdk/commit/27cc82186c73db5e68e00448133dd6e79e13d90c)), closes [#16679](https://github.com/aws/aws-cdk/issues/16679) + + +### Bug Fixes + +* **assertions:** `hasResourceProperties` is incompatible with `Match.not` and `Match.absent` ([#16678](https://github.com/aws/aws-cdk/issues/16678)) ([6f0a507](https://github.com/aws/aws-cdk/commit/6f0a5076b1e074fd33ed118af8e48b72d7593418)), closes [#16626](https://github.com/aws/aws-cdk/issues/16626) +* **cloudfront:** EdgeFunctions cannot be created when IDs contain spaces ([#16845](https://github.com/aws/aws-cdk/issues/16845)) ([b0752c5](https://github.com/aws/aws-cdk/commit/b0752c5dcd0f1fa64b39d1b80ab2c0e0a99a72b0)), closes [#16832](https://github.com/aws/aws-cdk/issues/16832) +* **cloudwatch:** alarms with accountId fails in regions that don't support cross-account alarms ([#16875](https://github.com/aws/aws-cdk/issues/16875)) ([54472a0](https://github.com/aws/aws-cdk/commit/54472a0ccebe208dca3402367626a938731544b0)), closes [#16874](https://github.com/aws/aws-cdk/issues/16874) +* **iam:** not possible to represent `Principal: *` ([#16843](https://github.com/aws/aws-cdk/issues/16843)) ([6829a2a](https://github.com/aws/aws-cdk/commit/6829a2abe4d020d6a6eae7ff31e23b43d8762920)) +* **lambda:** currentVersion fails when architecture specified ([#16849](https://github.com/aws/aws-cdk/issues/16849)) ([8a0d369](https://github.com/aws/aws-cdk/commit/8a0d3699d7fc3dff70aa6416d30a30b57d29ff7e)), closes [#16814](https://github.com/aws/aws-cdk/issues/16814) +* **s3:** auto-delete fails when bucket has been deleted manually ([#16645](https://github.com/aws/aws-cdk/issues/16645)) ([7b4fa72](https://github.com/aws/aws-cdk/commit/7b4fa721deac1d263d86c1d552c984fa1486f42e)), closes [#16619](https://github.com/aws/aws-cdk/issues/16619) + + +### Miscellaneous Chores + +* **assertions:** replace `absentProperty()` with `absent()` and support it as a `Matcher` type ([#16653](https://github.com/aws/aws-cdk/issues/16653)) ([c980185](https://github.com/aws/aws-cdk/commit/c980185142c58821b7ae7ef0b88c6c98ca8f0246)) + ## [1.126.0](https://github.com/aws/aws-cdk/compare/v1.125.0...v1.126.0) (2021-10-05) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc425a73c7d0f..dca9ebdce5f06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,7 +113,7 @@ $ yarn build $ yarn test ``` -However, if you wish to build the the entire repository, the following command will achieve this. +However, if you wish to build the entire repository, the following command will achieve this. ```console cd @@ -319,7 +319,13 @@ $ yarn watch & # runs in the background ## Breaking Changes -_NOTE: Breaking changes will not be allowed in the upcoming v2 release. These instructions apply to v1._ +**_NOTE_**: _Starting with version 2.0.0 of the AWS CDK, **all modules and members vended as part of the main CDK library**_ +_**(`aws-cdk-lib`) will always be stable**; we are committing to never introduce breaking changes in a non-major bump._ +_Breaking changes are only allowed on pre-released (experimental or dev preview) modules_ +_(those with a `stability` of `experimental` in their respective `package.json` files)._ +_For v1, each module is separately released. For v2, only `stable` modules are released as part of the_ +_main `aws-cdk-lib` release, and all `experimental` modules are released independently as `-alpha` versions,_ +_and not included in the main CDK library._ Whenever you are making changes, there is a chance for those changes to be *breaking* existing users of the library. A change is breaking if there are @@ -455,6 +461,47 @@ If the new behavior is going to be breaking, the user must opt in to it, either Of these two, the first one is preferred if possible (as feature flags have non-local effects which can cause unintended effects). +### Adding new experimental ("preview") APIs + +To make sure we can keep adding features fast, while keeping our commitment to +not release breaking changes, we are introducing a new model - API Previews. +APIs that we want to get in front of developers early, and are not yet +finalized, will be added to the AWS CDK with a specific suffix: `BetaX`. APIs +with the preview suffix will never be removed, instead they will be deprecated +and replaced by either the stable version (without the suffix), or by a newer +preview version. For example, assume we add the method +`grantAwesomePowerBeta1`: + +```ts +/** + * This methods grants awesome powers + */ +grantAwesomePowerBeta1(); +``` + +Times goes by, we get feedback that this method will actually be much better +if it accept a `Principal`. Since adding a required property is a breaking +change, we will add `grantAwesomePowerBeta2()` and deprecate +`grantAwesomePowerBeta1`: + +```ts +/** +* This methods grants awesome powers to the given principal +* +* @param grantee The principal to grant powers to +*/ +grantAwesomePowerBeta2(grantee: iam.IGrantable) + +/** +* This methods grants awesome powers +* @deprecated use grantAwesomePowerBeta2 +*/ +grantAwesomePowerBeta1() +``` + +When we decide its time to graduate the API, the latest preview version will +be deprecated and the final version - `grantAwesomePower` will be added. + ## Documentation Every module's README is rendered as the landing page of the official documentation. For example, this is @@ -931,3 +978,4 @@ $ node --inspect-brk /path/to/aws-cdk/node_modules/.bin/jest -i -t 'TESTNAME' * [Workshop](https://github.com/aws-samples/aws-cdk-intro-workshop): source for https://cdkworkshop.com * [Developer Guide](https://github.com/awsdocs/aws-cdk-guide): markdown source for developer guide * [jsii](https://github.com/aws/jsii): the technology we use for multi-language support. If you are looking to help us support new languages, start there. + diff --git a/DEPRECATED_APIs.md b/DEPRECATED_APIs.md index a08cf9de97f86..fdf0a1877b5dc 100644 --- a/DEPRECATED_APIs.md +++ b/DEPRECATED_APIs.md @@ -3,6 +3,8 @@ | Module | API Element | Message | |--------|-------------|---------| | @aws-cdk/core | AppProps.​runtimeInfo | use `versionReporting` instead | +| @aws-cdk/core | Arn.​parse() | use split instead | +| @aws-cdk/core | ArnComponents.​sep | use arnFormat instead | | @aws-cdk/core | AssetHashType.​BUNDLE | use `OUTPUT` instead | | @aws-cdk/core | AssetStaging.​sourceHash | see `assetHash`. | | @aws-cdk/core | AssetStaging.​stagedPath | Use `absoluteStagedPath` instead. | @@ -40,11 +42,13 @@ | @aws-cdk/core | Stack.​parentStack | use `nestedStackParent` | | @aws-cdk/core | Stack.​addDockerImageAsset() | Use `stack.synthesizer.addDockerImageAsset()` if you are calling, and a different `IStackSynthesizer` class if you are implementing. | | @aws-cdk/core | Stack.​addFileAsset() | Use `stack.synthesizer.addFileAsset()` if you are calling, and a different IStackSynthesizer class if you are implementing. | +| @aws-cdk/core | Stack.​parseArn() | use splitArn instead | | @aws-cdk/core | Stack.​prepareCrossReference() | cross reference handling has been moved to `App.prepare()`. | | @aws-cdk/core | Stack.​reportMissingContext() | use `reportMissingContextKey()` | | @aws-cdk/core | SynthesisOptions | use `app.synth()` or `stage.synth()` instead | | @aws-cdk/core | SynthesisOptions.​outdir | use `app.synth()` or `stage.synth()` instead | | @aws-cdk/core | SynthesisOptions.​skipValidation | use `app.synth()` or `stage.synth()` instead | +| @aws-cdk/core | SynthesisOptions.​validateOnSynthesis | use `app.synth()` or `stage.synth()` instead | | @aws-cdk/core | Tag.​add() | use `Tags.of(scope).add()` | | @aws-cdk/core | Tag.​remove() | use `Tags.of(scope).remove()` | | @aws-cdk/cloud-assembly-schema | ContainerImageAssetMetadataEntry.​imageNameParameter | specify `repositoryName` and `imageTag` instead, and then you know where the image will go. | @@ -60,7 +64,7 @@ | @aws-cdk/cx-api | MissingContext.​provider | moved to package 'cloud-assembly-schema' | | @aws-cdk/cx-api | RuntimeInfo | moved to package 'cloud-assembly-schema' | | constructs | Construct.​onValidate() | use `Node.addValidation()` to subscribe validation functions on this construct instead of overriding this method. | -| constructs | Node.​uniqueId | please avoid using this property and use `uid` instead. This algorithm uses MD5, which is not FIPS-complient and also excludes the identity of the root construct from the calculation. | +| constructs | Node.​uniqueId | please avoid using this property and use `addr` to form unique names. This algorithm uses MD5, which is not FIPS-complient and also excludes the identity of the root construct from the calculation. | | @aws-cdk/assets | CopyOptions | see `core.CopyOptions` | | @aws-cdk/assets | CopyOptions.​exclude | see `core.CopyOptions` | | @aws-cdk/assets | CopyOptions.​follow | use `followSymlinks` instead | @@ -77,10 +81,6 @@ | @aws-cdk/assets | Staging | use `core.AssetStaging` | | @aws-cdk/assets | StagingProps | use `core.AssetStagingProps` | | @aws-cdk/assets | StagingProps.​sourcePath | use `core.AssetStagingProps` | -| @aws-cdk/aws-iam | Anyone | use `AnyPrincipal` | -| @aws-cdk/aws-iam | IPrincipal.​addToPolicy() | Use `addToPrincipalPolicy` instead. | -| @aws-cdk/aws-iam | RoleProps.​externalId | see {@link externalIds} | -| @aws-cdk/aws-kms | KeyProps.​trustAccountIdentities | redundant with the `@aws-cdk/aws-kms:defaultKeyPolicies` feature flag | | @aws-cdk/aws-codebuild | LinuxBuildImage.​UBUNTU_​14_​04_​ANDROID_​JAVA8_​24_​4_​1 | Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section | | @aws-cdk/aws-codebuild | LinuxBuildImage.​UBUNTU_​14_​04_​ANDROID_​JAVA8_​26_​1_​1 | Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section | | @aws-cdk/aws-codebuild | LinuxBuildImage.​UBUNTU_​14_​04_​BASE | Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section | @@ -112,10 +112,11 @@ | @aws-cdk/aws-codebuild | LinuxBuildImage.​UBUNTU_​14_​04_​RUBY_​2_​5_​1 | Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section | | @aws-cdk/aws-codebuild | LinuxBuildImage.​UBUNTU_​14_​04_​RUBY_​2_​5_​3 | Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section | | @aws-cdk/aws-codebuild | WindowsBuildImage.​WIN_​SERVER_​CORE_​2016_​BASE | `WindowsBuildImage.WINDOWS_BASE_2_0` should be used instead. | +| @aws-cdk/aws-cloudwatch | CommonMetricOptions.​dimensions | Use 'dimensionsMap' instead. | | @aws-cdk/aws-cloudwatch | CreateAlarmOptions.​period | Use `metric.with({ period: ... })` to encode the period into the Metric object | | @aws-cdk/aws-cloudwatch | CreateAlarmOptions.​statistic | Use `metric.with({ statistic: ... })` to encode the period into the Metric object | -| @aws-cdk/aws-cloudwatch | IMetric.​toAlarmConfig() | Use `toMetricsConfig()` instead. | -| @aws-cdk/aws-cloudwatch | IMetric.​toGraphConfig() | Use `toMetricsConfig()` instead. | +| @aws-cdk/aws-cloudwatch | IMetric.​toAlarmConfig() | Use `toMetricConfig()` instead. | +| @aws-cdk/aws-cloudwatch | IMetric.​toGraphConfig() | Use `toMetricConfig()` instead. | | @aws-cdk/aws-cloudwatch | MathExpression.​toAlarmConfig() | use toMetricConfig() | | @aws-cdk/aws-cloudwatch | MathExpression.​toGraphConfig() | use toMetricConfig() | | @aws-cdk/aws-cloudwatch | Metric.​toAlarmConfig() | use toMetricConfig() | @@ -143,12 +144,21 @@ | @aws-cdk/aws-cloudwatch | MetricRenderingProperties.​color | Replaced by MetricConfig. | | @aws-cdk/aws-cloudwatch | MetricRenderingProperties.​label | Replaced by MetricConfig. | | @aws-cdk/aws-cloudwatch | MetricRenderingProperties.​stat | Replaced by MetricConfig. | +| @aws-cdk/aws-iam | Anyone | use `AnyPrincipal` | +| @aws-cdk/aws-iam | IPrincipal.​addToPolicy() | Use `addToPrincipalPolicy` instead. | +| @aws-cdk/aws-iam | RoleProps.​externalId | see {@link externalIds} | | @aws-cdk/aws-events | EventBus.​grantPutEvents() | use grantAllPutEvents instead | | @aws-cdk/aws-events | RuleTargetConfig.​id | no replacement. we will always use an autogenerated id. | +| @aws-cdk/aws-ec2 | ClientVpnAuthorizationRuleProps.​clientVpnEndoint | Use `clientVpnEndpoint` instead | +| @aws-cdk/aws-ec2 | ClientVpnRouteProps.​clientVpnEndoint | Use `clientVpnEndpoint` instead | | @aws-cdk/aws-ec2 | InterfaceVpcEndpoint.​securityGroupId | use the `connections` object | | @aws-cdk/aws-ec2 | InterfaceVpcEndpointAttributes.​securityGroupId | use `securityGroups` instead | +| @aws-cdk/aws-ec2 | MachineImage.​fromSSMParameter() | Use `MachineImage.fromSsmParameter()` instead | | @aws-cdk/aws-ec2 | NatInstanceProps.​allowAllTraffic | Use `defaultAllowedTraffic`. | +| @aws-cdk/aws-ec2 | SecurityGroup.​securityGroupName | returns the security group ID, rather than the name. | | @aws-cdk/aws-ec2 | SubnetSelection.​subnetName | Use `subnetGroupName` instead | +| @aws-cdk/aws-ec2 | SubnetType.​ISOLATED | use `SubnetType.PRIVATE_ISOLATED` | +| @aws-cdk/aws-ec2 | SubnetType.​PRIVATE | use `PRIVATE_WITH_NAT` | | @aws-cdk/aws-ec2 | Vpc.​natDependencies | This value is no longer used. | | @aws-cdk/aws-ec2 | Vpc.​addDynamoDbEndpoint() | use `addGatewayEndpoint()` instead | | @aws-cdk/aws-ec2 | Vpc.​addS3Endpoint() | use `addGatewayEndpoint()` instead | @@ -168,11 +178,13 @@ | @aws-cdk/aws-ec2 | WindowsVersion.​WINDOWS_​SERVER_​2012_​RTM_​PORTUGESE_​PORTUGAL_​64BIT_​BASE | use WINDOWS_SERVER_2012_RTM_PORTUGUESE_PORTUGAL_64BIT_BASE | | @aws-cdk/aws-ec2 | WindowsVersion.​WINDOWS_​SERVER_​2019_​PORTUGESE_​BRAZIL_​FULL_​BASE | use WINDOWS_SERVER_2019_PORTUGUESE_BRAZIL_FULL_BASE | | @aws-cdk/aws-ec2 | WindowsVersion.​WINDOWS_​SERVER_​2019_​PORTUGESE_​PORTUGAL_​FULL_​BASE | use WINDOWS_SERVER_2019_PORTUGUESE_PORTUGAL_FULL_BASE | +| @aws-cdk/aws-kms | KeyProps.​trustAccountIdentities | redundant with the `@aws-cdk/aws-kms:defaultKeyPolicies` feature flag | | @aws-cdk/aws-s3-assets | Asset.​s3Url | use `httpUrl` | | @aws-cdk/aws-s3-assets | Asset.​sourceHash | see `assetHash` | | @aws-cdk/aws-s3-assets | AssetOptions.​sourceHash | see `assetHash` and `assetHashType` | | @aws-cdk/aws-ecr-assets | DockerImageAsset.​sourceHash | use assetHash | | @aws-cdk/aws-ecr-assets | DockerImageAssetOptions.​repositoryName | to control the location of docker image assets, please override `Stack.addDockerImageAsset`. this feature will be removed in future releases. | +| @aws-cdk/aws-ecr-assets | TarballImageAsset.​sourceHash | use assetHash | | @aws-cdk/aws-secretsmanager | AttachedSecretOptions | use `secret.attach()` instead | | @aws-cdk/aws-secretsmanager | AttachedSecretOptions.​target | use `secret.attach()` instead | | @aws-cdk/aws-secretsmanager | AttachmentTargetType.​INSTANCE | use RDS_DB_INSTANCE instead | @@ -181,6 +193,8 @@ | @aws-cdk/aws-secretsmanager | Secret.​fromSecretName() | use `fromSecretNameV2` | | @aws-cdk/aws-secretsmanager | Secret.​addTargetAttachment() | use `attach()` instead | | @aws-cdk/aws-secretsmanager | SecretAttributes.​secretArn | use `secretCompleteArn` or `secretPartialArn` instead. | +| @aws-cdk/aws-secretsmanager | SecretRotationApplication.​applicationId | only valid when deploying to the 'aws' partition. Use `applicationArnForPartition` instead. | +| @aws-cdk/aws-secretsmanager | SecretRotationApplication.​semanticVersion | only valid when deploying to the 'aws' partition. Use `semanticVersionForPartition` instead. | | @aws-cdk/aws-lambda | Code.​isInline | this value is ignored since inline is now determined based on the the `inlineCode` field of `CodeConfig` returned from `bind()`. | | @aws-cdk/aws-lambda | Code.​asset() | use `fromAsset` | | @aws-cdk/aws-lambda | Code.​bucket() | use `fromBucket` | @@ -188,6 +202,7 @@ | @aws-cdk/aws-lambda | Code.​inline() | use `fromInline` | | @aws-cdk/aws-lambda | Function.​addVersion() | This method will create an AWS::Lambda::Version resource which snapshots the AWS Lambda function *at the time of its creation* and it won't get updated when the function changes. Instead, use `this.currentVersion` to obtain a reference to a version resource that gets automatically recreated when the function configuration (or code) changes. | | @aws-cdk/aws-lambda | FunctionAttributes.​securityGroupId | use `securityGroup` instead | +| @aws-cdk/aws-lambda | FunctionOptions.​architectures | use `architecture` | | @aws-cdk/aws-lambda | FunctionOptions.​securityGroup | This property is deprecated, use securityGroups instead | | @aws-cdk/aws-lambda | LogRetention | use `LogRetention` from ' | | @aws-cdk/aws-lambda | LogRetentionProps | use `LogRetentionProps` from ' | @@ -472,10 +487,10 @@ | @aws-cdk/aws-apigateway | CfnStageV2Props.​routeSettings | moved to package aws-apigatewayv2 | | @aws-cdk/aws-apigateway | CfnStageV2Props.​stageVariables | moved to package aws-apigatewayv2 | | @aws-cdk/aws-apigateway | CfnStageV2Props.​tags | moved to package aws-apigatewayv2 | -| @aws-cdk/aws-apigateway | EmptyModel | You should use | -| @aws-cdk/aws-apigateway | EmptyModel.​modelId | You should use | -| @aws-cdk/aws-apigateway | ErrorModel | You should use | -| @aws-cdk/aws-apigateway | ErrorModel.​modelId | You should use | +| @aws-cdk/aws-apigateway | EmptyModel | You should use Model.EMPTY_MODEL | +| @aws-cdk/aws-apigateway | EmptyModel.​modelId | You should use Model.EMPTY_MODEL | +| @aws-cdk/aws-apigateway | ErrorModel | You should use Model.ERROR_MODEL | +| @aws-cdk/aws-apigateway | ErrorModel.​modelId | You should use Model.ERROR_MODEL | | @aws-cdk/aws-apigateway | IResource.​restApi | Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. | | @aws-cdk/aws-apigateway | LambdaRestApiProps.​options | the `LambdaRestApiProps` now extends `RestApiProps`, so all options are just available here. Note that the options specified in `options` will be overridden by any props specified at the root level. | | @aws-cdk/aws-apigateway | Method.​restApi | Throws an error if this Resource is not associated with an instance of `RestApi`. Use `api` instead. | @@ -489,6 +504,7 @@ | @aws-cdk/aws-certificatemanager | CertificateProps.​validationDomains | use `validation` instead. | | @aws-cdk/aws-certificatemanager | CertificateProps.​validationMethod | use `validation` instead. | | @aws-cdk/aws-route53 | AddressRecordTarget | Use RecordTarget | +| @aws-cdk/custom-resources | AwsSdkCall.​outputPath | use outputPaths instead | | @aws-cdk/custom-resources | Provider.​bind() | use `provider.serviceToken` instead | | @aws-cdk/aws-cloudformation | CloudFormationCapabilities | use `core.CfnCapabilities` | | @aws-cdk/aws-cloudformation | CloudFormationCapabilities.​NONE | use `core.CfnCapabilities` | @@ -520,6 +536,8 @@ | @aws-cdk/aws-sns | NumericConditions.​whitelist | use `allowlist` | | @aws-cdk/aws-sns | StringConditions.​blacklist | use `denylist` | | @aws-cdk/aws-sns | StringConditions.​whitelist | use `allowlist` | +| @aws-cdk/aws-cognito | StandardAttributes.​emailVerified | this is not a standard attribute and was incorrectly added to the CDK. It is a Cognito built-in attribute and cannot be controlled as part of user pool creation. | +| @aws-cdk/aws-cognito | StandardAttributes.​phoneNumberVerified | this is not a standard attribute and was incorrectly added to the CDK. It is a Cognito built-in attribute and cannot be controlled as part of user pool creation. | | @aws-cdk/aws-elasticloadbalancingv2 | AddFixedResponseProps | Use `ApplicationListener.addAction` instead. | | @aws-cdk/aws-elasticloadbalancingv2 | AddRedirectResponseProps | Use `ApplicationListener.addAction` instead. | | @aws-cdk/aws-elasticloadbalancingv2 | AddRuleProps.​hostHeader | Use `conditions` instead. | @@ -573,6 +591,11 @@ | @aws-cdk/aws-elasticloadbalancingv2 | TargetGroupAttributes.​defaultPort | This property is unused and the wrong type. No need to use it. | | @aws-cdk/aws-elasticloadbalancingv2 | TargetGroupImportProps | Use TargetGroupAttributes instead | | @aws-cdk/aws-apigatewayv2 | IHttpApi.​httpApiId | use apiId instead | +| @aws-cdk/aws-appmesh | Protocol | not for use outside package | +| @aws-cdk/aws-appmesh | Protocol.​HTTP | not for use outside package | +| @aws-cdk/aws-appmesh | Protocol.​TCP | not for use outside package | +| @aws-cdk/aws-appmesh | Protocol.​HTTP2 | not for use outside package | +| @aws-cdk/aws-appmesh | Protocol.​GRPC | not for use outside package | | @aws-cdk/aws-dynamodb | ITable.​metricSystemErrors() | use `metricSystemErrorsForOperations` | | @aws-cdk/aws-dynamodb | Table.​grantListStreams() | Use {@link #grantTableListStreams} for more granular permission | | @aws-cdk/aws-dynamodb | Table.​metricSystemErrors() | use `metricSystemErrorsForOperations`. | @@ -594,6 +617,40 @@ | @aws-cdk/aws-rds | DatabaseInstanceEngine.​oracleSe() | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 | | @aws-cdk/aws-rds | DatabaseInstanceEngine.​oracleSe1() | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 | | @aws-cdk/aws-rds | DatabaseInstanceNewProps.​vpcPlacement | use `vpcSubnets` | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​17 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​24 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​28 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​31 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​32 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​34 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​0_​35 | MariaDB 10.0 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​14 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​19 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​23 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​26 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​31 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MariaDbEngineVersion.​VER_​10_​1_​34 | MariaDB 10.1 will reach end of life on May 18, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5_​46 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5_​53 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5_​57 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5_​59 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​5_​61 | MySQL 5.5 will reach end of life on May 25, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​34 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​35 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​37 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​39 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​40 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​41 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​43 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​44 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​46 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​48 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​49 | MySQL 5.6 will reach end of life on August 3, 2021 | +| @aws-cdk/aws-rds | MysqlEngineVersion.​VER_​5_​6_​51 | MySQL 5.6 will reach end of life on August 3, 2021 | | @aws-cdk/aws-rds | OracleLegacyEngineVersion | instances can no longer be created with these engine versions. See https://forums.aws.amazon.com/ann.jspa?annID=7341 | | @aws-cdk/aws-rds | OracleLegacyEngineVersion.​VER_​11_​2 | instances can no longer be created with these engine versions. See https://forums.aws.amazon.com/ann.jspa?annID=7341 | | @aws-cdk/aws-rds | OracleLegacyEngineVersion.​VER_​11_​2_​0_​2_​V2 | instances can no longer be created with these engine versions. See https://forums.aws.amazon.com/ann.jspa?annID=7341 | @@ -641,6 +698,8 @@ | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​21 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​22 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​23 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​24 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​25 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​4 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​6 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​5_​7 | PostgreSQL 9.5 will reach end of life on February 16, 2021 | @@ -657,12 +716,17 @@ | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​18 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​19 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​2 | PostgreSQL 9.6 will reach end of life in November 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​20 | PostgreSQL 9.6 will reach end of life in November 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​21 | PostgreSQL 9.6 will reach end of life in November 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​22 | PostgreSQL 9.6 will reach end of life in November 2021 | +| @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​23 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​3 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​5 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​6 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​8 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | PostgresEngineVersion.​VER_​9_​6_​9 | PostgreSQL 9.6 will reach end of life in November 2021 | | @aws-cdk/aws-rds | SnapshotCredentials.​fromGeneratedPassword() | use `fromGeneratedSecret()` for new Clusters and Instances. Note that switching from `fromGeneratedPassword()` to `fromGeneratedSecret()` for already deployed Clusters or Instances will update their master password. | +| @aws-cdk/aws-rds | SqlServerEngineVersion.​VER_​15_​00_​4043_​23_​V1 | This version is erroneous. You might be looking for {@link SqlServerEngineVersion.VER_15_00_4073_23_V1}, instead. | | @aws-cdk/aws-autoscaling | BlockDevice.​mappingEnabled | use `BlockDeviceVolume.noDevice()` as the volume to supress a mapping. | | @aws-cdk/aws-autoscaling | CommonAutoScalingGroupProps.​notificationsTopic | use `notifications` | | @aws-cdk/aws-autoscaling | CommonAutoScalingGroupProps.​replacingUpdateMinSuccessfulInstancesPercent | Use `signals` instead | @@ -683,17 +747,23 @@ | @aws-cdk/aws-autoscaling | UpdateType.​REPLACING_​UPDATE | Use UpdatePolicy instead | | @aws-cdk/aws-autoscaling | UpdateType.​ROLLING_​UPDATE | Use UpdatePolicy instead | | @aws-cdk/aws-elasticloadbalancing | LoadBalancerListener.​sslCertificateId | use sslCertificateArn instead | +| @aws-cdk/aws-ecs | AddAutoScalingGroupCapacityOptions.​taskDrainTime | The lifecycle draining hook is not configured if using the EC2 Capacity Provider. Enable managed termination protection instead. | | @aws-cdk/aws-ecs | BaseService.​configureAwsVpcNetworking() | use configureAwsVpcNetworkingWithSecurityGroups instead. | -| @aws-cdk/aws-ecs | Ec2ServiceProps.​propagateTaskTagsFrom | Use `propagateTags` instead. | +| @aws-cdk/aws-ecs | BaseServiceOptions.​propagateTaskTagsFrom | Use `propagateTags` instead. | +| @aws-cdk/aws-ecs | Cluster.​addAutoScalingGroup() | Use {@link Cluster.addAsgCapacityProvider} instead. | +| @aws-cdk/aws-ecs | Cluster.​addCapacity() | Use {@link Cluster.addAsgCapacityProvider} instead. | +| @aws-cdk/aws-ecs | Cluster.​addCapacityProvider() | Use {@link enableFargateCapacityProviders} instead. | +| @aws-cdk/aws-ecs | ClusterProps.​capacityProviders | Use {@link ClusterProps.enableFargateCapacityProviders} instead. | | @aws-cdk/aws-ecs | Ec2ServiceProps.​securityGroup | use securityGroups instead. | | @aws-cdk/aws-ecs | EcsOptimizedAmi | see {@link EcsOptimizedImage#amazonLinux}, {@link EcsOptimizedImage#amazonLinux} and {@link EcsOptimizedImage#windows} | | @aws-cdk/aws-ecs | EcsOptimizedAmi.​getImage() | see {@link EcsOptimizedImage#amazonLinux}, {@link EcsOptimizedImage#amazonLinux} and {@link EcsOptimizedImage#windows} | | @aws-cdk/aws-ecs | EcsOptimizedAmiProps | see {@link EcsOptimizedImage} | +| @aws-cdk/aws-ecs | EcsOptimizedAmiProps.​cachedInContext | see {@link EcsOptimizedImage} | | @aws-cdk/aws-ecs | EcsOptimizedAmiProps.​generation | see {@link EcsOptimizedImage} | | @aws-cdk/aws-ecs | EcsOptimizedAmiProps.​hardwareType | see {@link EcsOptimizedImage} | | @aws-cdk/aws-ecs | EcsOptimizedAmiProps.​windowsVersion | see {@link EcsOptimizedImage} | -| @aws-cdk/aws-ecs | FargateServiceProps.​propagateTaskTagsFrom | Use `propagateTags` instead. | | @aws-cdk/aws-ecs | FargateServiceProps.​securityGroup | use securityGroups instead. | +| @aws-cdk/aws-ecs | SplunkLogDriverProps.​token | Use {@link SplunkLogDriverProps.secretToken} instead. | | @aws-cdk/aws-cloudfront | AliasConfiguration | see {@link CloudFrontWebDistributionProps#viewerCertificate} with {@link ViewerCertificate#acmCertificate} | | @aws-cdk/aws-cloudfront | AliasConfiguration.​acmCertRef | see {@link CloudFrontWebDistributionProps#viewerCertificate} with {@link ViewerCertificate#acmCertificate} | | @aws-cdk/aws-cloudfront | AliasConfiguration.​names | see {@link CloudFrontWebDistributionProps#viewerCertificate} with {@link ViewerCertificate#acmCertificate} | @@ -757,6 +827,8 @@ | @aws-cdk/aws-stepfunctions | Task.​next() | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | | @aws-cdk/aws-stepfunctions | Task.​toStateJson() | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | | @aws-cdk/aws-stepfunctions | Task.​whenBoundToGraph() | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | +| @aws-cdk/aws-stepfunctions | TaskInput.​fromContextAt() | Use `fromJsonPathAt`. | +| @aws-cdk/aws-stepfunctions | TaskInput.​fromDataAt() | Use `fromJsonPathAt`. | | @aws-cdk/aws-stepfunctions | TaskProps | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | | @aws-cdk/aws-stepfunctions | TaskProps.​task | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | | @aws-cdk/aws-stepfunctions | TaskProps.​comment | replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) | @@ -861,5 +933,193 @@ | @aws-cdk/aws-stepfunctions-tasks | StartExecutionProps.​input | use 'StepFunctionsStartExecution' | | @aws-cdk/aws-stepfunctions-tasks | StartExecutionProps.​integrationPattern | use 'StepFunctionsStartExecution' | | @aws-cdk/aws-stepfunctions-tasks | StartExecutionProps.​name | use 'StepFunctionsStartExecution' | +| @aws-cdk/pipelines | AddManualApprovalOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddManualApprovalOptions.​actionName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddManualApprovalOptions.​runOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStackOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStackOptions.​executeRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStackOptions.​runOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStageOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStageOptions.​extraRunOrderSpace | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AddStageOptions.​manualApprovals | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AdditionalArtifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AdditionalArtifact.​artifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AdditionalArtifact.​directory | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand.​assetId | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand.​assetManifestPath | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand.​assetPublishingRoleArn | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand.​assetSelector | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | AssetPublishingCommand.​assetType | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | BaseStageOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | BaseStageOptions.​confirmBroadeningPermissions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | BaseStageOptions.​securityNotificationTopic | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​codePipeline | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​addApplicationStage() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​addStage() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​stackOutput() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​stage() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipeline.​validate() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​cloudAssemblyArtifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​assetBuildSpec | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​assetPreInstallCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​cdkCliVersion | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​codePipeline | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​crossAccountKeys | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​dockerCredentials | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​enableKeyRotation | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​pipelineName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​selfMutating | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​selfMutationBuildSpec | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​singlePublisherPerType | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​sourceAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​subnetSelection | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​supportDockerAssets | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​synthAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkPipelineProps.​vpc | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStackActionFromArtifactOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStackActionFromArtifactOptions.​stackName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​addActions() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​addApplication() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​addManualApprovalAction() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​addStackArtifactDeployment() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​deploysStack() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStage.​nextSequentialRunOrder() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​cloudAssemblyArtifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​host | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​pipelineStage | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​stageName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​confirmBroadeningPermissions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | CdkStageProps.​securityNotificationTopic | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​actionProperties | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​dependencyStackArtifactIds | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​executeRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​prepareRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​stackName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​stackArtifactId | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​fromStackArtifact() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​bind() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackAction.​onStateChange() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​cloudAssemblyInput | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​baseActionName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​changeSetName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​executeRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​output | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​outputFileName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionOptions.​prepareRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​actionRole | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​stackName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​templatePath | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​cloudFormationExecutionRole | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​dependencyStackArtifactIds | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​region | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​stackArtifactId | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | DeployCdkStackActionProps.​templateConfigurationPath | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions.​cloudAssemblyInput | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions.​executeRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions.​output | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions.​outputFileName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | FromStackArtifactOptions.​prepareRunOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | IStageHost | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | IStageHost.​publishAsset() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | IStageHost.​stackOutputArtifact() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsAction.​actionProperties | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsAction.​addPublishCommand() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsAction.​bind() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsAction.​onStateChange() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​actionName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​assetType | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​cloudAssemblyInput | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​buildSpec | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​cdkCliVersion | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​createBuildspecFile | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​dependable | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​preInstallCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​projectName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​role | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​subnetSelection | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | PublishAssetsActionProps.​vpc | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction.​actionProperties | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction.​grantPrincipal | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction.​project | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction.​bind() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptAction.​onStateChange() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​actionName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​commands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​additionalArtifacts | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​bashOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​environment | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​environmentVariables | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​rolePolicyStatements | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​runOrder | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​securityGroups | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​subnetSelection | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​useOutputs | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | ShellScriptActionProps.​vpc | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​actionProperties | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​grantPrincipal | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​project | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​standardNpmSynth() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​standardYarnSynth() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​bind() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthAction.​onStateChange() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthActionProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthActionProps.​synthCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | | @aws-cdk/pipelines | SimpleSynthActionProps.​buildCommand | Use `buildCommands` instead | +| @aws-cdk/pipelines | SimpleSynthActionProps.​buildCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | | @aws-cdk/pipelines | SimpleSynthActionProps.​installCommand | Use `installCommands` instead | +| @aws-cdk/pipelines | SimpleSynthActionProps.​installCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthActionProps.​testCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​cloudAssemblyArtifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​sourceArtifact | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​actionName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​additionalArtifacts | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​buildSpec | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​copyEnvironmentVariables | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​environment | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​environmentVariables | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​projectName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​rolePolicyStatements | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​subdirectory | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​subnetSelection | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | SimpleSynthOptions.​vpc | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StackOutput | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StackOutput.​artifactFile | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StackOutput.​outputName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardNpmSynthOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardNpmSynthOptions.​buildCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardNpmSynthOptions.​installCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardNpmSynthOptions.​synthCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardNpmSynthOptions.​testCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardYarnSynthOptions | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardYarnSynthOptions.​buildCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardYarnSynthOptions.​installCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardYarnSynthOptions.​synthCommand | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | StandardYarnSynthOptions.​testCommands | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineAction | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineAction.​actionProperties | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineAction.​bind() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineAction.​onStateChange() | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​cloudAssemblyInput | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​pipelineStackHierarchicalId | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​buildSpec | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​cdkCliVersion | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​dockerCredentials | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​pipelineStackName | Use `pipelineStackHierarchicalId` instead. | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​privileged | This class is part of the old API. Use the API based on the `CodePipeline` class instead | +| @aws-cdk/pipelines | UpdatePipelineActionProps.​projectName | This class is part of the old API. Use the API based on the `CodePipeline` class instead | diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 6def9776b6dc9..6b5d57a000a4e 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -77,4 +77,12 @@ strengthened:@aws-cdk/aws-lambda-event-sources.ManagedKafkaEventSourceProps # Remove IO2 from autoscaling EbsDeviceVolumeType. This value is not supported # at the moment and was not supported in the past. -removed:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType.IO2 \ No newline at end of file +removed:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType.IO2 + +# Remove autoTerminationPolicy from stepfunctions-tasks EmrCreateClusterProps. This value is not supported by stepfunctions at the moment and was not supported in the past. +removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateCluster.AutoTerminationPolicyProperty +removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateClusterProps.autoTerminationPolicy + +# Changed property securityGroupId to optional because either securityGroupId or +# securityGroupName is required. Therefore securityGroupId is no longer mandatory. +weakened:@aws-cdk/cloud-assembly-schema.SecurityGroupContextQuery diff --git a/build.sh b/build.sh index 99719cd328874..64ecc043faf45 100755 --- a/build.sh +++ b/build.sh @@ -6,11 +6,10 @@ runtarget="build" run_tests="true" check_prereqs="true" check_compat="true" -extract_snippets="false" while [[ "${1:-}" != "" ]]; do case $1 in -h|--help) - echo "Usage: build.sh [--no-bail] [--force|-f] [--skip-test] [--skip-prereqs] [--skip-compat] [--extract]" + echo "Usage: build.sh [--no-bail] [--force|-f] [--skip-test] [--skip-prereqs] [--skip-compat]" exit 1 ;; --no-bail) @@ -28,9 +27,6 @@ while [[ "${1:-}" != "" ]]; do --skip-compat) check_compat="false" ;; - --extract) - extract_snippets="true" - ;; *) echo "Unrecognized parameter: $1" exit 1 @@ -81,9 +77,6 @@ trap "rm -rf $MERKLE_BUILD_CACHE" EXIT if [ "$run_tests" == "true" ]; then runtarget="$runtarget+test" fi -if [ "$extract_snippets" == "true" ]; then - runtarget="$runtarget+extract" -fi echo "=============================================================================================" echo "building..." @@ -93,4 +86,11 @@ if [ "$check_compat" == "true" ]; then /bin/bash scripts/check-api-compatibility.sh fi +# Create the release notes for the current version. These are ephemeral and not saved in source. +# Skip this step for a "bump candidate" build, where a new, fake version number has been created +# without any corresponding changelog entries. +if ! ${BUMP_CANDIDATE:-false}; then + node ./scripts/create-release-notes.js +fi + touch $BUILD_INDICATOR diff --git a/buildspec-pr.yaml b/buildspec-pr.yaml index 647b78849b3e8..ade1f4a9be0c6 100644 --- a/buildspec-pr.yaml +++ b/buildspec-pr.yaml @@ -16,8 +16,14 @@ phases: # Packing the mono-libraries (monocdk & aws-cdk-lib) can cause # memory errors. Increasing this value allows our build to more consistently succeed - (command -v sysctl || yum install -y procps-ng) && /sbin/sysctl -w vm.max_map_count=2251954 + pre_build: + commands: + - /bin/bash ./scripts/cache-load.sh build: commands: - - /bin/bash ./build.sh --extract - - /bin/bash ./scripts/transform.sh --extract + - /bin/bash ./build.sh + - /bin/bash ./scripts/transform.sh + # After compilation, run Rosetta (using the cache if available). + # This will print errors, and fail the build if there are compilation errors in any packages marked as 'strict'. + - /bin/bash ./scripts/run-rosetta.sh - git diff-index --exit-code --ignore-space-at-eol --stat HEAD diff --git a/buildspec.yaml b/buildspec.yaml index 0fc990a17c805..94a75e2357f78 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -16,6 +16,9 @@ phases: # Packing the mono-libraries (monocdk & aws-cdk-lib) can cause # memory errors. Increasing this value allows our build to more consistently succeed - /sbin/sysctl -w vm.max_map_count=2251954 + pre_build: + commands: + - /bin/bash ./scripts/cache-load.sh build: commands: - 'if ${BUMP_CANDIDATE:-false}; then /bin/bash ./scripts/bump-candidate.sh; fi' @@ -25,6 +28,7 @@ phases: post_build: commands: - "[ -f .BUILD_COMPLETED ] && /bin/bash ./pack.sh" + - /bin/bash ./scripts/cache-store.sh artifacts: files: - "**/*" diff --git a/deprecated_apis.txt b/deprecated_apis.txt new file mode 100644 index 0000000000000..cfda6c5068605 --- /dev/null +++ b/deprecated_apis.txt @@ -0,0 +1,1121 @@ +@aws-cdk/core.AppProps#runtimeInfo +@aws-cdk/core.Arn#parse +@aws-cdk/core.ArnComponents#sep +@aws-cdk/core.AssetHashType#BUNDLE +@aws-cdk/core.AssetStaging#sourceHash +@aws-cdk/core.AssetStaging#stagedPath +@aws-cdk/core.BundlingDockerImage +@aws-cdk/core.BundlingDockerImage#image +@aws-cdk/core.BundlingDockerImage#fromAsset +@aws-cdk/core.BundlingDockerImage#fromRegistry +@aws-cdk/core.BundlingDockerImage#cp +@aws-cdk/core.BundlingDockerImage#run +@aws-cdk/core.BundlingDockerImage#toJSON +@aws-cdk/core.CfnInclude +@aws-cdk/core.CfnInclude#template +@aws-cdk/core.CfnIncludeProps +@aws-cdk/core.CfnIncludeProps#template +@aws-cdk/core.ConstructNode#metadata +@aws-cdk/core.ConstructNode#uniqueId +@aws-cdk/core.ConstructNode#prepare +@aws-cdk/core.ConstructNode#synth +@aws-cdk/core.ConstructNode#addError +@aws-cdk/core.ConstructNode#addInfo +@aws-cdk/core.ConstructNode#addWarning +@aws-cdk/core.ConstructNode#applyAspect +@aws-cdk/core.CustomResourceProviderRuntime#NODEJS_12 +@aws-cdk/core.DefaultStackSynthesizerProps#fileAssetKeyArnExportName +@aws-cdk/core.DockerImageAssetSource#repositoryName +@aws-cdk/core.Duration#toISOString +@aws-cdk/core.FileAssetLocation#kmsKeyArn +@aws-cdk/core.FileAssetLocation#s3Url +@aws-cdk/core.ITemplateOptions#transform +@aws-cdk/core.Lazy#anyValue +@aws-cdk/core.Lazy#listValue +@aws-cdk/core.Lazy#numberValue +@aws-cdk/core.Lazy#stringValue +@aws-cdk/core.Size#pebibyte +@aws-cdk/core.Stack#parentStack +@aws-cdk/core.Stack#addDockerImageAsset +@aws-cdk/core.Stack#addFileAsset +@aws-cdk/core.Stack#parseArn +@aws-cdk/core.Stack#prepareCrossReference +@aws-cdk/core.Stack#reportMissingContext +@aws-cdk/core.SynthesisOptions +@aws-cdk/core.SynthesisOptions#outdir +@aws-cdk/core.SynthesisOptions#skipValidation +@aws-cdk/core.SynthesisOptions#validateOnSynthesis +@aws-cdk/core.Tag#add +@aws-cdk/core.Tag#remove +@aws-cdk/cloud-assembly-schema.ContainerImageAssetMetadataEntry#imageNameParameter +@aws-cdk/cloud-assembly-schema.Manifest#load +@aws-cdk/cloud-assembly-schema.Manifest#save +@aws-cdk/cx-api.AssemblyBuildOptions#runtimeInfo +@aws-cdk/cx-api.CloudAssembly#getStack +@aws-cdk/cx-api.CloudFormationStackArtifact#name +@aws-cdk/cx-api.MetadataEntry +@aws-cdk/cx-api.MissingContext +@aws-cdk/cx-api.MissingContext#key +@aws-cdk/cx-api.MissingContext#props +@aws-cdk/cx-api.MissingContext#provider +@aws-cdk/cx-api.RuntimeInfo +constructs.Construct#onValidate +constructs.Node#uniqueId +@aws-cdk/assets.CopyOptions +@aws-cdk/assets.CopyOptions#exclude +@aws-cdk/assets.CopyOptions#follow +@aws-cdk/assets.CopyOptions#ignoreMode +@aws-cdk/assets.FingerprintOptions +@aws-cdk/assets.FingerprintOptions#extraHash +@aws-cdk/assets.FollowMode +@aws-cdk/assets.FollowMode#NEVER +@aws-cdk/assets.FollowMode#ALWAYS +@aws-cdk/assets.FollowMode#EXTERNAL +@aws-cdk/assets.FollowMode#BLOCK_EXTERNAL +@aws-cdk/assets.IAsset +@aws-cdk/assets.IAsset#sourceHash +@aws-cdk/assets.Staging +@aws-cdk/assets.StagingProps +@aws-cdk/assets.StagingProps#sourcePath +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_ANDROID_JAVA8_24_4_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_ANDROID_JAVA8_26_1_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_BASE +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_DOCKER_17_09_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_DOCKER_18_09_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_DOTNET_CORE_1_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_DOTNET_CORE_2_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_DOTNET_CORE_2_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_GOLANG_1_10 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_GOLANG_1_11 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_NODEJS_10_1_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_NODEJS_10_14_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_NODEJS_6_3_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_NODEJS_8_11_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_OPEN_JDK_11 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_OPEN_JDK_8 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_OPEN_JDK_9 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PHP_5_6 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PHP_7_0 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PHP_7_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_2_7_12 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_3_3_6 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_3_4_5 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_3_5_2 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_3_6_5 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_PYTHON_3_7_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_RUBY_2_2_5 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_RUBY_2_3_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_RUBY_2_5_1 +@aws-cdk/aws-codebuild.LinuxBuildImage#UBUNTU_14_04_RUBY_2_5_3 +@aws-cdk/aws-codebuild.WindowsBuildImage#WIN_SERVER_CORE_2016_BASE +@aws-cdk/aws-cloudwatch.CommonMetricOptions#dimensions +@aws-cdk/aws-cloudwatch.CreateAlarmOptions#period +@aws-cdk/aws-cloudwatch.CreateAlarmOptions#statistic +@aws-cdk/aws-cloudwatch.IMetric#toAlarmConfig +@aws-cdk/aws-cloudwatch.IMetric#toGraphConfig +@aws-cdk/aws-cloudwatch.MathExpression#toAlarmConfig +@aws-cdk/aws-cloudwatch.MathExpression#toGraphConfig +@aws-cdk/aws-cloudwatch.Metric#toAlarmConfig +@aws-cdk/aws-cloudwatch.Metric#toGraphConfig +@aws-cdk/aws-cloudwatch.MetricAlarmConfig +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#metricName +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#namespace +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#period +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#dimensions +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#extendedStatistic +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#statistic +@aws-cdk/aws-cloudwatch.MetricAlarmConfig#unit +@aws-cdk/aws-cloudwatch.MetricGraphConfig +@aws-cdk/aws-cloudwatch.MetricGraphConfig#metricName +@aws-cdk/aws-cloudwatch.MetricGraphConfig#namespace +@aws-cdk/aws-cloudwatch.MetricGraphConfig#period +@aws-cdk/aws-cloudwatch.MetricGraphConfig#renderingProperties +@aws-cdk/aws-cloudwatch.MetricGraphConfig#color +@aws-cdk/aws-cloudwatch.MetricGraphConfig#dimensions +@aws-cdk/aws-cloudwatch.MetricGraphConfig#label +@aws-cdk/aws-cloudwatch.MetricGraphConfig#statistic +@aws-cdk/aws-cloudwatch.MetricGraphConfig#unit +@aws-cdk/aws-cloudwatch.MetricRenderingProperties +@aws-cdk/aws-cloudwatch.MetricRenderingProperties#period +@aws-cdk/aws-cloudwatch.MetricRenderingProperties#color +@aws-cdk/aws-cloudwatch.MetricRenderingProperties#label +@aws-cdk/aws-cloudwatch.MetricRenderingProperties#stat +@aws-cdk/aws-iam.Anyone +@aws-cdk/aws-iam.IPrincipal#addToPolicy +@aws-cdk/aws-iam.RoleProps#externalId +@aws-cdk/aws-events.EventBus#grantPutEvents +@aws-cdk/aws-events.RuleTargetConfig#id +@aws-cdk/aws-ec2.ClientVpnAuthorizationRuleProps#clientVpnEndoint +@aws-cdk/aws-ec2.ClientVpnRouteProps#clientVpnEndoint +@aws-cdk/aws-ec2.InterfaceVpcEndpoint#securityGroupId +@aws-cdk/aws-ec2.InterfaceVpcEndpointAttributes#securityGroupId +@aws-cdk/aws-ec2.MachineImage#fromSSMParameter +@aws-cdk/aws-ec2.NatInstanceProps#allowAllTraffic +@aws-cdk/aws-ec2.SecurityGroup#securityGroupName +@aws-cdk/aws-ec2.SubnetSelection#subnetName +@aws-cdk/aws-ec2.SubnetType#ISOLATED +@aws-cdk/aws-ec2.SubnetType#PRIVATE +@aws-cdk/aws-ec2.Vpc#natDependencies +@aws-cdk/aws-ec2.Vpc#addDynamoDbEndpoint +@aws-cdk/aws-ec2.Vpc#addS3Endpoint +@aws-cdk/aws-ec2.VpcEndpointService#whitelistedPrincipals +@aws-cdk/aws-ec2.VpcEndpointServiceProps#vpcEndpointServiceName +@aws-cdk/aws-ec2.VpcEndpointServiceProps#whitelistedPrincipals +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2016_GERMAL_FULL_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_R2_SP1_PORTUGESE_BRAZIL_64BIT_CORE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2016_PORTUGESE_PORTUGAL_FULL_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_R2_RTM_PORTUGESE_BRAZIL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_R2_RTM_PORTUGESE_PORTUGAL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2016_PORTUGESE_BRAZIL_FULL_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_SP2_PORTUGESE_BRAZIL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_RTM_PORTUGESE_BRAZIL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2008_R2_SP1_PORTUGESE_BRAZIL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2008_SP2_PORTUGESE_BRAZIL_32BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2012_RTM_PORTUGESE_PORTUGAL_64BIT_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2019_PORTUGESE_BRAZIL_FULL_BASE +@aws-cdk/aws-ec2.WindowsVersion#WINDOWS_SERVER_2019_PORTUGESE_PORTUGAL_FULL_BASE +@aws-cdk/aws-kms.KeyProps#trustAccountIdentities +@aws-cdk/aws-s3-assets.Asset#s3Url +@aws-cdk/aws-s3-assets.Asset#sourceHash +@aws-cdk/aws-s3-assets.AssetOptions#sourceHash +@aws-cdk/aws-ecr-assets.DockerImageAsset#sourceHash +@aws-cdk/aws-ecr-assets.DockerImageAssetOptions#repositoryName +@aws-cdk/aws-ecr-assets.TarballImageAsset#sourceHash +@aws-cdk/aws-secretsmanager.AttachedSecretOptions +@aws-cdk/aws-secretsmanager.AttachedSecretOptions#target +@aws-cdk/aws-secretsmanager.AttachmentTargetType#INSTANCE +@aws-cdk/aws-secretsmanager.AttachmentTargetType#CLUSTER +@aws-cdk/aws-secretsmanager.Secret#fromSecretArn +@aws-cdk/aws-secretsmanager.Secret#fromSecretName +@aws-cdk/aws-secretsmanager.Secret#addTargetAttachment +@aws-cdk/aws-secretsmanager.SecretAttributes#secretArn +@aws-cdk/aws-secretsmanager.SecretRotationApplication#applicationId +@aws-cdk/aws-secretsmanager.SecretRotationApplication#semanticVersion +@aws-cdk/aws-lambda.Code#isInline +@aws-cdk/aws-lambda.Code#asset +@aws-cdk/aws-lambda.Code#bucket +@aws-cdk/aws-lambda.Code#cfnParameters +@aws-cdk/aws-lambda.Code#inline +@aws-cdk/aws-lambda.Function#addVersion +@aws-cdk/aws-lambda.FunctionAttributes#securityGroupId +@aws-cdk/aws-lambda.FunctionOptions#architectures +@aws-cdk/aws-lambda.FunctionOptions#securityGroup +@aws-cdk/aws-lambda.LogRetention +@aws-cdk/aws-lambda.LogRetentionProps +@aws-cdk/aws-lambda.Runtime#bundlingDockerImage +@aws-cdk/aws-apigateway.CfnApiMappingV2 +@aws-cdk/aws-apigateway.CfnApiMappingV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnApiMappingV2#cfnProperties +@aws-cdk/aws-apigateway.CfnApiMappingV2#apiId +@aws-cdk/aws-apigateway.CfnApiMappingV2#domainName +@aws-cdk/aws-apigateway.CfnApiMappingV2#stage +@aws-cdk/aws-apigateway.CfnApiMappingV2#apiMappingKey +@aws-cdk/aws-apigateway.CfnApiMappingV2#inspect +@aws-cdk/aws-apigateway.CfnApiMappingV2#renderProperties +@aws-cdk/aws-apigateway.CfnApiMappingV2Props +@aws-cdk/aws-apigateway.CfnApiMappingV2Props#apiId +@aws-cdk/aws-apigateway.CfnApiMappingV2Props#domainName +@aws-cdk/aws-apigateway.CfnApiMappingV2Props#stage +@aws-cdk/aws-apigateway.CfnApiMappingV2Props#apiMappingKey +@aws-cdk/aws-apigateway.CfnApiV2 +@aws-cdk/aws-apigateway.CfnApiV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnApiV2#cfnProperties +@aws-cdk/aws-apigateway.CfnApiV2#tags +@aws-cdk/aws-apigateway.CfnApiV2#body +@aws-cdk/aws-apigateway.CfnApiV2#apiKeySelectionExpression +@aws-cdk/aws-apigateway.CfnApiV2#basePath +@aws-cdk/aws-apigateway.CfnApiV2#bodyS3Location +@aws-cdk/aws-apigateway.CfnApiV2#corsConfiguration +@aws-cdk/aws-apigateway.CfnApiV2#credentialsArn +@aws-cdk/aws-apigateway.CfnApiV2#description +@aws-cdk/aws-apigateway.CfnApiV2#disableSchemaValidation +@aws-cdk/aws-apigateway.CfnApiV2#failOnWarnings +@aws-cdk/aws-apigateway.CfnApiV2#name +@aws-cdk/aws-apigateway.CfnApiV2#protocolType +@aws-cdk/aws-apigateway.CfnApiV2#routeKey +@aws-cdk/aws-apigateway.CfnApiV2#routeSelectionExpression +@aws-cdk/aws-apigateway.CfnApiV2#target +@aws-cdk/aws-apigateway.CfnApiV2#version +@aws-cdk/aws-apigateway.CfnApiV2#inspect +@aws-cdk/aws-apigateway.CfnApiV2#renderProperties +@aws-cdk/aws-apigateway.BodyS3LocationProperty +@aws-cdk/aws-apigateway.BodyS3LocationProperty#bucket +@aws-cdk/aws-apigateway.BodyS3LocationProperty#etag +@aws-cdk/aws-apigateway.BodyS3LocationProperty#key +@aws-cdk/aws-apigateway.BodyS3LocationProperty#version +@aws-cdk/aws-apigateway.CorsProperty +@aws-cdk/aws-apigateway.CorsProperty#allowCredentials +@aws-cdk/aws-apigateway.CorsProperty#allowHeaders +@aws-cdk/aws-apigateway.CorsProperty#allowMethods +@aws-cdk/aws-apigateway.CorsProperty#allowOrigins +@aws-cdk/aws-apigateway.CorsProperty#exposeHeaders +@aws-cdk/aws-apigateway.CorsProperty#maxAge +@aws-cdk/aws-apigateway.CfnApiV2Props +@aws-cdk/aws-apigateway.CfnApiV2Props#apiKeySelectionExpression +@aws-cdk/aws-apigateway.CfnApiV2Props#basePath +@aws-cdk/aws-apigateway.CfnApiV2Props#body +@aws-cdk/aws-apigateway.CfnApiV2Props#bodyS3Location +@aws-cdk/aws-apigateway.CfnApiV2Props#corsConfiguration +@aws-cdk/aws-apigateway.CfnApiV2Props#credentialsArn +@aws-cdk/aws-apigateway.CfnApiV2Props#description +@aws-cdk/aws-apigateway.CfnApiV2Props#disableSchemaValidation +@aws-cdk/aws-apigateway.CfnApiV2Props#failOnWarnings +@aws-cdk/aws-apigateway.CfnApiV2Props#name +@aws-cdk/aws-apigateway.CfnApiV2Props#protocolType +@aws-cdk/aws-apigateway.CfnApiV2Props#routeKey +@aws-cdk/aws-apigateway.CfnApiV2Props#routeSelectionExpression +@aws-cdk/aws-apigateway.CfnApiV2Props#tags +@aws-cdk/aws-apigateway.CfnApiV2Props#target +@aws-cdk/aws-apigateway.CfnApiV2Props#version +@aws-cdk/aws-apigateway.CfnAuthorizerV2 +@aws-cdk/aws-apigateway.CfnAuthorizerV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnAuthorizerV2#cfnProperties +@aws-cdk/aws-apigateway.CfnAuthorizerV2#apiId +@aws-cdk/aws-apigateway.CfnAuthorizerV2#authorizerType +@aws-cdk/aws-apigateway.CfnAuthorizerV2#identitySource +@aws-cdk/aws-apigateway.CfnAuthorizerV2#name +@aws-cdk/aws-apigateway.CfnAuthorizerV2#authorizerCredentialsArn +@aws-cdk/aws-apigateway.CfnAuthorizerV2#authorizerResultTtlInSeconds +@aws-cdk/aws-apigateway.CfnAuthorizerV2#authorizerUri +@aws-cdk/aws-apigateway.CfnAuthorizerV2#identityValidationExpression +@aws-cdk/aws-apigateway.CfnAuthorizerV2#jwtConfiguration +@aws-cdk/aws-apigateway.CfnAuthorizerV2#inspect +@aws-cdk/aws-apigateway.CfnAuthorizerV2#renderProperties +@aws-cdk/aws-apigateway.JWTConfigurationProperty +@aws-cdk/aws-apigateway.JWTConfigurationProperty#audience +@aws-cdk/aws-apigateway.JWTConfigurationProperty#issuer +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#apiId +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#authorizerType +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#identitySource +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#name +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#authorizerCredentialsArn +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#authorizerResultTtlInSeconds +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#authorizerUri +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#identityValidationExpression +@aws-cdk/aws-apigateway.CfnAuthorizerV2Props#jwtConfiguration +@aws-cdk/aws-apigateway.CfnDeploymentV2 +@aws-cdk/aws-apigateway.CfnDeploymentV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnDeploymentV2#cfnProperties +@aws-cdk/aws-apigateway.CfnDeploymentV2#apiId +@aws-cdk/aws-apigateway.CfnDeploymentV2#description +@aws-cdk/aws-apigateway.CfnDeploymentV2#stageName +@aws-cdk/aws-apigateway.CfnDeploymentV2#inspect +@aws-cdk/aws-apigateway.CfnDeploymentV2#renderProperties +@aws-cdk/aws-apigateway.CfnDeploymentV2Props +@aws-cdk/aws-apigateway.CfnDeploymentV2Props#apiId +@aws-cdk/aws-apigateway.CfnDeploymentV2Props#description +@aws-cdk/aws-apigateway.CfnDeploymentV2Props#stageName +@aws-cdk/aws-apigateway.CfnDomainNameV2 +@aws-cdk/aws-apigateway.CfnDomainNameV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnDomainNameV2#attrRegionalDomainName +@aws-cdk/aws-apigateway.CfnDomainNameV2#attrRegionalHostedZoneId +@aws-cdk/aws-apigateway.CfnDomainNameV2#cfnProperties +@aws-cdk/aws-apigateway.CfnDomainNameV2#tags +@aws-cdk/aws-apigateway.CfnDomainNameV2#domainName +@aws-cdk/aws-apigateway.CfnDomainNameV2#domainNameConfigurations +@aws-cdk/aws-apigateway.CfnDomainNameV2#inspect +@aws-cdk/aws-apigateway.CfnDomainNameV2#renderProperties +@aws-cdk/aws-apigateway.DomainNameConfigurationProperty +@aws-cdk/aws-apigateway.DomainNameConfigurationProperty#certificateArn +@aws-cdk/aws-apigateway.DomainNameConfigurationProperty#certificateName +@aws-cdk/aws-apigateway.DomainNameConfigurationProperty#endpointType +@aws-cdk/aws-apigateway.CfnDomainNameV2Props +@aws-cdk/aws-apigateway.CfnDomainNameV2Props#domainName +@aws-cdk/aws-apigateway.CfnDomainNameV2Props#domainNameConfigurations +@aws-cdk/aws-apigateway.CfnDomainNameV2Props#tags +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2 +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#cfnProperties +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#apiId +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#integrationId +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#integrationResponseKey +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#responseParameters +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#responseTemplates +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#contentHandlingStrategy +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#templateSelectionExpression +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#inspect +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2#renderProperties +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#apiId +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#integrationId +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#integrationResponseKey +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#contentHandlingStrategy +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#responseParameters +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#responseTemplates +@aws-cdk/aws-apigateway.CfnIntegrationResponseV2Props#templateSelectionExpression +@aws-cdk/aws-apigateway.CfnIntegrationV2 +@aws-cdk/aws-apigateway.CfnIntegrationV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnIntegrationV2#cfnProperties +@aws-cdk/aws-apigateway.CfnIntegrationV2#apiId +@aws-cdk/aws-apigateway.CfnIntegrationV2#integrationType +@aws-cdk/aws-apigateway.CfnIntegrationV2#requestParameters +@aws-cdk/aws-apigateway.CfnIntegrationV2#requestTemplates +@aws-cdk/aws-apigateway.CfnIntegrationV2#connectionType +@aws-cdk/aws-apigateway.CfnIntegrationV2#contentHandlingStrategy +@aws-cdk/aws-apigateway.CfnIntegrationV2#credentialsArn +@aws-cdk/aws-apigateway.CfnIntegrationV2#description +@aws-cdk/aws-apigateway.CfnIntegrationV2#integrationMethod +@aws-cdk/aws-apigateway.CfnIntegrationV2#integrationUri +@aws-cdk/aws-apigateway.CfnIntegrationV2#passthroughBehavior +@aws-cdk/aws-apigateway.CfnIntegrationV2#payloadFormatVersion +@aws-cdk/aws-apigateway.CfnIntegrationV2#templateSelectionExpression +@aws-cdk/aws-apigateway.CfnIntegrationV2#timeoutInMillis +@aws-cdk/aws-apigateway.CfnIntegrationV2#inspect +@aws-cdk/aws-apigateway.CfnIntegrationV2#renderProperties +@aws-cdk/aws-apigateway.CfnIntegrationV2Props +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#apiId +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#integrationType +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#connectionType +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#contentHandlingStrategy +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#credentialsArn +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#description +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#integrationMethod +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#integrationUri +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#passthroughBehavior +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#payloadFormatVersion +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#requestParameters +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#requestTemplates +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#templateSelectionExpression +@aws-cdk/aws-apigateway.CfnIntegrationV2Props#timeoutInMillis +@aws-cdk/aws-apigateway.CfnModelV2 +@aws-cdk/aws-apigateway.CfnModelV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnModelV2#cfnProperties +@aws-cdk/aws-apigateway.CfnModelV2#apiId +@aws-cdk/aws-apigateway.CfnModelV2#name +@aws-cdk/aws-apigateway.CfnModelV2#schema +@aws-cdk/aws-apigateway.CfnModelV2#contentType +@aws-cdk/aws-apigateway.CfnModelV2#description +@aws-cdk/aws-apigateway.CfnModelV2#inspect +@aws-cdk/aws-apigateway.CfnModelV2#renderProperties +@aws-cdk/aws-apigateway.CfnModelV2Props +@aws-cdk/aws-apigateway.CfnModelV2Props#apiId +@aws-cdk/aws-apigateway.CfnModelV2Props#name +@aws-cdk/aws-apigateway.CfnModelV2Props#schema +@aws-cdk/aws-apigateway.CfnModelV2Props#contentType +@aws-cdk/aws-apigateway.CfnModelV2Props#description +@aws-cdk/aws-apigateway.CfnRouteResponseV2 +@aws-cdk/aws-apigateway.CfnRouteResponseV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnRouteResponseV2#cfnProperties +@aws-cdk/aws-apigateway.CfnRouteResponseV2#apiId +@aws-cdk/aws-apigateway.CfnRouteResponseV2#responseModels +@aws-cdk/aws-apigateway.CfnRouteResponseV2#responseParameters +@aws-cdk/aws-apigateway.CfnRouteResponseV2#routeId +@aws-cdk/aws-apigateway.CfnRouteResponseV2#routeResponseKey +@aws-cdk/aws-apigateway.CfnRouteResponseV2#modelSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteResponseV2#inspect +@aws-cdk/aws-apigateway.CfnRouteResponseV2#renderProperties +@aws-cdk/aws-apigateway.ParameterConstraintsProperty +@aws-cdk/aws-apigateway.ParameterConstraintsProperty#required +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#apiId +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#routeId +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#routeResponseKey +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#modelSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#responseModels +@aws-cdk/aws-apigateway.CfnRouteResponseV2Props#responseParameters +@aws-cdk/aws-apigateway.CfnRouteV2 +@aws-cdk/aws-apigateway.CfnRouteV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnRouteV2#cfnProperties +@aws-cdk/aws-apigateway.CfnRouteV2#apiId +@aws-cdk/aws-apigateway.CfnRouteV2#requestModels +@aws-cdk/aws-apigateway.CfnRouteV2#requestParameters +@aws-cdk/aws-apigateway.CfnRouteV2#routeKey +@aws-cdk/aws-apigateway.CfnRouteV2#apiKeyRequired +@aws-cdk/aws-apigateway.CfnRouteV2#authorizationScopes +@aws-cdk/aws-apigateway.CfnRouteV2#authorizationType +@aws-cdk/aws-apigateway.CfnRouteV2#authorizerId +@aws-cdk/aws-apigateway.CfnRouteV2#modelSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteV2#operationName +@aws-cdk/aws-apigateway.CfnRouteV2#routeResponseSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteV2#target +@aws-cdk/aws-apigateway.CfnRouteV2#inspect +@aws-cdk/aws-apigateway.CfnRouteV2#renderProperties +@aws-cdk/aws-apigateway.ParameterConstraintsProperty +@aws-cdk/aws-apigateway.ParameterConstraintsProperty#required +@aws-cdk/aws-apigateway.CfnRouteV2Props +@aws-cdk/aws-apigateway.CfnRouteV2Props#apiId +@aws-cdk/aws-apigateway.CfnRouteV2Props#routeKey +@aws-cdk/aws-apigateway.CfnRouteV2Props#apiKeyRequired +@aws-cdk/aws-apigateway.CfnRouteV2Props#authorizationScopes +@aws-cdk/aws-apigateway.CfnRouteV2Props#authorizationType +@aws-cdk/aws-apigateway.CfnRouteV2Props#authorizerId +@aws-cdk/aws-apigateway.CfnRouteV2Props#modelSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteV2Props#operationName +@aws-cdk/aws-apigateway.CfnRouteV2Props#requestModels +@aws-cdk/aws-apigateway.CfnRouteV2Props#requestParameters +@aws-cdk/aws-apigateway.CfnRouteV2Props#routeResponseSelectionExpression +@aws-cdk/aws-apigateway.CfnRouteV2Props#target +@aws-cdk/aws-apigateway.CfnStageV2 +@aws-cdk/aws-apigateway.CfnStageV2#CFN_RESOURCE_TYPE_NAME +@aws-cdk/aws-apigateway.CfnStageV2#cfnProperties +@aws-cdk/aws-apigateway.CfnStageV2#tags +@aws-cdk/aws-apigateway.CfnStageV2#apiId +@aws-cdk/aws-apigateway.CfnStageV2#routeSettings +@aws-cdk/aws-apigateway.CfnStageV2#stageName +@aws-cdk/aws-apigateway.CfnStageV2#stageVariables +@aws-cdk/aws-apigateway.CfnStageV2#accessLogSettings +@aws-cdk/aws-apigateway.CfnStageV2#autoDeploy +@aws-cdk/aws-apigateway.CfnStageV2#clientCertificateId +@aws-cdk/aws-apigateway.CfnStageV2#defaultRouteSettings +@aws-cdk/aws-apigateway.CfnStageV2#deploymentId +@aws-cdk/aws-apigateway.CfnStageV2#description +@aws-cdk/aws-apigateway.CfnStageV2#inspect +@aws-cdk/aws-apigateway.CfnStageV2#renderProperties +@aws-cdk/aws-apigateway.AccessLogSettingsProperty +@aws-cdk/aws-apigateway.AccessLogSettingsProperty#destinationArn +@aws-cdk/aws-apigateway.AccessLogSettingsProperty#format +@aws-cdk/aws-apigateway.RouteSettingsProperty +@aws-cdk/aws-apigateway.RouteSettingsProperty#dataTraceEnabled +@aws-cdk/aws-apigateway.RouteSettingsProperty#detailedMetricsEnabled +@aws-cdk/aws-apigateway.RouteSettingsProperty#loggingLevel +@aws-cdk/aws-apigateway.RouteSettingsProperty#throttlingBurstLimit +@aws-cdk/aws-apigateway.RouteSettingsProperty#throttlingRateLimit +@aws-cdk/aws-apigateway.CfnStageV2Props +@aws-cdk/aws-apigateway.CfnStageV2Props#apiId +@aws-cdk/aws-apigateway.CfnStageV2Props#stageName +@aws-cdk/aws-apigateway.CfnStageV2Props#accessLogSettings +@aws-cdk/aws-apigateway.CfnStageV2Props#autoDeploy +@aws-cdk/aws-apigateway.CfnStageV2Props#clientCertificateId +@aws-cdk/aws-apigateway.CfnStageV2Props#defaultRouteSettings +@aws-cdk/aws-apigateway.CfnStageV2Props#deploymentId +@aws-cdk/aws-apigateway.CfnStageV2Props#description +@aws-cdk/aws-apigateway.CfnStageV2Props#routeSettings +@aws-cdk/aws-apigateway.CfnStageV2Props#stageVariables +@aws-cdk/aws-apigateway.CfnStageV2Props#tags +@aws-cdk/aws-apigateway.EmptyModel +@aws-cdk/aws-apigateway.EmptyModel#modelId +@aws-cdk/aws-apigateway.ErrorModel +@aws-cdk/aws-apigateway.ErrorModel#modelId +@aws-cdk/aws-apigateway.IResource#restApi +@aws-cdk/aws-apigateway.LambdaRestApiProps#options +@aws-cdk/aws-apigateway.Method#restApi +@aws-cdk/aws-apigateway.Resource#restApi +@aws-cdk/aws-apigateway.ResourceBase#restApi +@aws-cdk/aws-apigateway.ResourceBase#url +@aws-cdk/aws-apigateway.RestApiBase#configureCloudWatchRole +@aws-cdk/aws-apigateway.RestApiBase#configureDeployment +@aws-cdk/aws-apigateway.RestApiOptions +@aws-cdk/aws-apigateway.UsagePlanProps#apiKey +@aws-cdk/aws-certificatemanager.CertificateProps#validationDomains +@aws-cdk/aws-certificatemanager.CertificateProps#validationMethod +@aws-cdk/aws-route53.AddressRecordTarget +@aws-cdk/custom-resources.AwsSdkCall#outputPath +@aws-cdk/custom-resources.Provider#bind +@aws-cdk/aws-cloudformation.CloudFormationCapabilities +@aws-cdk/aws-cloudformation.CloudFormationCapabilities#NONE +@aws-cdk/aws-cloudformation.CloudFormationCapabilities#ANONYMOUS_IAM +@aws-cdk/aws-cloudformation.CloudFormationCapabilities#NAMED_IAM +@aws-cdk/aws-cloudformation.CloudFormationCapabilities#AUTO_EXPAND +@aws-cdk/aws-cloudformation.CustomResource +@aws-cdk/aws-cloudformation.CustomResourceProps +@aws-cdk/aws-cloudformation.CustomResourceProps#provider +@aws-cdk/aws-cloudformation.CustomResourceProps#properties +@aws-cdk/aws-cloudformation.CustomResourceProps#removalPolicy +@aws-cdk/aws-cloudformation.CustomResourceProps#resourceType +@aws-cdk/aws-cloudformation.CustomResourceProvider +@aws-cdk/aws-cloudformation.CustomResourceProvider#serviceToken +@aws-cdk/aws-cloudformation.CustomResourceProvider#fromLambda +@aws-cdk/aws-cloudformation.CustomResourceProvider#fromTopic +@aws-cdk/aws-cloudformation.CustomResourceProvider#lambda +@aws-cdk/aws-cloudformation.CustomResourceProvider#topic +@aws-cdk/aws-cloudformation.CustomResourceProvider#bind +@aws-cdk/aws-cloudformation.CustomResourceProviderConfig +@aws-cdk/aws-cloudformation.CustomResourceProviderConfig#serviceToken +@aws-cdk/aws-cloudformation.ICustomResourceProvider +@aws-cdk/aws-cloudformation.ICustomResourceProvider#bind +@aws-cdk/aws-cloudformation.NestedStack +@aws-cdk/aws-cloudformation.NestedStackProps +@aws-cdk/aws-cloudformation.NestedStackProps#notifications +@aws-cdk/aws-cloudformation.NestedStackProps#parameters +@aws-cdk/aws-cloudformation.NestedStackProps#timeout +@aws-cdk/aws-sns.NumericConditions#whitelist +@aws-cdk/aws-sns.StringConditions#blacklist +@aws-cdk/aws-sns.StringConditions#whitelist +@aws-cdk/aws-cognito.StandardAttributes#emailVerified +@aws-cdk/aws-cognito.StandardAttributes#phoneNumberVerified +@aws-cdk/aws-elasticloadbalancingv2.AddFixedResponseProps +@aws-cdk/aws-elasticloadbalancingv2.AddRedirectResponseProps +@aws-cdk/aws-elasticloadbalancingv2.AddRuleProps#hostHeader +@aws-cdk/aws-elasticloadbalancingv2.AddRuleProps#pathPattern +@aws-cdk/aws-elasticloadbalancingv2.AddRuleProps#pathPatterns +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener#addCertificateArns +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener#addFixedResponse +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListener#addRedirectResponse +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerAttributes#securityGroupAllowsAllOutbound +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerAttributes#securityGroupId +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerCertificateProps#certificateArns +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerRule#addFixedResponse +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerRule#addRedirectResponse +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerRule#addTargetGroup +@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerRule#setCondition +@aws-cdk/aws-elasticloadbalancingv2.ApplicationTargetGroup#import +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerProps#certificateArns +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerRuleProps#fixedResponse +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerRuleProps#hostHeader +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerRuleProps#pathPattern +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerRuleProps#pathPatterns +@aws-cdk/aws-elasticloadbalancingv2.BaseApplicationListenerRuleProps#redirectResponse +@aws-cdk/aws-elasticloadbalancingv2.ContentType +@aws-cdk/aws-elasticloadbalancingv2.ContentType#TEXT_PLAIN +@aws-cdk/aws-elasticloadbalancingv2.ContentType#TEXT_CSS +@aws-cdk/aws-elasticloadbalancingv2.ContentType#TEXT_HTML +@aws-cdk/aws-elasticloadbalancingv2.ContentType#APPLICATION_JAVASCRIPT +@aws-cdk/aws-elasticloadbalancingv2.ContentType#APPLICATION_JSON +@aws-cdk/aws-elasticloadbalancingv2.FixedResponse +@aws-cdk/aws-elasticloadbalancingv2.FixedResponse#statusCode +@aws-cdk/aws-elasticloadbalancingv2.FixedResponse#contentType +@aws-cdk/aws-elasticloadbalancingv2.FixedResponse#messageBody +@aws-cdk/aws-elasticloadbalancingv2.IApplicationListener#addCertificateArns +@aws-cdk/aws-elasticloadbalancingv2.INetworkListenerCertificateProps +@aws-cdk/aws-elasticloadbalancingv2.InstanceTarget +@aws-cdk/aws-elasticloadbalancingv2.InstanceTarget#attachToApplicationTargetGroup +@aws-cdk/aws-elasticloadbalancingv2.InstanceTarget#attachToNetworkTargetGroup +@aws-cdk/aws-elasticloadbalancingv2.IpTarget +@aws-cdk/aws-elasticloadbalancingv2.IpTarget#attachToApplicationTargetGroup +@aws-cdk/aws-elasticloadbalancingv2.IpTarget#attachToNetworkTargetGroup +@aws-cdk/aws-elasticloadbalancingv2.NetworkLoadBalancer#metricHealthyHostCount +@aws-cdk/aws-elasticloadbalancingv2.NetworkLoadBalancer#metricUnHealthyHostCount +@aws-cdk/aws-elasticloadbalancingv2.NetworkTargetGroup#import +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#statusCode +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#host +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#path +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#port +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#protocol +@aws-cdk/aws-elasticloadbalancingv2.RedirectResponse#query +@aws-cdk/aws-elasticloadbalancingv2.TargetGroupAttributes#defaultPort +@aws-cdk/aws-elasticloadbalancingv2.TargetGroupImportProps +@aws-cdk/aws-apigatewayv2.IHttpApi#httpApiId +@aws-cdk/aws-appmesh.Protocol +@aws-cdk/aws-appmesh.Protocol#HTTP +@aws-cdk/aws-appmesh.Protocol#TCP +@aws-cdk/aws-appmesh.Protocol#HTTP2 +@aws-cdk/aws-appmesh.Protocol#GRPC +@aws-cdk/aws-dynamodb.ITable#metricSystemErrors +@aws-cdk/aws-dynamodb.Table#grantListStreams +@aws-cdk/aws-dynamodb.Table#metricSystemErrors +@aws-cdk/aws-dynamodb.TableOptions#serverSideEncryption +@aws-cdk/aws-rds.Credentials#fromUsername +@aws-cdk/aws-rds.CredentialsFromUsernameOptions +@aws-cdk/aws-rds.CredentialsFromUsernameOptions#password +@aws-cdk/aws-rds.DatabaseInstanceEngine#MARIADB +@aws-cdk/aws-rds.DatabaseInstanceEngine#MYSQL +@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_EE +@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE +@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE1 +@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE2 +@aws-cdk/aws-rds.DatabaseInstanceEngine#POSTGRES +@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_EE +@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_EX +@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_SE +@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_WEB +@aws-cdk/aws-rds.DatabaseInstanceEngine#oracleSe +@aws-cdk/aws-rds.DatabaseInstanceEngine#oracleSe1 +@aws-cdk/aws-rds.DatabaseInstanceNewProps#vpcPlacement +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_17 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_24 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_28 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_31 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_32 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_34 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_0_35 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_14 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_19 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_23 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_26 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_31 +@aws-cdk/aws-rds.MariaDbEngineVersion#VER_10_1_34 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5_46 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5_53 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5_57 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5_59 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_5_61 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_34 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_35 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_37 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_39 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_40 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_41 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_43 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_44 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_46 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_48 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_49 +@aws-cdk/aws-rds.MysqlEngineVersion#VER_5_6_51 +@aws-cdk/aws-rds.OracleLegacyEngineVersion +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_2_V2 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V1 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V10 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V11 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V12 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V13 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V14 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V15 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V16 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V17 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V18 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V19 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V20 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V21 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V22 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V23 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V24 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V25 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V3 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V4 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V5 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V6 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V7 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V8 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#VER_11_2_0_4_V9 +@aws-cdk/aws-rds.OracleLegacyEngineVersion#oracleLegacyFullVersion +@aws-cdk/aws-rds.OracleLegacyEngineVersion#oracleLegacyMajorVersion +@aws-cdk/aws-rds.OracleSe1InstanceEngineProps +@aws-cdk/aws-rds.OracleSe1InstanceEngineProps#version +@aws-cdk/aws-rds.OracleSeInstanceEngineProps +@aws-cdk/aws-rds.OracleSeInstanceEngineProps#version +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_10 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_12 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_13 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_14 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_15 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_16 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_18 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_19 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_2 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_20 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_21 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_22 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_23 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_24 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_25 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_4 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_6 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_7 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_5_9 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_1 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_10 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_11 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_12 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_14 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_15 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_16 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_17 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_18 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_19 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_2 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_20 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_21 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_22 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_23 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_3 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_5 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_6 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_8 +@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_9 +@aws-cdk/aws-rds.SnapshotCredentials#fromGeneratedPassword +@aws-cdk/aws-rds.SqlServerEngineVersion#VER_15_00_4043_23_V1 +@aws-cdk/aws-autoscaling.BlockDevice#mappingEnabled +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#notificationsTopic +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#replacingUpdateMinSuccessfulInstancesPercent +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#resourceSignalCount +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#resourceSignalTimeout +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#rollingUpdateConfiguration +@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#updateType +@aws-cdk/aws-autoscaling.RequestCountScalingProps#targetRequestsPerSecond +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#maxBatchSize +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#minInstancesInService +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#minSuccessfulInstancesPercent +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#pauseTime +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#suspendProcesses +@aws-cdk/aws-autoscaling.RollingUpdateConfiguration#waitOnResourceSignals +@aws-cdk/aws-autoscaling.UpdateType +@aws-cdk/aws-autoscaling.UpdateType#NONE +@aws-cdk/aws-autoscaling.UpdateType#REPLACING_UPDATE +@aws-cdk/aws-autoscaling.UpdateType#ROLLING_UPDATE +@aws-cdk/aws-elasticloadbalancing.LoadBalancerListener#sslCertificateId +@aws-cdk/aws-ecs.AddAutoScalingGroupCapacityOptions#taskDrainTime +@aws-cdk/aws-ecs.BaseService#configureAwsVpcNetworking +@aws-cdk/aws-ecs.BaseServiceOptions#propagateTaskTagsFrom +@aws-cdk/aws-ecs.Cluster#addAutoScalingGroup +@aws-cdk/aws-ecs.Cluster#addCapacity +@aws-cdk/aws-ecs.Cluster#addCapacityProvider +@aws-cdk/aws-ecs.ClusterProps#capacityProviders +@aws-cdk/aws-ecs.Ec2ServiceProps#securityGroup +@aws-cdk/aws-ecs.EcsOptimizedAmi +@aws-cdk/aws-ecs.EcsOptimizedAmi#getImage +@aws-cdk/aws-ecs.EcsOptimizedAmiProps +@aws-cdk/aws-ecs.EcsOptimizedAmiProps#cachedInContext +@aws-cdk/aws-ecs.EcsOptimizedAmiProps#generation +@aws-cdk/aws-ecs.EcsOptimizedAmiProps#hardwareType +@aws-cdk/aws-ecs.EcsOptimizedAmiProps#windowsVersion +@aws-cdk/aws-ecs.FargateServiceProps#securityGroup +@aws-cdk/aws-ecs.SplunkLogDriverProps#token +@aws-cdk/aws-cloudfront.AliasConfiguration +@aws-cdk/aws-cloudfront.AliasConfiguration#acmCertRef +@aws-cdk/aws-cloudfront.AliasConfiguration#names +@aws-cdk/aws-cloudfront.AliasConfiguration#securityPolicy +@aws-cdk/aws-cloudfront.AliasConfiguration#sslMethod +@aws-cdk/aws-cloudfront.Behavior#trustedSigners +@aws-cdk/aws-cloudfront.CloudFrontWebDistribution#domainName +@aws-cdk/aws-cloudfront.CloudFrontWebDistributionProps#aliasConfiguration +@aws-cdk/aws-cloudfront.GeoRestriction#blacklist +@aws-cdk/aws-cloudfront.GeoRestriction#whitelist +@aws-cdk/aws-cloudfront.IDistribution#domainName +@aws-cdk/aws-cloudfront.SourceConfiguration#originHeaders +@aws-cdk/aws-cloudfront.SourceConfiguration#originPath +@aws-cdk/aws-cloudtrail.Trail#onCloudTrailEvent +@aws-cdk/aws-cloudtrail.TrailProps#kmsKey +@aws-cdk/aws-codepipeline-actions.BitBucketSourceAction +@aws-cdk/aws-codepipeline-actions.BitBucketSourceAction#actionProperties +@aws-cdk/aws-codepipeline-actions.BitBucketSourceAction#bind +@aws-cdk/aws-codepipeline-actions.BitBucketSourceAction#onStateChange +@aws-cdk/aws-codepipeline-actions.BitBucketSourceActionProps +@aws-cdk/aws-codepipeline-actions.CloudFormationCreateReplaceChangeSetActionProps#capabilities +@aws-cdk/aws-codepipeline-actions.CloudFormationCreateUpdateStackActionProps#capabilities +@aws-cdk/aws-codepipeline-actions.CloudFormationDeleteStackActionProps#capabilities +@aws-cdk/aws-events-targets.EcsTask#securityGroup +@aws-cdk/aws-events-targets.EcsTaskProps#securityGroup +@aws-cdk/aws-stepfunctions.Context +@aws-cdk/aws-stepfunctions.Context#entireContext +@aws-cdk/aws-stepfunctions.Context#taskToken +@aws-cdk/aws-stepfunctions.Context#numberAt +@aws-cdk/aws-stepfunctions.Context#stringAt +@aws-cdk/aws-stepfunctions.Data +@aws-cdk/aws-stepfunctions.Data#entirePayload +@aws-cdk/aws-stepfunctions.Data#isJsonPathString +@aws-cdk/aws-stepfunctions.Data#listAt +@aws-cdk/aws-stepfunctions.Data#numberAt +@aws-cdk/aws-stepfunctions.Data#stringAt +@aws-cdk/aws-stepfunctions.IStepFunctionsTask +@aws-cdk/aws-stepfunctions.IStepFunctionsTask#bind +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#resourceArn +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#heartbeat +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#metricDimensions +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#metricPrefixPlural +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#metricPrefixSingular +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#parameters +@aws-cdk/aws-stepfunctions.StepFunctionsTaskConfig#policyStatements +@aws-cdk/aws-stepfunctions.Task +@aws-cdk/aws-stepfunctions.Task#endStates +@aws-cdk/aws-stepfunctions.Task#addCatch +@aws-cdk/aws-stepfunctions.Task#addRetry +@aws-cdk/aws-stepfunctions.Task#metric +@aws-cdk/aws-stepfunctions.Task#metricFailed +@aws-cdk/aws-stepfunctions.Task#metricHeartbeatTimedOut +@aws-cdk/aws-stepfunctions.Task#metricRunTime +@aws-cdk/aws-stepfunctions.Task#metricScheduled +@aws-cdk/aws-stepfunctions.Task#metricScheduleTime +@aws-cdk/aws-stepfunctions.Task#metricStarted +@aws-cdk/aws-stepfunctions.Task#metricSucceeded +@aws-cdk/aws-stepfunctions.Task#metricTime +@aws-cdk/aws-stepfunctions.Task#metricTimedOut +@aws-cdk/aws-stepfunctions.Task#next +@aws-cdk/aws-stepfunctions.Task#toStateJson +@aws-cdk/aws-stepfunctions.Task#whenBoundToGraph +@aws-cdk/aws-stepfunctions.TaskInput#fromContextAt +@aws-cdk/aws-stepfunctions.TaskInput#fromDataAt +@aws-cdk/aws-stepfunctions.TaskProps +@aws-cdk/aws-stepfunctions.TaskProps#task +@aws-cdk/aws-stepfunctions.TaskProps#comment +@aws-cdk/aws-stepfunctions.TaskProps#inputPath +@aws-cdk/aws-stepfunctions.TaskProps#outputPath +@aws-cdk/aws-stepfunctions.TaskProps#parameters +@aws-cdk/aws-stepfunctions.TaskProps#resultPath +@aws-cdk/aws-stepfunctions.TaskProps#timeout +@aws-cdk/aws-ecs-patterns.ApplicationLoadBalancedServiceBase#desiredCount +@aws-cdk/aws-ecs-patterns.ApplicationMultipleTargetGroupsServiceBase#desiredCount +@aws-cdk/aws-ecs-patterns.NetworkLoadBalancedServiceBase#desiredCount +@aws-cdk/aws-ecs-patterns.NetworkMultipleTargetGroupsServiceBase#desiredCount +@aws-cdk/aws-ecs-patterns.QueueProcessingServiceBase#desiredCount +@aws-cdk/aws-ecs-patterns.QueueProcessingServiceBaseProps#desiredTaskCount +@aws-cdk/aws-eks.NodegroupOptions#instanceType +@aws-cdk/aws-eks.ServiceAccount#addToPolicy +@aws-cdk/aws-s3-deployment.Expires +@aws-cdk/aws-s3-deployment.Expires#value +@aws-cdk/aws-s3-deployment.Expires#after +@aws-cdk/aws-s3-deployment.Expires#atDate +@aws-cdk/aws-s3-deployment.Expires#atTimestamp +@aws-cdk/aws-s3-deployment.Expires#fromString +@aws-cdk/aws-ses.WhiteListReceiptFilter +@aws-cdk/aws-ses.WhiteListReceiptFilterProps +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBase +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBase#connections +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBase#bind +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBase#configureAwsVpcNetworking +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBaseProps +@aws-cdk/aws-stepfunctions-tasks.EcsRunTaskBaseProps#parameters +@aws-cdk/aws-stepfunctions-tasks.InvocationType +@aws-cdk/aws-stepfunctions-tasks.InvocationType#REQUEST_RESPONSE +@aws-cdk/aws-stepfunctions-tasks.InvocationType#EVENT +@aws-cdk/aws-stepfunctions-tasks.InvocationType#DRY_RUN +@aws-cdk/aws-stepfunctions-tasks.InvokeActivity +@aws-cdk/aws-stepfunctions-tasks.InvokeActivity#bind +@aws-cdk/aws-stepfunctions-tasks.InvokeActivityProps +@aws-cdk/aws-stepfunctions-tasks.InvokeActivityProps#heartbeat +@aws-cdk/aws-stepfunctions-tasks.InvokeFunction +@aws-cdk/aws-stepfunctions-tasks.InvokeFunction#bind +@aws-cdk/aws-stepfunctions-tasks.InvokeFunctionProps +@aws-cdk/aws-stepfunctions-tasks.InvokeFunctionProps#payload +@aws-cdk/aws-stepfunctions-tasks.PublishToTopic +@aws-cdk/aws-stepfunctions-tasks.PublishToTopic#bind +@aws-cdk/aws-stepfunctions-tasks.PublishToTopicProps +@aws-cdk/aws-stepfunctions-tasks.PublishToTopicProps#message +@aws-cdk/aws-stepfunctions-tasks.PublishToTopicProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.PublishToTopicProps#messagePerSubscriptionType +@aws-cdk/aws-stepfunctions-tasks.PublishToTopicProps#subject +@aws-cdk/aws-stepfunctions-tasks.RunBatchJob +@aws-cdk/aws-stepfunctions-tasks.RunBatchJob#bind +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#jobDefinitionArn +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#jobName +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#jobQueueArn +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#arraySize +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#attempts +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#containerOverrides +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#dependsOn +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#payload +@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps#timeout +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2Task +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2TaskProps +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2TaskProps#placementConstraints +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2TaskProps#placementStrategies +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2TaskProps#securityGroup +@aws-cdk/aws-stepfunctions-tasks.RunEcsEc2TaskProps#subnets +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTask +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTaskProps +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTaskProps#assignPublicIp +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTaskProps#platformVersion +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTaskProps#securityGroup +@aws-cdk/aws-stepfunctions-tasks.RunEcsFargateTaskProps#subnets +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTask +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTask#bind +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps#arguments +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps#notifyDelayAfter +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps#securityConfiguration +@aws-cdk/aws-stepfunctions-tasks.RunGlueJobTaskProps#timeout +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTask +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTask#bind +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps#clientContext +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps#invocationType +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps#payload +@aws-cdk/aws-stepfunctions-tasks.RunLambdaTaskProps#qualifier +@aws-cdk/aws-stepfunctions-tasks.SendToQueue +@aws-cdk/aws-stepfunctions-tasks.SendToQueue#bind +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps#messageBody +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps#delay +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps#messageDeduplicationId +@aws-cdk/aws-stepfunctions-tasks.SendToQueueProps#messageGroupId +@aws-cdk/aws-stepfunctions-tasks.StartExecution +@aws-cdk/aws-stepfunctions-tasks.StartExecution#bind +@aws-cdk/aws-stepfunctions-tasks.StartExecutionProps +@aws-cdk/aws-stepfunctions-tasks.StartExecutionProps#input +@aws-cdk/aws-stepfunctions-tasks.StartExecutionProps#integrationPattern +@aws-cdk/aws-stepfunctions-tasks.StartExecutionProps#name +@aws-cdk/pipelines.AddManualApprovalOptions +@aws-cdk/pipelines.AddManualApprovalOptions#actionName +@aws-cdk/pipelines.AddManualApprovalOptions#runOrder +@aws-cdk/pipelines.AddStackOptions +@aws-cdk/pipelines.AddStackOptions#executeRunOrder +@aws-cdk/pipelines.AddStackOptions#runOrder +@aws-cdk/pipelines.AddStageOptions +@aws-cdk/pipelines.AddStageOptions#extraRunOrderSpace +@aws-cdk/pipelines.AddStageOptions#manualApprovals +@aws-cdk/pipelines.AdditionalArtifact +@aws-cdk/pipelines.AdditionalArtifact#artifact +@aws-cdk/pipelines.AdditionalArtifact#directory +@aws-cdk/pipelines.AssetPublishingCommand +@aws-cdk/pipelines.AssetPublishingCommand#assetId +@aws-cdk/pipelines.AssetPublishingCommand#assetManifestPath +@aws-cdk/pipelines.AssetPublishingCommand#assetPublishingRoleArn +@aws-cdk/pipelines.AssetPublishingCommand#assetSelector +@aws-cdk/pipelines.AssetPublishingCommand#assetType +@aws-cdk/pipelines.BaseStageOptions +@aws-cdk/pipelines.BaseStageOptions#confirmBroadeningPermissions +@aws-cdk/pipelines.BaseStageOptions#securityNotificationTopic +@aws-cdk/pipelines.CdkPipeline +@aws-cdk/pipelines.CdkPipeline#codePipeline +@aws-cdk/pipelines.CdkPipeline#addApplicationStage +@aws-cdk/pipelines.CdkPipeline#addStage +@aws-cdk/pipelines.CdkPipeline#stackOutput +@aws-cdk/pipelines.CdkPipeline#stage +@aws-cdk/pipelines.CdkPipeline#validate +@aws-cdk/pipelines.CdkPipelineProps +@aws-cdk/pipelines.CdkPipelineProps#cloudAssemblyArtifact +@aws-cdk/pipelines.CdkPipelineProps#assetBuildSpec +@aws-cdk/pipelines.CdkPipelineProps#assetPreInstallCommands +@aws-cdk/pipelines.CdkPipelineProps#cdkCliVersion +@aws-cdk/pipelines.CdkPipelineProps#codePipeline +@aws-cdk/pipelines.CdkPipelineProps#crossAccountKeys +@aws-cdk/pipelines.CdkPipelineProps#dockerCredentials +@aws-cdk/pipelines.CdkPipelineProps#enableKeyRotation +@aws-cdk/pipelines.CdkPipelineProps#pipelineName +@aws-cdk/pipelines.CdkPipelineProps#selfMutating +@aws-cdk/pipelines.CdkPipelineProps#selfMutationBuildSpec +@aws-cdk/pipelines.CdkPipelineProps#singlePublisherPerType +@aws-cdk/pipelines.CdkPipelineProps#sourceAction +@aws-cdk/pipelines.CdkPipelineProps#subnetSelection +@aws-cdk/pipelines.CdkPipelineProps#supportDockerAssets +@aws-cdk/pipelines.CdkPipelineProps#synthAction +@aws-cdk/pipelines.CdkPipelineProps#vpc +@aws-cdk/pipelines.CdkStackActionFromArtifactOptions +@aws-cdk/pipelines.CdkStackActionFromArtifactOptions#stackName +@aws-cdk/pipelines.CdkStage +@aws-cdk/pipelines.CdkStage#addActions +@aws-cdk/pipelines.CdkStage#addApplication +@aws-cdk/pipelines.CdkStage#addManualApprovalAction +@aws-cdk/pipelines.CdkStage#addStackArtifactDeployment +@aws-cdk/pipelines.CdkStage#deploysStack +@aws-cdk/pipelines.CdkStage#nextSequentialRunOrder +@aws-cdk/pipelines.CdkStageProps +@aws-cdk/pipelines.CdkStageProps#cloudAssemblyArtifact +@aws-cdk/pipelines.CdkStageProps#host +@aws-cdk/pipelines.CdkStageProps#pipelineStage +@aws-cdk/pipelines.CdkStageProps#stageName +@aws-cdk/pipelines.CdkStageProps#confirmBroadeningPermissions +@aws-cdk/pipelines.CdkStageProps#securityNotificationTopic +@aws-cdk/pipelines.DeployCdkStackAction +@aws-cdk/pipelines.DeployCdkStackAction#actionProperties +@aws-cdk/pipelines.DeployCdkStackAction#dependencyStackArtifactIds +@aws-cdk/pipelines.DeployCdkStackAction#executeRunOrder +@aws-cdk/pipelines.DeployCdkStackAction#prepareRunOrder +@aws-cdk/pipelines.DeployCdkStackAction#stackName +@aws-cdk/pipelines.DeployCdkStackAction#stackArtifactId +@aws-cdk/pipelines.DeployCdkStackAction#fromStackArtifact +@aws-cdk/pipelines.DeployCdkStackAction#bind +@aws-cdk/pipelines.DeployCdkStackAction#onStateChange +@aws-cdk/pipelines.DeployCdkStackActionOptions +@aws-cdk/pipelines.DeployCdkStackActionOptions#cloudAssemblyInput +@aws-cdk/pipelines.DeployCdkStackActionOptions#baseActionName +@aws-cdk/pipelines.DeployCdkStackActionOptions#changeSetName +@aws-cdk/pipelines.DeployCdkStackActionOptions#executeRunOrder +@aws-cdk/pipelines.DeployCdkStackActionOptions#output +@aws-cdk/pipelines.DeployCdkStackActionOptions#outputFileName +@aws-cdk/pipelines.DeployCdkStackActionOptions#prepareRunOrder +@aws-cdk/pipelines.DeployCdkStackActionProps +@aws-cdk/pipelines.DeployCdkStackActionProps#actionRole +@aws-cdk/pipelines.DeployCdkStackActionProps#stackName +@aws-cdk/pipelines.DeployCdkStackActionProps#templatePath +@aws-cdk/pipelines.DeployCdkStackActionProps#cloudFormationExecutionRole +@aws-cdk/pipelines.DeployCdkStackActionProps#dependencyStackArtifactIds +@aws-cdk/pipelines.DeployCdkStackActionProps#region +@aws-cdk/pipelines.DeployCdkStackActionProps#stackArtifactId +@aws-cdk/pipelines.DeployCdkStackActionProps#templateConfigurationPath +@aws-cdk/pipelines.FromStackArtifactOptions +@aws-cdk/pipelines.FromStackArtifactOptions#cloudAssemblyInput +@aws-cdk/pipelines.FromStackArtifactOptions#executeRunOrder +@aws-cdk/pipelines.FromStackArtifactOptions#output +@aws-cdk/pipelines.FromStackArtifactOptions#outputFileName +@aws-cdk/pipelines.FromStackArtifactOptions#prepareRunOrder +@aws-cdk/pipelines.IStageHost +@aws-cdk/pipelines.IStageHost#publishAsset +@aws-cdk/pipelines.IStageHost#stackOutputArtifact +@aws-cdk/pipelines.PublishAssetsAction +@aws-cdk/pipelines.PublishAssetsAction#actionProperties +@aws-cdk/pipelines.PublishAssetsAction#addPublishCommand +@aws-cdk/pipelines.PublishAssetsAction#bind +@aws-cdk/pipelines.PublishAssetsAction#onStateChange +@aws-cdk/pipelines.PublishAssetsActionProps +@aws-cdk/pipelines.PublishAssetsActionProps#actionName +@aws-cdk/pipelines.PublishAssetsActionProps#assetType +@aws-cdk/pipelines.PublishAssetsActionProps#cloudAssemblyInput +@aws-cdk/pipelines.PublishAssetsActionProps#buildSpec +@aws-cdk/pipelines.PublishAssetsActionProps#cdkCliVersion +@aws-cdk/pipelines.PublishAssetsActionProps#createBuildspecFile +@aws-cdk/pipelines.PublishAssetsActionProps#dependable +@aws-cdk/pipelines.PublishAssetsActionProps#preInstallCommands +@aws-cdk/pipelines.PublishAssetsActionProps#projectName +@aws-cdk/pipelines.PublishAssetsActionProps#role +@aws-cdk/pipelines.PublishAssetsActionProps#subnetSelection +@aws-cdk/pipelines.PublishAssetsActionProps#vpc +@aws-cdk/pipelines.ShellScriptAction +@aws-cdk/pipelines.ShellScriptAction#actionProperties +@aws-cdk/pipelines.ShellScriptAction#grantPrincipal +@aws-cdk/pipelines.ShellScriptAction#project +@aws-cdk/pipelines.ShellScriptAction#bind +@aws-cdk/pipelines.ShellScriptAction#onStateChange +@aws-cdk/pipelines.ShellScriptActionProps +@aws-cdk/pipelines.ShellScriptActionProps#actionName +@aws-cdk/pipelines.ShellScriptActionProps#commands +@aws-cdk/pipelines.ShellScriptActionProps#additionalArtifacts +@aws-cdk/pipelines.ShellScriptActionProps#bashOptions +@aws-cdk/pipelines.ShellScriptActionProps#environment +@aws-cdk/pipelines.ShellScriptActionProps#environmentVariables +@aws-cdk/pipelines.ShellScriptActionProps#rolePolicyStatements +@aws-cdk/pipelines.ShellScriptActionProps#runOrder +@aws-cdk/pipelines.ShellScriptActionProps#securityGroups +@aws-cdk/pipelines.ShellScriptActionProps#subnetSelection +@aws-cdk/pipelines.ShellScriptActionProps#useOutputs +@aws-cdk/pipelines.ShellScriptActionProps#vpc +@aws-cdk/pipelines.SimpleSynthAction +@aws-cdk/pipelines.SimpleSynthAction#actionProperties +@aws-cdk/pipelines.SimpleSynthAction#grantPrincipal +@aws-cdk/pipelines.SimpleSynthAction#project +@aws-cdk/pipelines.SimpleSynthAction#standardNpmSynth +@aws-cdk/pipelines.SimpleSynthAction#standardYarnSynth +@aws-cdk/pipelines.SimpleSynthAction#bind +@aws-cdk/pipelines.SimpleSynthAction#onStateChange +@aws-cdk/pipelines.SimpleSynthActionProps +@aws-cdk/pipelines.SimpleSynthActionProps#synthCommand +@aws-cdk/pipelines.SimpleSynthActionProps#buildCommand +@aws-cdk/pipelines.SimpleSynthActionProps#buildCommands +@aws-cdk/pipelines.SimpleSynthActionProps#installCommand +@aws-cdk/pipelines.SimpleSynthActionProps#installCommands +@aws-cdk/pipelines.SimpleSynthActionProps#testCommands +@aws-cdk/pipelines.SimpleSynthOptions +@aws-cdk/pipelines.SimpleSynthOptions#cloudAssemblyArtifact +@aws-cdk/pipelines.SimpleSynthOptions#sourceArtifact +@aws-cdk/pipelines.SimpleSynthOptions#actionName +@aws-cdk/pipelines.SimpleSynthOptions#additionalArtifacts +@aws-cdk/pipelines.SimpleSynthOptions#buildSpec +@aws-cdk/pipelines.SimpleSynthOptions#copyEnvironmentVariables +@aws-cdk/pipelines.SimpleSynthOptions#environment +@aws-cdk/pipelines.SimpleSynthOptions#environmentVariables +@aws-cdk/pipelines.SimpleSynthOptions#projectName +@aws-cdk/pipelines.SimpleSynthOptions#rolePolicyStatements +@aws-cdk/pipelines.SimpleSynthOptions#subdirectory +@aws-cdk/pipelines.SimpleSynthOptions#subnetSelection +@aws-cdk/pipelines.SimpleSynthOptions#vpc +@aws-cdk/pipelines.StackOutput +@aws-cdk/pipelines.StackOutput#artifactFile +@aws-cdk/pipelines.StackOutput#outputName +@aws-cdk/pipelines.StandardNpmSynthOptions +@aws-cdk/pipelines.StandardNpmSynthOptions#buildCommand +@aws-cdk/pipelines.StandardNpmSynthOptions#installCommand +@aws-cdk/pipelines.StandardNpmSynthOptions#synthCommand +@aws-cdk/pipelines.StandardNpmSynthOptions#testCommands +@aws-cdk/pipelines.StandardYarnSynthOptions +@aws-cdk/pipelines.StandardYarnSynthOptions#buildCommand +@aws-cdk/pipelines.StandardYarnSynthOptions#installCommand +@aws-cdk/pipelines.StandardYarnSynthOptions#synthCommand +@aws-cdk/pipelines.StandardYarnSynthOptions#testCommands +@aws-cdk/pipelines.UpdatePipelineAction +@aws-cdk/pipelines.UpdatePipelineAction#actionProperties +@aws-cdk/pipelines.UpdatePipelineAction#bind +@aws-cdk/pipelines.UpdatePipelineAction#onStateChange +@aws-cdk/pipelines.UpdatePipelineActionProps +@aws-cdk/pipelines.UpdatePipelineActionProps#cloudAssemblyInput +@aws-cdk/pipelines.UpdatePipelineActionProps#pipelineStackHierarchicalId +@aws-cdk/pipelines.UpdatePipelineActionProps#buildSpec +@aws-cdk/pipelines.UpdatePipelineActionProps#cdkCliVersion +@aws-cdk/pipelines.UpdatePipelineActionProps#dockerCredentials +@aws-cdk/pipelines.UpdatePipelineActionProps#pipelineStackName +@aws-cdk/pipelines.UpdatePipelineActionProps#privileged +@aws-cdk/pipelines.UpdatePipelineActionProps#projectName diff --git a/pack.sh b/pack.sh index d270fb28271c5..4b9a13ab5aa26 100755 --- a/pack.sh +++ b/pack.sh @@ -36,14 +36,7 @@ function lerna_scopes() { done } -# Compile examples with respect to "decdk" directory, as all packages will -# be symlinked there so they can all be included. -echo "Extracting code samples" >&2 -node --experimental-worker $(which $ROSETTA) \ - --compile \ - --output samples.tabl.json \ - --directory packages/decdk \ - $(cat $TMPDIR/jsii.txt) +scripts/run-rosetta.sh --infuse --pkgs-from $TMPDIR/jsii.txt # Jsii packaging (all at once using jsii-pacmak) echo "Packaging jsii modules" >&2 @@ -92,8 +85,12 @@ cat > ${distdir}/build.json < { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts index 1f893305ef788..443c9fa9f9f68 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as cdk from '@aws-cdk/core'; @@ -148,9 +149,13 @@ describe('environment', () => { // WHEN const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, @@ -222,9 +227,13 @@ describe('environment', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); // WHEN const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts index 5e0d0e36ff554..e06c1632d93b5 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts @@ -72,4 +72,72 @@ describe('http load balancer', () => { }); + test('allows scaling on request count for the HTTP load balancer', () => { + // 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 HttpLoadBalancerExtension({ requestsPerTarget: 100 })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + autoScaleTaskCount: { + maxTaskCount: 5, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { + PredefinedMetricType: 'ALBRequestCountPerTarget', + }, + TargetValue: 100, + }, + }); + }); + + test('should error when adding scaling policy if scaling target has not been configured', () => { + // 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 HttpLoadBalancerExtension({ requestsPerTarget: 100 })); + + // THEN + expect(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + }).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure 'minTaskCount' and 'maxTaskCount'./); + }); + }); \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json index 47ba97c3ee10d..bd9224e586933 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.all-service-addons.expected.json @@ -102,15 +102,15 @@ "productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" + }, "AllocationId": { "Fn::GetAtt": [ "productionenvironmentvpcPublicSubnet1EIP54BA88DB", "AllocationId" ] }, - "SubnetId": { - "Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089" - }, "Tags": [ { "Key": "Name", @@ -199,15 +199,15 @@ "productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" + }, "AllocationId": { "Fn::GetAtt": [ "productionenvironmentvpcPublicSubnet2EIP14CA46AA", "AllocationId" ] }, - "SubnetId": { - "Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31" - }, "Tags": [ { "Key": "Name", @@ -296,15 +296,15 @@ "productionenvironmentvpcPublicSubnet3NATGateway94604057": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" + }, "AllocationId": { "Fn::GetAtt": [ "productionenvironmentvpcPublicSubnet3EIP53405AED", "AllocationId" ] }, - "SubnetId": { - "Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D" - }, "Tags": [ { "Key": "Name", @@ -949,7 +949,11 @@ "Fn::Join": [ "", [ - "arn:aws:ecr:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", { "Ref": "AWS::Region" }, @@ -1818,7 +1822,11 @@ "Fn::Join": [ "", [ - "arn:aws:ecr:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", { "Ref": "AWS::Region" }, @@ -2383,6 +2391,10 @@ { "Key": "deregistration_delay.timeout_seconds", "Value": "10" + }, + { + "Key": "stickiness.enabled", + "Value": "false" } ], "TargetType": "ip", @@ -2806,7 +2818,11 @@ "Fn::Join": [ "", [ - "arn:aws:ecr:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", { "Ref": "AWS::Region" }, @@ -3316,6 +3332,12 @@ "ca-central-1": { "ecrRepo": "840364872350" }, + "cn-north-1": { + "ecrRepo": "919366029133" + }, + "cn-northwest-1": { + "ecrRepo": "919830735681" + }, "eu-central-1": { "ecrRepo": "840364872350" }, @@ -3378,6 +3400,12 @@ "ca-central-1": { "ecrRepo": "840364872350" }, + "cn-north-1": { + "ecrRepo": "919366029133" + }, + "cn-northwest-1": { + "ecrRepo": "919830735681" + }, "eu-central-1": { "ecrRepo": "840364872350" }, @@ -3440,6 +3468,12 @@ "ca-central-1": { "ecrRepo": "840364872350" }, + "cn-north-1": { + "ecrRepo": "919366029133" + }, + "cn-northwest-1": { + "ecrRepo": "919830735681" + }, "eu-central-1": { "ecrRepo": "840364872350" }, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json index dbc216370e40d..c85f5d9b0231d 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json @@ -17,7 +17,7 @@ }, "/", { - "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412" + "Ref": "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886S3Bucket85F9E22A" }, "/", { @@ -27,7 +27,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + "Ref": "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886S3VersionKey8103F967" } ] } @@ -40,7 +40,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + "Ref": "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886S3VersionKey8103F967" } ] } @@ -163,6 +163,10 @@ { "Key": "deregistration_delay.timeout_seconds", "Value": "10" + }, + { + "Key": "stickiness.enabled", + "Value": "false" } ], "TargetType": "ip", @@ -358,17 +362,17 @@ } }, "Parameters": { - "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412": { + "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886S3Bucket85F9E22A": { "Type": "String", - "Description": "S3 bucket for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + "Description": "S3 bucket for asset \"e2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886\"" }, - "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52": { + "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886S3VersionKey8103F967": { "Type": "String", - "Description": "S3 key for asset version \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + "Description": "S3 key for asset version \"e2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886\"" }, - "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8ArtifactHash5EEB924C": { + "AssetParameterse2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886ArtifactHash6FA4ABA8": { "Type": "String", - "Description": "Artifact hash for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + "Description": "Artifact hash for asset \"e2fc78c01b63f40b6bbcc8ce3c0eaccd3bf8f0aa6196b65f8ee87fb97e343886\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json index 3a3aaddac6fce..d5f00cb5708c7 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.multiple-environments.expected.json @@ -1304,7 +1304,11 @@ "Fn::Join": [ "", [ - "arn:aws:ecr:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", { "Ref": "AWS::Region" }, @@ -1828,7 +1832,11 @@ "Fn::Join": [ "", [ - "arn:aws:ecr:", + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", { "Ref": "AWS::Region" }, @@ -2135,6 +2143,12 @@ "ca-central-1": { "ecrRepo": "840364872350" }, + "cn-north-1": { + "ecrRepo": "919366029133" + }, + "cn-northwest-1": { + "ecrRepo": "919830735681" + }, "eu-central-1": { "ecrRepo": "840364872350" }, @@ -2197,6 +2211,12 @@ "ca-central-1": { "ecrRepo": "840364872350" }, + "cn-north-1": { + "ecrRepo": "919366029133" + }, + "cn-northwest-1": { + "ecrRepo": "919830735681" + }, "eu-central-1": { "ecrRepo": "840364872350" }, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts index eb21eec01b5f5..39d26ef371f17 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts @@ -1,4 +1,6 @@ +import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; @@ -30,9 +32,13 @@ describe('service', () => { 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'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, @@ -102,4 +108,117 @@ describe('service', () => { }); + test('allows scaling on a target CPU utilization', () => { + // 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'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + desiredCount: 3, + autoScaleTaskCount: { + maxTaskCount: 5, + targetCpuUtilization: 70, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::Service', { + DesiredCount: ABSENT, + }); + + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { PredefinedMetricType: 'ECSServiceAverageCPUUtilization' }, + TargetValue: 70, + }, + }); + }); + + test('allows scaling on a target memory utilization', () => { + // 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'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + desiredCount: 3, + autoScaleTaskCount: { + maxTaskCount: 5, + targetMemoryUtilization: 70, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::Service', { + DesiredCount: ABSENT, + }); + + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { PredefinedMetricType: 'ECSServiceAverageMemoryUtilization' }, + TargetValue: 70, + }, + }); + }); + + test('should error when no auto scaling policies have been configured after creating the auto scaling target', () => { + // 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'), + })); + + // THEN + expect(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + autoScaleTaskCount: { + maxTaskCount: 5, + }, + }); + }).toThrow(/The auto scaling target for the service 'my-service' has been created but no auto scaling policies have been configured./); + }); + }); \ 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 e773c5c1e9364..562bde75e3b81 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index 3448885fbbe39..2f7f766ae2406 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -65,9 +65,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "fast-check": "^2.17.0", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "fast-check": "^2.19.0", + "jest": "^27.3.1" }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts b/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts index e065330c152d3..b0c8e4f941a04 100644 --- a/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts +++ b/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts @@ -7,6 +7,7 @@ import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; @@ -19,7 +20,7 @@ interface SelfUpdatingPipeline { } const accountId = fc.array(fc.integer(0, 9), 12, 12).map(arr => arr.join()); -describe('pipeline deploy stack action', () => { +describeDeprecated('pipeline deploy stack action', () => { test('rejects cross-environment deployment', () => { fc.assert( fc.property( diff --git a/packages/@aws-cdk/assert-internal/README.md b/packages/@aws-cdk/assert-internal/README.md index 9256b46d2b154..ae72469dc59e2 100644 --- a/packages/@aws-cdk/assert-internal/README.md +++ b/packages/@aws-cdk/assert-internal/README.md @@ -11,6 +11,8 @@ > 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. +If using monocdk, use [@monocdk-experiment/assert](https://www.npmjs.com/package/@monocdk-experiment/assert) instead. + --- diff --git a/packages/@aws-cdk/assert-internal/package.json b/packages/@aws-cdk/assert-internal/package.json index c8fa0b2fca84d..89d3b53aebc0d 100644 --- a/packages/@aws-cdk/assert-internal/package.json +++ b/packages/@aws-cdk/assert-internal/package.json @@ -26,9 +26,9 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "@types/jest": "^27.0.2", + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -40,7 +40,7 @@ "peerDependencies": { "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 156a2b583f02c..14783485e9e32 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -37,11 +37,11 @@ "@aws-cdk/assert-internal": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-cdk-migration": "0.0.0", "constructs": "^3.3.69", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/cloudformation-diff": "0.0.0", diff --git a/packages/@aws-cdk/assertions/NOTICE b/packages/@aws-cdk/assertions/NOTICE index 4af990c1637a0..9e3fd34dbe209 100644 --- a/packages/@aws-cdk/assertions/NOTICE +++ b/packages/@aws-cdk/assertions/NOTICE @@ -5,461 +5,84 @@ Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. The AWS CDK includes the following third-party software/licensing: -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires +** fs-extra - https://www.npmjs.com/package/fs-extra +Copyright (c) 2011-2017 JP Richardson -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +** at-least-node - https://www.npmjs.com/package/at-least-node +Copyright (c) 2020 Ryan Zimmerman -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +** graceful-fs - https://www.npmjs.com/package/graceful-fs +Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +** jsonfile - https://www.npmjs.com/package/jsonfile +Copyright (c) 2012-2015, JP Richardson -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- +** universalify - https://www.npmjs.com/package/universalify +Copyright (c) 2017, Ryan Zimmerman -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** punycode - https://www.npmjs.com/package/punycode -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** lodash.clonedeep - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md index cb081c32053a2..559bb3cea3c0f 100644 --- a/packages/@aws-cdk/assertions/README.md +++ b/packages/@aws-cdk/assertions/README.md @@ -3,13 +3,7 @@ --- -![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. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- @@ -21,20 +15,20 @@ The `Template` class includes a set of methods for writing assertions against Cl To create `Template` from CDK stack, start off with: -```ts +```ts nofixture import { Stack } from '@aws-cdk/core'; import { Template } from '@aws-cdk/assertions'; -const stack = new Stack(...) -... -const assert = Template.fromStack(stack); +const stack = new Stack(/* ... */); +// ... +const template = Template.fromStack(stack); ``` Alternatively, assertions can be run on an existing CloudFormation template - -```ts -const template = fs.readFileSync('/path/to/template/file'); -const assert = Template.fromString(template); +```ts fixture=init +const templateJson = '{ "Resources": ... }'; /* The CloudFormation template as JSON serialized string. */ +const template = Template.fromString(templateJson); ``` ## Full Template Match @@ -43,26 +37,34 @@ The simplest assertion would be to assert that the template matches a given template. ```ts -assert.templateMatches({ +const expected = { Resources: { - Type: 'Foo::Bar', - Properties: { - Baz: 'Qux', + BarLogicalId: { + Type: 'Foo::Bar', + Properties: { + Baz: 'Qux', + }, }, }, -}); +}; + +template.templateMatches(expected); ``` -The `Template` class also supports [snapshot -testing](https://jestjs.io/docs/snapshot-testing) using jest. +By default, the `templateMatches()` API will use the an 'object-like' comparison, +which means that it will allow for the actual template to be a superset of the +given expectation. See [Special Matchers](#special-matchers) for details on how +to change this. -```ts -// using jest -expect(Template.fromStack(stack)).toMatchSnapshot(); -``` +Snapshot testing is a common technique to store a snapshot of the output and +compare it during future changes. Since CloudFormation templates are human readable, +they are a good target for snapshot testing. -For non-javascript languages, the `toJSON()` can be called to get an in-memory object -of the template. +The `toJSON()` method on the `Template` can be used to produce a well formatted JSON +of the CloudFormation template that can be used as a snapshot. + +See [Snapshot Testing in Jest](https://jestjs.io/docs/snapshot-testing) and [Snapshot +Testing in Java](https://json-snapshot.github.io/). ## Counting Resources @@ -70,7 +72,7 @@ This module allows asserting the number of resources of a specific type found in a template. ```ts -assert.resourceCountIs('Foo::Bar', 2); +template.resourceCountIs('Foo::Bar', 2); ``` ## Resource Matching & Retrieval @@ -82,21 +84,23 @@ The following code asserts that the `Properties` section of a resource of type `Foo::Bar` contains the specified properties - ```ts -assert.hasResourceProperties('Foo::Bar', { +const expected = { Foo: 'Bar', Baz: 5, Qux: [ 'Waldo', 'Fred' ], -}); +}; +template.hasResourceProperties('Foo::Bar', expected); ``` Alternatively, if you would like to assert the entire resource definition, you can use the `hasResource()` API. ```ts -assert.hasResource('Foo::Bar', { +const expected = { Properties: { Foo: 'Bar' }, DependsOn: [ 'Waldo', 'Fred' ], -}); +}; +template.hasResource('Foo::Bar', expected); ``` Beyond assertions, the module provides APIs to retrieve matching resources. @@ -114,28 +118,31 @@ that matches specific properties. The following code asserts that a template con an Output with a `logicalId` of `Foo` and the specified properties - ```ts -assert.hasOutput('Foo', { +const expected = { Value: 'Bar', Export: { Name: 'ExportBaz' }, -}); +}; +template.hasOutput('Foo', expected); ``` If you want to match against all Outputs in the template, use `*` as the `logicalId`. ```ts -assert.hasOutput('*', { +const expected = { Value: 'Bar', Export: { Name: 'ExportBaz' }, -}); +}; +template.hasOutput('*', expected); ``` `findOutputs()` will return a set of outputs that match the `logicalId` and `props`, and you can use the `'*'` special case as well. ```ts -const result = assert.findOutputs('*', { +const expected = { Value: 'Fred', -}); +}; +const result = template.findOutputs('*', expected); expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' }); expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' }); ``` @@ -175,18 +182,20 @@ level, the list of keys in the target is a subset of the provided pattern. // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: Match.objectLike({ Wobble: 'Flob', }), -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const unexpected = { Fred: Match.objectLike({ Brew: 'Coffee', - }) -}); + }), +} +template.hasResourceProperties('Foo::Bar', unexpected); ``` The `Match.objectEquals()` API can be used to assert a target as a deep exact @@ -214,18 +223,20 @@ or outside of any matchers. // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: Match.objectLike({ Bob: Match.absent(), }), -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const unexpected = { Fred: Match.objectLike({ Wobble: Match.absent(), }), -}); +}; +template.hasResourceProperties('Foo::Bar', unexpected); ``` The `Match.anyValue()` matcher can be used to specify that a specific value should be found @@ -250,18 +261,20 @@ This matcher can be combined with any of the other matchers. // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: { Wobble: [Match.anyValue(), "Flip"], }, -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const unexpected = { Fred: { Wimble: Match.anyValue(), }, -}); +}; +template.hasResourceProperties('Foo::Bar', unexpected); ``` ### Array Matchers @@ -284,14 +297,16 @@ This API will perform subset match on the target. // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: Match.arrayWith(['Flob']), -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', Match.objectLike({ - Fred: Match.arrayWith(['Wobble']); -}}); +const unexpected = Match.objectLike({ + Fred: Match.arrayWith(['Wobble']), +}); +template.hasResourceProperties('Foo::Bar', unexpected); ``` *Note:* The list of items in the pattern array should be in order as they appear in the @@ -319,14 +334,16 @@ not match the pattern specified. // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: Match.not(['Flob']), -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', Match.objectLike({ - Fred: Match.not(['Flob', 'Cat']); -}}); +const unexpected = Match.objectLike({ + Fred: Match.not(['Flob', 'Cat']), +}); +template.hasResourceProperties('Foo::Bar', unexpected); ``` ### Serialized JSON @@ -353,18 +370,20 @@ The `Match.serializedJson()` matcher allows deep matching within a stringified J // } // The following will NOT throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const expected = { Baz: Match.serializedJson({ Fred: Match.arrayWith(["Waldo"]), }), -}); +}; +template.hasResourceProperties('Foo::Bar', expected); // The following will throw an assertion error -assert.hasResourceProperties('Foo::Bar', { +const unexpected = { Baz: Match.serializedJson({ Fred: ["Waldo", "Johnny"], }), -}); +}; +template.hasResourceProperties('Foo::Bar', unexpected); ``` [Pipeline BuildSpec]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-source.html#cfn-codebuild-project-source-buildspec @@ -392,37 +411,12 @@ matching resource. const fredCapture = new Capture(); const waldoCapture = new Capture(); -assert.hasResourceProperties('Foo::Bar', { +const expected = { Fred: fredCapture, Waldo: ["Qix", waldoCapture], -}); +} +template.hasResourceProperties('Foo::Bar', expected); fredCapture.asArray(); // returns ["Flob", "Cat"] waldoCapture.asString(); // returns "Qux" ``` - -## Strongly typed languages - -Some of the APIs documented above, such as `templateMatches()` and -`hasResourceProperties()` accept fluently an arbitrary JSON (like) structure -its parameter. -This fluency is available only in dynamically typed languages like javascript -and Python. - -For strongly typed languages, like Java, you can achieve similar fluency using -any popular JSON deserializer. The following Java example uses `Gson` - - -```java -// In Java, using text blocks and Gson -import com.google.gson.Gson; - -String json = """ - { - "Foo": "Bar", - "Baz": 5, - "Qux": [ "Waldo", "Fred" ], - } """; - -Map expected = new Gson().fromJson(json, Map.class); -assert.hasResourceProperties("Foo::Bar", expected); -``` diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index 01e0d3376dc8c..dfc830cf8d822 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -1,4 +1,6 @@ +import * as path from 'path'; import { Stack, Stage } from '@aws-cdk/core'; +import * as fs from 'fs-extra'; import { Match } from './match'; import { Matcher } from './matcher'; import { findMappings, hasMapping } from './private/mappings'; @@ -179,5 +181,9 @@ function toTemplate(stack: Stack): any { throw new Error('unexpected: all stacks must be part of a Stage or an App'); } const assembly = root.synth(); + if (stack.nestedStackParent) { + // if this is a nested stack (it has a parent), then just read the template as a string + return JSON.parse(fs.readFileSync(path.join(assembly.directory, stack.templateFile)).toString('utf-8')); + } return assembly.getStackArtifact(stack.artifactId).template; } \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/package.json b/packages/@aws-cdk/assertions/package.json index 818bf9fa289ed..1b515143d4d58 100644 --- a/packages/@aws-cdk/assertions/package.json +++ b/packages/@aws-cdk/assertions/package.json @@ -29,7 +29,7 @@ "package": "software.amazon.awscdk.assertions", "maven": { "groupId": "software.amazon.awscdk", - "artifactId": "cdk-assertions" + "artifactId": "assertions" } }, "dotnet": { @@ -46,7 +46,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "author": { "name": "Amazon Web Services", @@ -57,21 +64,18 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.0.2", "constructs": "^3.3.69", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "colors": "^1.4.0", "constructs": "^3.3.69", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", - "string-width": "^4.2.3", - "table": "^6.7.2" + "fs-extra": "^9.1.0" }, "peerDependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -80,11 +84,7 @@ "constructs": "^3.3.69" }, "bundledDependencies": [ - "colors", - "diff", - "fast-deep-equal", - "string-width", - "table" + "fs-extra" ], "repository": { "url": "https://github.com/aws/aws-cdk.git", @@ -100,8 +100,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "publishConfig": { "tag": "latest" }, diff --git a/packages/monocdk/rosetta/with-vpc.ts-fixture b/packages/@aws-cdk/assertions/rosetta/default.ts-fixture similarity index 54% rename from packages/monocdk/rosetta/with-vpc.ts-fixture rename to packages/@aws-cdk/assertions/rosetta/default.ts-fixture index dd8e539f8cf9f..53eee35747e52 100644 --- a/packages/monocdk/rosetta/with-vpc.ts-fixture +++ b/packages/@aws-cdk/assertions/rosetta/default.ts-fixture @@ -1,12 +1,12 @@ -// Fixture with packages imported and a VPC created import { Construct, Stack } from '@aws-cdk/core'; -import ec2 = require('@aws-cdk/aws-ec2'); +import { Capture, Match, Template } from '@aws-cdk/assertions'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const vpc = new ec2.Vpc(this, 'VPC'); + const stack = new Stack(); + const template = Template.fromStack(stack); /// here } diff --git a/packages/@aws-cdk/assertions/rosetta/init.ts-fixture b/packages/@aws-cdk/assertions/rosetta/init.ts-fixture new file mode 100644 index 0000000000000..ce18625a2744b --- /dev/null +++ b/packages/@aws-cdk/assertions/rosetta/init.ts-fixture @@ -0,0 +1,3 @@ +import { Template } from '@aws-cdk/assertions'; + +/// here \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index 3384cda21207f..64141edf52e89 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -1,11 +1,10 @@ -import { App, CfnMapping, CfnOutput, CfnResource, Stack } from '@aws-cdk/core'; +import { App, CfnMapping, CfnOutput, CfnResource, NestedStack, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Match, Template } from '../lib'; describe('Template', () => { - describe('asObject', () => { - test('fromString', () => { - const template = Template.fromString(`{ + test('fromString', () => { + const template = Template.fromString(`{ "Resources": { "Foo": { "Type": "Baz::Qux", @@ -14,30 +13,57 @@ describe('Template', () => { } }`); + expect(template.toJSON()).toEqual({ + Resources: { + Foo: { + Type: 'Baz::Qux', + Properties: { Fred: 'Waldo' }, + }, + }, + }); + }); + + describe('fromStack', () => { + test('default', () => { + const app = new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': false, + }, + }); + const stack = new Stack(app); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { + Baz: 'Qux', + }, + }); + const template = Template.fromStack(stack); + expect(template.toJSON()).toEqual({ Resources: { Foo: { - Type: 'Baz::Qux', - Properties: { Fred: 'Waldo' }, + Type: 'Foo::Bar', + Properties: { Baz: 'Qux' }, }, }, }); }); - test('fromStack', () => { + test('nested', () => { const app = new App({ context: { '@aws-cdk/core:newStyleStackSynthesis': false, }, }); const stack = new Stack(app); - new CfnResource(stack, 'Foo', { + const nested = new NestedStack(stack, 'MyNestedStack'); + new CfnResource(nested, 'Foo', { type: 'Foo::Bar', properties: { Baz: 'Qux', }, }); - const template = Template.fromStack(stack); + const template = Template.fromStack(nested); expect(template.toJSON()).toEqual({ Resources: { diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 7b77655ea3a16..c099d2edc4c02 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -73,12 +73,12 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", "aws-cdk": "0.0.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "sinon": "^9.2.4", - "ts-mock-imports": "^1.3.7" + "ts-mock-imports": "^1.3.8" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/assets/test/staging.test.ts b/packages/@aws-cdk/assets/test/staging.test.ts index 4c95f236f2d81..bd924e434207b 100644 --- a/packages/@aws-cdk/assets/test/staging.test.ts +++ b/packages/@aws-cdk/assets/test/staging.test.ts @@ -1,11 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import '@aws-cdk/assert-internal/jest'; import { Staging } from '../lib'; -describe('staging', () => { +describeDeprecated('staging', () => { test('base case', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-accessanalyzer/package.json b/packages/@aws-cdk/aws-accessanalyzer/package.json index d1938b9d354e5..67e9594bf1c0d 100644 --- a/packages/@aws-cdk/aws-accessanalyzer/package.json +++ b/packages/@aws-cdk/aws-accessanalyzer/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-acmpca/package.json b/packages/@aws-cdk/aws-acmpca/package.json index a3478244753f9..4dd5abf981e07 100644 --- a/packages/@aws-cdk/aws-acmpca/package.json +++ b/packages/@aws-cdk/aws-acmpca/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index fd3fd5946e6f3..0cf6fab9471d8 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-amplify/NOTICE b/packages/@aws-cdk/aws-amplify/NOTICE index 5fc3826926b5b..c84ff36099c3b 100644 --- a/packages/@aws-cdk/aws-amplify/NOTICE +++ b/packages/@aws-cdk/aws-amplify/NOTICE @@ -1,2 +1,23 @@ AWS Cloud Development Kit (AWS CDK) Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** yaml - https://www.npmjs.com/package/yaml +Copyright 2018 Eemeli Aro + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---------------- diff --git a/packages/@aws-cdk/aws-amplify/README.md b/packages/@aws-cdk/aws-amplify/README.md index a706cb73bb39b..059588e493241 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -55,7 +55,8 @@ const amplifyApp = new amplify.App(this, 'MyApp', { }, artifacts: { baseDirectory: 'public', - files: '**/*' + files: + - '**/*' } } }) @@ -178,3 +179,32 @@ const amplifyApp = new amplify.App(this, 'MyApp', { autoBranchDeletion: true, // Automatically disconnect a branch when you delete a branch from your repository }); ``` + +## Adding custom response headers + +Use the `customResponseHeaders` prop to configure custom response headers for an Amplify app: + +```ts +const amplifyApp = new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: '', + repository: '', + oauthToken: cdk.SecretValue.secretsManager('my-github-token') + }), + customResponseHeaders: [ + { + pattern: '*.json', + headers: { + 'custom-header-name-1': 'custom-header-value-1', + 'custom-header-name-2': 'custom-header-value-2', + }, + }, + { + pattern: '/path/*', + headers: { + 'custom-header-name-1': 'custom-header-value-2', + }, + }, + ], +}); +``` diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 831921f89dbf8..030dc58059a6e 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -2,6 +2,7 @@ import * as codebuild from '@aws-cdk/aws-codebuild'; import * as iam from '@aws-cdk/aws-iam'; import { IResource, Lazy, Resource, SecretValue } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import * as YAML from 'yaml'; import { CfnApp } from './amplify.generated'; import { BasicAuth } from './basic-auth'; import { Branch, BranchOptions } from './branch'; @@ -118,6 +119,16 @@ export interface AppProps { */ readonly buildSpec?: codebuild.BuildSpec; + + /** + * The custom HTTP response headers for an Amplify app. + * + * @see https://docs.aws.amazon.com/amplify/latest/userguide/custom-headers.html + * + * @default - no custom response headers + */ + readonly customResponseHeaders?: CustomResponseHeader[]; + /** * Custom rewrite/redirect rules for the application * @@ -238,6 +249,7 @@ export class App extends Resource implements IApp, iam.IGrantable { name: props.appName || this.node.id, oauthToken: sourceCodeProviderOptions?.oauthToken?.toString(), repository: sourceCodeProviderOptions?.repository, + customHeaders: props.customResponseHeaders ? renderCustomResponseHeaders(props.customResponseHeaders) : undefined, }); this.appId = app.attrAppId; @@ -486,3 +498,28 @@ export class CustomRule { this.condition = options.condition; } } + +/** + * Custom response header of an Amplify App. + */ +export interface CustomResponseHeader { + /** + * These custom headers will be applied to all URL file paths that match this pattern. + */ + readonly pattern: string; + + /** + * The map of custom headers to be applied. + */ + readonly headers: { [key: string]: string }; +} + +function renderCustomResponseHeaders(customHeaders: CustomResponseHeader[]): string { + const modifiedHeaders = customHeaders.map(customHeader => ({ + ...customHeader, + headers: Object.entries(customHeader.headers).map(([key, value]) => ({ key, value })), + })); + + const customHeadersObject = { customHeaders: modifiedHeaders }; + return YAML.stringify(customHeadersObject); +} diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index 13bbcc0ec61d4..4b14568aada7d 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -79,7 +79,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2", + "@types/yaml": "1.9.6" }, "dependencies": { "@aws-cdk/aws-codebuild": "0.0.0", @@ -88,8 +89,12 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", - "constructs": "^3.3.69" + "constructs": "^3.3.69", + "yaml": "1.10.2" }, + "bundledDependencies": [ + "yaml" + ], "peerDependencies": { "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", diff --git a/packages/@aws-cdk/aws-amplify/test/app.test.ts b/packages/@aws-cdk/aws-amplify/test/app.test.ts index 76b6830bec1c5..87b15e143ab7f 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/app.test.ts @@ -394,3 +394,34 @@ test('with auto branch deletion', () => { EnableBranchAutoDeletion: true, }); }); + +test('with custom headers', () => { + // WHEN + new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + customResponseHeaders: [ + { + pattern: '*.json', + headers: { + 'custom-header-name-1': 'custom-header-value-1', + 'custom-header-name-2': 'custom-header-value-2', + }, + }, + { + pattern: '/path/*', + headers: { + 'custom-header-name-1': 'custom-header-value-2', + }, + }, + ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Amplify::App', { + CustomHeaders: 'customHeaders:\n - pattern: "*.json"\n headers:\n - key: custom-header-name-1\n value: custom-header-value-1\n - key: custom-header-name-2\n value: custom-header-value-2\n - pattern: /path/*\n headers:\n - key: custom-header-name-1\n value: custom-header-value-2\n', + }); +}); diff --git a/packages/@aws-cdk/aws-amplify/test/integ.app.expected.json b/packages/@aws-cdk/aws-amplify/test/integ.app.expected.json index 25971c2ea44a7..bd8f69d9f66d8 100644 --- a/packages/@aws-cdk/aws-amplify/test/integ.app.expected.json +++ b/packages/@aws-cdk/aws-amplify/test/integ.app.expected.json @@ -24,7 +24,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"aws\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "AppF1B96344": { "Type": "AWS::Amplify::App", @@ -54,6 +56,7 @@ }, "Username": "aws" }, + "CustomHeaders": "customHeaders:\n - pattern: \"*.json\"\n headers:\n - key: custom-header-name-1\n value: custom-header-value-1\n - key: custom-header-name-2\n value: custom-header-value-2\n - pattern: /path/*\n headers:\n - key: custom-header-name-1\n value: custom-header-value-2\n", "CustomRules": [ { "Source": "/source", diff --git a/packages/@aws-cdk/aws-amplify/test/integ.app.ts b/packages/@aws-cdk/aws-amplify/test/integ.app.ts index 83ff527a292e2..accdaed6840bf 100644 --- a/packages/@aws-cdk/aws-amplify/test/integ.app.ts +++ b/packages/@aws-cdk/aws-amplify/test/integ.app.ts @@ -9,6 +9,21 @@ class TestStack extends Stack { const amplifyApp = new amplify.App(this, 'App', { basicAuth: amplify.BasicAuth.fromGeneratedPassword('aws'), autoBranchCreation: {}, + customResponseHeaders: [ + { + pattern: '*.json', + headers: { + 'custom-header-name-1': 'custom-header-value-1', + 'custom-header-name-2': 'custom-header-value-2', + }, + }, + { + pattern: '/path/*', + headers: { + 'custom-header-name-1': 'custom-header-value-2', + }, + }, + ], }); amplifyApp.addCustomRule({ diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts index 850087920f152..96b9a5aced9e1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts @@ -3,6 +3,10 @@ import * as s3_assets from '@aws-cdk/aws-s3-assets'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order +import * as cxapi from '@aws-cdk/cx-api'; +import { Node } from 'constructs'; +import { CfnRestApi } from './apigateway.generated'; +import { IRestApi } from './restapi'; import { Construct } from '@aws-cdk/core'; /** @@ -82,6 +86,15 @@ export abstract class ApiDefinition { * assume it's initialized. You may just use it as a construct scope. */ public abstract bind(scope: Construct): ApiDefinitionConfig; + + /** + * Called after the CFN RestApi resource has been created to allow the Api + * Definition to bind to it. Specifically it's required to allow assets to add + * metadata for tooling like SAM CLI to be able to find their origins. + */ + public bindAfterCreate(_scope: Construct, _restApi: IRestApi) { + return; + } } /** @@ -198,4 +211,18 @@ export class AssetApiDefinition extends ApiDefinition { }, }; } + + public bindAfterCreate(scope: Construct, restApi: IRestApi) { + if (!scope.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + if (!this.asset) { + throw new Error('bindToResource() must be called after bind()'); + } + + const child = Node.of(restApi).defaultChild as CfnRestApi; + child.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PATH_KEY, this.asset.assetPath); + child.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY, 'BodyS3Location'); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index f48a01193385f..c6dd3e8c44dbd 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from './resource'; @@ -40,6 +40,13 @@ export interface ApiKeyOptions extends ResourceOptions { * @default none */ readonly value?: string; + + /** + * A description of the purpose of the API key. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-description + * @default none + */ + readonly description?: string; } /** @@ -59,13 +66,6 @@ export interface ApiKeyProps extends ApiKeyOptions { */ readonly customerId?: string; - /** - * A description of the purpose of the API key. - * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-description - * @default none - */ - readonly description?: string; - /** * Indicates whether the API key can be used by clients. * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-enabled @@ -146,7 +146,7 @@ export class ApiKey extends ApiKeyBase { service: 'apigateway', account: '', resource: '/apikeys', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: apiKeyId, }); } @@ -177,7 +177,7 @@ export class ApiKey extends ApiKeyBase { service: 'apigateway', account: '', resource: '/apikeys', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: this.keyId, }); } @@ -236,12 +236,12 @@ export class RateLimitedApiKey extends ApiKeyBase { const resource = new ApiKey(this, 'Resource', props); if (props.apiStages || props.quota || props.throttle) { - new UsagePlan(this, 'UsagePlanResource', { - apiKey: resource, + const usageplan = new UsagePlan(this, 'UsagePlanResource', { apiStages: props.apiStages, quota: props.quota, throttle: props.throttle, }); + usageplan.addApiKey(resource); } this.keyId = resource.keyId; diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 0e607e25a21cf..8b7ef8cccd565 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Integration, IntegrationConfig, IntegrationOptions, IntegrationType } from '../integration'; import { Method } from '../method'; import { parseAwsApiCall } from '../util'; @@ -92,7 +93,7 @@ export class AwsIntegration extends Integration { service: 'apigateway', account: backend, resource: apiType, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: apiValue, region: props.region, }); diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 919d27e6573be..5a99d03270f41 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -1,4 +1,4 @@ -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { Authorizer, IAuthorizer } from './authorizer'; @@ -275,7 +275,7 @@ export class Method extends Resource { } else if (options.credentialsPassthrough) { // arn:aws:iam::*:user/* // eslint-disable-next-line max-len - credentials = Stack.of(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); + credentials = Stack.of(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: '*' }); } return { @@ -346,7 +346,7 @@ export class Method extends Resource { } if (options.requestValidatorOptions) { - const validator = this.restApi.addRequestValidator('validator', options.requestValidatorOptions); + const validator = (this.api as RestApi).addRequestValidator('validator', options.requestValidatorOptions); return validator.requestValidatorId; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index e213ddad7f22f..0ed981690af3c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,7 +1,7 @@ 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'; +import { ArnFormat, CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; @@ -373,7 +373,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { return Stack.of(this).formatArn({ service: 'execute-api', resource: this.restApiId, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${stage}/${method}${path}`, }); } @@ -405,7 +405,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { return new cloudwatch.Metric({ namespace: 'AWS/ApiGateway', metricName, - dimensions: { ApiName: this.restApiName }, + dimensionsMap: { ApiName: this.restApiName }, ...props, }).attachTo(this); } @@ -626,6 +626,9 @@ export class SpecRestApi extends RestApiBase { endpointConfiguration: this._configureEndpoints(props), parameters: props.parameters, }); + + props.apiDefinition.bindAfterCreate(this, this); + this.node.defaultChild = resource; this.restApiId = resource.ref; this.restApiRootResourceId = resource.attrRootResourceId; diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 132082167f0f6..d20ad7f17d5f5 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,7 +84,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts b/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts index 709900b36c71d..c4af056038451 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts @@ -1,7 +1,9 @@ import '@aws-cdk/assert-internal/jest'; import * as path from 'path'; +import { ResourcePart } from '@aws-cdk/assert-internal'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import * as apigw from '../lib'; describe('api definition', () => { @@ -73,6 +75,23 @@ describe('api definition', () => { expect(synthesized.assets.length).toEqual(1); }); + + test('asset metadata added to RestApi resource that contains Asset Api Definition', () => { + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + const assetApiDefinition = apigw.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')); + new apigw.SpecRestApi(stack, 'API', { + apiDefinition: assetApiDefinition, + }); + + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', { + Metadata: { + 'aws:asset:path': 'asset.68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb.yaml', + 'aws:asset:property': 'BodyS3Location', + }, + }, ResourcePart.CompleteDefinition); + + }); }); describe('apigateway.ApiDefinition.fromBucket', () => { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts b/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts index f4f348a2a3d23..a929519d39c5a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts @@ -41,6 +41,23 @@ describe('api key', () => { }); }); + test('add description to apiKey', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api'); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + api.addApiKey('my-api-key', { + description: 'The most secret api key', + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::ApiKey', { + Description: 'The most secret api key', + }); + }); + test('use an imported api key', () => { // GIVEN const stack = new cdk.Stack(); @@ -49,9 +66,8 @@ describe('api key', () => { // WHEN const importedKey = apigateway.ApiKey.fromApiKeyId(stack, 'imported', 'KeyIdabc'); - api.addUsagePlan('plan', { - apiKey: importedKey, - }); + const usagePlan = api.addUsagePlan('plan'); + usagePlan.addApiKey(importedKey); // THEN expect(stack).toHaveResourceLike('AWS::ApiGateway::UsagePlanKey', { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json index e976e6426e0a5..e59d8e02743fc 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" @@ -313,4 +313,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts index d8160e3be6f49..066badbe4e0bb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts @@ -15,7 +15,7 @@ const app = new App(); const stack = new Stack(app, 'RequestAuthorizerInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.request-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json index 1f3a157fc36b9..27e2f89e8f631 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts index 47d05cf481009..5890d03f9bc3a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts @@ -16,7 +16,7 @@ const app = new App(); const stack = new Stack(app, 'TokenAuthorizerIAMRoleInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.token-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json index bb4c493fac04b..8a56511175436 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" @@ -313,4 +313,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts index e62e476e8cd4a..655fa91962b1a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts @@ -15,7 +15,7 @@ const app = new App(); const stack = new Stack(app, 'TokenAuthorizerInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.token-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/domains.test.ts b/packages/@aws-cdk/aws-apigateway/test/domains.test.ts index 7ad0f4224d70b..7b8817df48853 100644 --- a/packages/@aws-cdk/aws-apigateway/test/domains.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/domains.test.ts @@ -388,7 +388,7 @@ describe('domains', () => { test('accepts a mutual TLS configuration', () => { const stack = new Stack(); - const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); new apigw.DomainName(stack, 'another-domain', { domainName: 'example.com', mtls: { @@ -402,14 +402,14 @@ describe('domains', () => { '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' }, + 'MutualTlsAuthentication': { 'TruststoreUri': 's3://example-bucket/someca.pem' }, }); }); test('mTLS should allow versions to be set on the s3 bucket', () => { const stack = new Stack(); - const bucket = Bucket.fromBucketName(stack, 'testBucket', 'exampleBucket'); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); 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'), @@ -423,7 +423,7 @@ describe('domains', () => { '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' }, + 'MutualTlsAuthentication': { 'TruststoreUri': 's3://example-bucket/someca.pem', 'TruststoreVersion': 'version' }, }); }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json index 6d17b2e53232e..146d5220f7540 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json @@ -564,14 +564,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "handlerServiceRole187D5A5A", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "handlerServiceRole187D5A5A" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts index 5b3fc8bdc36c2..e5617f4c9b202 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts @@ -12,7 +12,7 @@ class TestStack extends Stack { const api = new apigw.RestApi(this, 'cors-api-test'); const handler = new lambda.Function(this, 'handler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'integ.cors.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json index 17dd7ccf222e8..8cf5ef7c0e552 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "foo" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myfnServiceRole7822DC24", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "myfnServiceRole7822DC24" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts index d6176fdb78301..615c934f918fe 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts @@ -9,7 +9,7 @@ class LateBoundDeploymentStageStack extends Stack { const fn = new Function(this, 'myfn', { code: Code.fromInline('foo'), - runtime: Runtime.NODEJS_10_X, + runtime: Runtime.NODEJS_14_X, handler: 'index.handler', }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json index 91af30b6ef8d4..a77027807057d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "exports.handler = function echoHandlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "BooksHandlerServiceRole5B6A8847", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "BooksHandlerServiceRole5B6A8847" @@ -87,14 +87,14 @@ "Code": { "ZipFile": "exports.handler = function echoHandlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "BookHandlerServiceRole894768AD", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "BookHandlerServiceRole894768AD" @@ -137,14 +137,14 @@ "Code": { "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "HelloServiceRole1E55EA16", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "HelloServiceRole1E55EA16" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts index 9c647c83dcebd..0ec001e3d1a3a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts @@ -7,19 +7,19 @@ class BookStack extends cdk.Stack { super(scope, id); const booksHandler = new apigw.LambdaIntegration(new lambda.Function(this, 'BooksHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${echoHandlerCode}`), })); const bookHandler = new apigw.LambdaIntegration(new lambda.Function(this, 'BookHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${echoHandlerCode}`), })); const hello = new apigw.LambdaIntegration(new lambda.Function(this, 'Hello', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${helloCode}`), })); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index a0fb6357db3c7..606c5f2098a0d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -651,14 +651,14 @@ "Code": { "ZipFile": "exports.handler = function handlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyHandlerServiceRoleFFA06653", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyHandlerServiceRoleFFA06653" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json index 5aa57732955bc..198d0e80231ae 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json @@ -38,15 +38,15 @@ "Code": { "ZipFile": "exports.handler = async function(event) {\n return {\n 'headers': { 'Content-Type': 'text/plain' },\n 'statusCode': 200\n }\n }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "firstLambdaServiceRoleB6408C31", "Arn" ] }, - "Runtime": "nodejs10.x", - "FunctionName": "FirstLambda" + "FunctionName": "FirstLambda", + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "firstLambdaServiceRoleB6408C31" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts index 0ac4e01241eba..75b6e219613de 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts @@ -20,7 +20,7 @@ class FirstStack extends cdk.Stack { } }`), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, }); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json index 6a7cea680ef60..0ed3cb19c6aad 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "HelloServiceRole1E55EA16", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "HelloServiceRole1E55EA16" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts index a248bf9041158..612bd0e83b963 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts @@ -7,7 +7,7 @@ class MultiStack extends cdk.Stack { super(scope, id); const hello = new apigw.LambdaIntegration(new lambda.Function(this, 'Hello', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${helloCode}`), })); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index 10e3d357108d7..537ec3031d58a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -23,7 +23,7 @@ class Test extends cdk.Stack { }); const handler = new lambda.Function(this, 'MyHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, code: lambda.Code.fromInline(`exports.handler = ${handlerCode}`), handler: 'index.handler', }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json index 9051ff580c010..4279ff70893aa 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json @@ -95,15 +95,15 @@ "MyVpcPublicSubnet1NATGatewayAD3400C1": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet1EIP096967CB", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet1SubnetF6608456" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "MyVpcPublicSubnet2NATGateway91BFBEC9": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet2EIP8CCBA239", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "MyVpcPublicSubnet3NATGatewayD4B50EBE": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet3EIPC5ACADAB", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet3Subnet57EEE236" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts b/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts index 0babe3d5671e3..7e412c549e897 100644 --- a/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts @@ -180,7 +180,7 @@ describe('lambda api', () => { expect(() => new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler, - options: { defaultIntegration: new apigw.HttpIntegration('https://foo/bar') }, + defaultIntegration: new apigw.HttpIntegration('https://foo/bar'), })).toThrow(/Cannot specify \"defaultIntegration\" since Lambda integration is automatically defined/); expect(() => new apigw.LambdaRestApi(stack, 'lambda-rest-api', { diff --git a/packages/@aws-cdk/aws-apigateway/test/method.test.ts b/packages/@aws-cdk/aws-apigateway/test/method.test.ts index fdddb91777755..f1037e550e23a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/method.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/method.test.ts @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { ABSENT } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as apigw from '../lib'; @@ -902,7 +903,7 @@ describe('method', () => { }); - test('"restApi" and "api" properties return the RestApi correctly', () => { + testDeprecated('"restApi" and "api" properties return the RestApi correctly', () => { // GIVEN const stack = new cdk.Stack(); @@ -918,7 +919,7 @@ describe('method', () => { }); - test('"restApi" throws an error on imported while "api" returns correctly', () => { + testDeprecated('"restApi" throws an error on imported while "api" returns correctly', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts index 36d3fff2ccdab..b118c328073cb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts @@ -201,7 +201,7 @@ describe('resource', () => { const cResource = aResource.addResource('b').addResource('c'); // THEN - expect(stack.resolve(aResource.url)).toEqual({ + expect(stack.resolve(api.urlForPath(aResource.path))).toEqual({ 'Fn::Join': [ '', [ @@ -217,7 +217,7 @@ describe('resource', () => { ], ], }); - expect(stack.resolve(cResource.url)).toEqual({ + expect(stack.resolve(api.urlForPath(cResource.path))).toEqual({ 'Fn::Join': [ '', [ diff --git a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts index 60cc707468c4c..d6f17c21df8da 100644 --- a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import { ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; import { GatewayVpcEndpoint } from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnElement, CfnResource, Stack } from '@aws-cdk/core'; import * as apigw from '../lib'; @@ -807,7 +808,7 @@ describe('restapi', () => { }); }); - test('"restApi" and "api" properties return the RestApi correctly', () => { + testDeprecated('"restApi" and "api" properties return the RestApi correctly', () => { // GIVEN const stack = new Stack(); @@ -821,7 +822,7 @@ describe('restapi', () => { expect(stack.resolve(method.api.restApiId)).toEqual(stack.resolve(method.restApi.restApiId)); }); - test('"restApi" throws an error on imported while "api" returns correctly', () => { + testDeprecated('"restApi" throws an error on imported while "api" returns correctly', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md index 7dd9c2f5e61bd..7da981240612e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md @@ -46,11 +46,14 @@ When using default authorization, all routes of the api will inherit the configu In the example below, all routes will require the `manage:books` scope present in order to invoke the integration. ```ts +import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; + const authorizer = new HttpJwtAuthorizer({ - ... + jwtAudience: ['3131231'], + jwtIssuer: 'https://test.us.auth0.com', }); -const api = new HttpApi(stack, 'HttpApi', { +const api = new apigwv2.HttpApi(this, 'HttpApi', { defaultAuthorizer: authorizer, defaultAuthorizationScopes: ['manage:books'], }); @@ -67,39 +70,51 @@ The example below showcases default authorization, along with route authorizatio - `POST /login` removes the default authorizer (unauthenticated route) ```ts +import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + const authorizer = new HttpJwtAuthorizer({ - ... + jwtAudience: ['3131231'], + jwtIssuer: 'https://test.us.auth0.com', }); -const api = new HttpApi(stack, 'HttpApi', { +const api = new apigwv2.HttpApi(this, 'HttpApi', { defaultAuthorizer: authorizer, defaultAuthorizationScopes: ['read:books'], }); api.addRoutes({ - ... + integration: new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', + }), path: '/books', - method: 'get', + methods: [apigwv2.HttpMethod.GET], }); api.addRoutes({ - ... + integration: new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', + }), path: '/books/{id}', - method: 'get', + methods: [apigwv2.HttpMethod.GET], }); api.addRoutes({ - ... + integration: new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', + }), path: '/books', - method: 'post', + methods: [apigwv2.HttpMethod.POST], authorizationScopes: ['write:books'] }); api.addRoutes({ - ... + integration: new HttpProxyIntegration({ + url: 'https://get-books-proxy.myproxy.internal', + }), path: '/login', - method: 'post', - authorizer: new NoneAuthorizer(), + methods: [apigwv2.HttpMethod.POST], + authorizer: new apigwv2.HttpNoneAuthorizer(), }); ``` @@ -120,12 +135,15 @@ Clients that fail authorization are presented with either 2 responses: - `403 - Forbidden` - When the JWT validation is successful but the required scopes are not met ```ts +import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + const authorizer = new HttpJwtAuthorizer({ jwtAudience: ['3131231'], jwtIssuer: 'https://test.us.auth0.com', }); -const api = new HttpApi(stack, 'HttpApi'); +const api = new apigwv2.HttpApi(this, 'HttpApi'); api.addRoutes({ integration: new HttpProxyIntegration({ @@ -145,15 +163,19 @@ They must then use this token in the specified `identitySource` for the API call pools as authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). ```ts -const userPool = new UserPool(stack, 'UserPool'); +import * as cognito from '@aws-cdk/aws-cognito'; +import { HttpUserPoolAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +const userPool = new cognito.UserPool(this, 'UserPool'); const userPoolClient = userPool.addClient('UserPoolClient'); const authorizer = new HttpUserPoolAuthorizer({ userPool, - userPoolClient, + userPoolClients: [userPoolClient], }); -const api = new HttpApi(stack, 'HttpApi'); +const api = new apigwv2.HttpApi(this, 'HttpApi'); api.addRoutes({ integration: new HttpProxyIntegration({ @@ -172,17 +194,19 @@ Lambda authorizers depending on their response, fall into either two types - Sim ```ts +import { HttpLambdaAuthorizer, HttpLambdaResponseType } from '@aws-cdk/aws-apigatewayv2-authorizers'; +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + // This function handles your auth logic -const authHandler = new Function(this, 'auth-function', { - //... -}); +declare const authHandler: lambda.Function; const authorizer = new HttpLambdaAuthorizer({ - responseTypes: [HttpLambdaResponseType.SIMPLE] // Define if returns simple and/or iam response + authorizerName: 'lambda-authorizer', + responseTypes: [HttpLambdaResponseType.SIMPLE], // Define if returns simple and/or iam response handler: authHandler, }); -const api = new HttpApi(stack, 'HttpApi'); +const api = new apigwv2.HttpApi(this, 'HttpApi'); api.addRoutes({ integration: new HttpProxyIntegration({ diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts index 702a3a05576ec..21cef2e478756 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/user-pool.ts @@ -7,9 +7,9 @@ import { Stack, Token } from '@aws-cdk/core'; */ export interface UserPoolAuthorizerProps { /** - * The user pool client that should be used to authorize requests with the user pool. + * The user pool clients that should be used to authorize requests with the user pool. */ - readonly userPoolClient: IUserPoolClient; + readonly userPoolClients: IUserPoolClient[]; /** * The associated user pool @@ -33,7 +33,7 @@ export interface UserPoolAuthorizerProps { * * @default ['$request.header.Authorization'] */ - readonly identitySource?: string[], + readonly identitySource?: string[]; } /** @@ -56,7 +56,7 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer { identitySource: this.props.identitySource ?? ['$request.header.Authorization'], type: HttpAuthorizerType.JWT, authorizerName: this.props.authorizerName, - jwtAudience: [this.props.userPoolClient.userPoolClientId], + jwtAudience: this.props.userPoolClients.map((c) => c.userPoolClientId), jwtIssuer: `https://cognito-idp.${region}.amazonaws.com/${this.props.userPool.userPoolId}`, }); } @@ -66,4 +66,4 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer { authorizationType: 'JWT', }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index cc9588ca09adc..8465cb5e8a9a5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -30,7 +30,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,8 +85,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24" + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-apigatewayv2": "0.0.0", diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-apigatewayv2-authorizers/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..3d0f887754334 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as apigwv2 from '@aws-cdk/aws-apigatewayv2'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json index 69c407f274908..6f0d30c71ad66 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json @@ -101,9 +101,6 @@ "Ref": "MyHttpApi8AEAAC21" }, "AuthorizerType": "REQUEST", - "IdentitySource": [ - "$request.header.X-API-Key" - ], "Name": "my-simple-authorizer", "AuthorizerPayloadFormatVersion": "2.0", "AuthorizerResultTtlInSeconds": 300, @@ -130,7 +127,10 @@ ] ] }, - "EnableSimpleResponses": true + "EnableSimpleResponses": true, + "IdentitySource": [ + "$request.header.X-API-Key" + ] } }, "MyHttpApiAuthorizerIntegMyHttpApimysimpleauthorizer0F14A472PermissionF37EF5C8": { diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.user-pool.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.user-pool.ts index edf455f4a787c..3e607b4474365 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.user-pool.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.user-pool.ts @@ -25,7 +25,7 @@ const userPoolClient = userPool.addClient('my-client'); const authorizer = new HttpUserPoolAuthorizer({ userPool, - userPoolClient, + userPoolClients: [userPoolClient], }); const handler = new lambda.Function(stack, 'lambda', { diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/user-pool.test.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/user-pool.test.ts index 0e3c339e7f744..127b389b8b0f2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/user-pool.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/user-pool.test.ts @@ -13,7 +13,7 @@ describe('HttpUserPoolAuthorizer', () => { const userPoolClient = userPool.addClient('UserPoolClient'); const authorizer = new HttpUserPoolAuthorizer({ userPool, - userPoolClient, + userPoolClients: [userPoolClient], }); // WHEN @@ -52,7 +52,7 @@ describe('HttpUserPoolAuthorizer', () => { const userPoolClient = userPool.addClient('UserPoolClient'); const authorizer = new HttpUserPoolAuthorizer({ userPool, - userPoolClient, + userPoolClients: [userPoolClient], }); // WHEN @@ -70,6 +70,46 @@ describe('HttpUserPoolAuthorizer', () => { // THEN Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::Authorizer', 1); }); + + test('multiple userPoolClients are attached', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + const userPool = new UserPool(stack, 'UserPool'); + const userPoolClient1 = userPool.addClient('UserPoolClient1'); + const userPoolClient2 = userPool.addClient('UserPoolClient2'); + const authorizer = new HttpUserPoolAuthorizer({ + userPool, + userPoolClients: [userPoolClient1, userPoolClient2], + }); + + // WHEN + api.addRoutes({ + integration: new DummyRouteIntegration(), + path: '/books', + authorizer, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', { + AuthorizerType: 'JWT', + IdentitySource: ['$request.header.Authorization'], + JwtConfiguration: { + Audience: [stack.resolve(userPoolClient1.userPoolClientId), stack.resolve(userPoolClient2.userPoolClientId)], + Issuer: { + 'Fn::Join': [ + '', + [ + 'https://cognito-idp.', + { Ref: 'AWS::Region' }, + '.amazonaws.com/', + stack.resolve(userPool.userPoolId), + ], + ], + }, + }, + }); + }); }); class DummyRouteIntegration implements IHttpRouteIntegration { diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index cce77fd6398e6..d284be9491e99 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -21,6 +21,7 @@ - [Lambda Integration](#lambda) - [HTTP Proxy Integration](#http-proxy) - [Private Integration](#private-integration) + - [Request Parameters](#request-parameters) - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) @@ -40,16 +41,18 @@ proxy integrations](https://docs.aws.amazon.com/apigateway/latest/developerguide The following code configures a route `GET /books` with a Lambda proxy integration. ```ts -const booksFn = new lambda.Function(stack, 'BooksDefaultFn', { ... }); +import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const booksDefaultFn: lambda.Function; const booksIntegration = new LambdaProxyIntegration({ handler: booksDefaultFn, }); -const httpApi = new HttpApi(stack, 'HttpApi'); +const httpApi = new apigwv2.HttpApi(this, 'HttpApi'); httpApi.addRoutes({ path: '/books', - methods: [ HttpMethod.GET ], + methods: [ apigwv2.HttpMethod.GET ], integration: booksIntegration, }); ``` @@ -65,15 +68,17 @@ The following code configures a route `GET /books` with an HTTP proxy integratio `get-books-proxy.myproxy.internal`. ```ts +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + const booksIntegration = new HttpProxyIntegration({ url: 'https://get-books-proxy.myproxy.internal', }); -const httpApi = new HttpApi(stack, 'HttpApi'); +const httpApi = new apigwv2.HttpApi(this, 'HttpApi'); httpApi.addRoutes({ path: '/books', - methods: [ HttpMethod.GET ], + methods: [ apigwv2.HttpMethod.GET ], integration: booksIntegration, }); ``` @@ -91,14 +96,16 @@ The following integrations are supported for private resources in a VPC. The following code is a basic application load balancer private integration of HTTP API: ```ts -const vpc = new ec2.Vpc(stack, 'VPC'); -const lb = new elbv2.ALB(stack, 'lb', { vpc }); +import { HttpAlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +const vpc = new ec2.Vpc(this, 'VPC'); +const lb = new elbv2.ApplicationLoadBalancer(this, 'lb', { vpc }); const listener = lb.addListener('listener', { port: 80 }); listener.addTargets('target', { port: 80, }); -const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { +const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ listener, }), @@ -112,14 +119,16 @@ When an imported load balancer is used, the `vpc` option must be specified for ` The following code is a basic network load balancer private integration of HTTP API: ```ts -const vpc = new ec2.Vpc(stack, 'VPC'); -const lb = new elbv2.NLB(stack, 'lb', { vpc }); +import { HttpNlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +const vpc = new ec2.Vpc(this, 'VPC'); +const lb = new elbv2.NetworkLoadBalancer(this, 'lb', { vpc }); const listener = lb.addListener('listener', { port: 80 }); listener.addTargets('target', { port: 80, }); -const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { +const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { defaultIntegration: new HttpNlbIntegration({ listener, }), @@ -133,15 +142,18 @@ When an imported load balancer is used, the `vpc` option must be specified for ` The following code is a basic discovery service private integration of HTTP API: ```ts -const vpc = new ec2.Vpc(stack, 'VPC'); -const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); -const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { +import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; +import { HttpServiceDiscoveryIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +const vpc = new ec2.Vpc(this, 'VPC'); +const vpcLink = new apigwv2.VpcLink(this, 'VpcLink', { vpc }); +const namespace = new servicediscovery.PrivateDnsNamespace(this, 'Namespace', { name: 'boobar.com', vpc, }); const service = namespace.createService('Service'); -const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { +const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { defaultIntegration: new HttpServiceDiscoveryIntegration({ vpcLink, service, @@ -149,13 +161,60 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { }); ``` +### Request Parameters + +Request parameter mapping allows API requests from clients to be modified before they reach backend integrations. +Parameter mapping can be used to specify modifications to request parameters. See [Transforming API requests and +responses](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html). + +The following example creates a new header - `header2` - as a copy of `header1` and removes `header1`. + +```ts +import { HttpAlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const lb: elbv2.ApplicationLoadBalancer; +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { + port: 80, +}); + +const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegration({ + listener, + parameterMapping: new apigwv2.ParameterMapping() + .appendHeader('header2', apigwv2.MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), +}); +``` + +To add mapping keys and values not yet supported by the CDK, use the `custom()` method: + +```ts +import { HttpAlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const lb: elbv2.ApplicationLoadBalancer; +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { + port: 80, +}); + +const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegration({ + listener, + parameterMapping: new apigwv2.ParameterMapping().custom('myKey', 'myValue'), + }), +}); +``` + + ## WebSocket APIs WebSocket integrations connect a route to backend resources. The following integrations are supported in the CDK. ### Lambda WebSocket Integration -Lambda integrations enable integrating a WebSocket API route with a Lambda function. When a client connects/disconnects +Lambda integrations enable integrating a WebSocket API route with a Lambda function. When a client connects/disconnects or sends message specific to a route, the API Gateway service forwards the request to the Lambda function The API Gateway service will invoke the lambda function with an event payload of a specific format. @@ -163,17 +222,19 @@ The API Gateway service will invoke the lambda function with an event payload of The following code configures a `sendmessage` route with a Lambda integration ```ts -const webSocketApi = new WebSocketApi(stack, 'mywsapi'); -new WebSocketStage(stack, 'mystage', { +import { LambdaWebSocketIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi'); +new apigwv2.WebSocketStage(this, 'mystage', { webSocketApi, stageName: 'dev', autoDeploy: true, }); -const messageHandler = new lambda.Function(stack, 'MessageHandler', {...}); +declare const messageHandler: lambda.Function; webSocketApi.addRoute('sendmessage', { integration: new LambdaWebSocketIntegration({ - handler: connectHandler, + handler: messageHandler, }), }); ``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts index 656e0a550408f..e5e6d5c448663 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts @@ -44,6 +44,7 @@ export class HttpAlbIntegration extends HttpPrivateIntegration { connectionId: vpcLink.vpcLinkId, uri: this.props.listener.listenerArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts index db14e50f7fc54..1627b9b0c4deb 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts @@ -1,4 +1,4 @@ -import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpMethod, IVpcLink, ParameterMapping } from '@aws-cdk/aws-apigatewayv2'; /** * Base options for private integration @@ -24,4 +24,11 @@ export interface HttpPrivateIntegrationOptions { */ readonly secureServerName?: string; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts index a7ef2d1b4d7b9..70873c9582fc8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts @@ -4,6 +4,7 @@ import { HttpRouteIntegrationConfig, HttpMethod, IHttpRouteIntegration, + ParameterMapping, PayloadFormatVersion, } from '@aws-cdk/aws-apigatewayv2'; @@ -21,6 +22,13 @@ export interface HttpProxyIntegrationProps { * @default HttpMethod.ANY */ readonly method?: HttpMethod; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } /** @@ -36,6 +44,7 @@ export class HttpProxyIntegration implements IHttpRouteIntegration { payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format type: HttpIntegrationType.HTTP_PROXY, uri: this.props.url, + parameterMapping: this.props.parameterMapping, }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts index 220d3dca57210..358263f724bda 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts @@ -4,6 +4,7 @@ import { HttpRouteIntegrationConfig, IHttpRouteIntegration, PayloadFormatVersion, + ParameterMapping, } from '@aws-cdk/aws-apigatewayv2'; import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; @@ -24,6 +25,13 @@ export interface LambdaProxyIntegrationProps { * @default PayloadFormatVersion.VERSION_2_0 */ readonly payloadFormatVersion?: PayloadFormatVersion; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } /** @@ -50,6 +58,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { type: HttpIntegrationType.LAMBDA_PROXY, uri: this.props.handler.functionArn, payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0, + parameterMapping: this.props.parameterMapping, }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts index 1c405b51b3bfd..7aae0aa002354 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts @@ -44,6 +44,7 @@ export class HttpNlbIntegration extends HttpPrivateIntegration { connectionId: vpcLink.vpcLinkId, uri: this.props.listener.listenerArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts index f9f204b6eba3e..6f3b8eedbea0a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts @@ -34,6 +34,7 @@ export class HttpServiceDiscoveryIntegration extends HttpPrivateIntegration { connectionId: this.props.vpcLink.vpcLinkId, uri: this.props.service.serviceArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json index a950d542429e0..203793018a92a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -74,7 +81,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-apigatewayv2": "0.0.0", diff --git a/packages/monocdk/rosetta/with-cluster.ts-fixture b/packages/@aws-cdk/aws-apigatewayv2-integrations/rosetta/default.ts-fixture similarity index 50% rename from packages/monocdk/rosetta/with-cluster.ts-fixture rename to packages/@aws-cdk/aws-apigatewayv2-integrations/rosetta/default.ts-fixture index c638d8b4d04fa..d5ba330e26005 100644 --- a/packages/monocdk/rosetta/with-cluster.ts-fixture +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/rosetta/default.ts-fixture @@ -1,19 +1,15 @@ -import { Duration, Stack } from '@aws-cdk/core'; +// Fixture with packages imported, but nothing else import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as apigwv2 from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; -import * as neptune from '@aws-cdk/aws-neptune'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as lambda from '@aws-cdk/aws-lambda'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 2 }); - - const cluster = new neptune.DatabaseCluster(this, 'Database', { - vpc, - instanceType: neptune.InstanceType.R5_LARGE, - }); - /// here } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts index e5871da260bc2..3c25e92fe6d21 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink, ParameterMapping, MappingValue } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { Stack } from '@aws-cdk/core'; @@ -143,4 +143,34 @@ describe('HttpAlbIntegration', () => { }, }); }); + + test('parameterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpAlbIntegration({ + listener, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts index 0c76996fe7867..0f29ec0915fd9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; import { Stack } from '@aws-cdk/core'; import { HttpProxyIntegration } from '../../lib'; @@ -71,4 +71,26 @@ describe('HttpProxyIntegration', () => { IntegrationUri: 'some-target-url', }); }); + + test('parameterMapping is correctly recognized', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpIntegration(stack, 'HttpInteg', { + httpApi: api, + integrationType: HttpIntegrationType.HTTP_PROXY, + integrationUri: 'some-target-url', + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationUri: 'some-target-url', + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json index b9f5d96ff4656..65149587fb3ce 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - }, "Tags": [ { "Key": "Name", @@ -596,6 +596,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts index d0ead43945ec4..85bb624a25d54 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; import { LambdaProxyIntegration } from '../../lib'; @@ -41,6 +41,28 @@ describe('LambdaProxyIntegration', () => { }); }); + test('parameterMapping selection', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'LambdaProxyRoute', { + httpApi: api, + integration: new LambdaProxyIntegration({ + handler: fooFunction(stack, 'Fn'), + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); + test('no dependency cycles', () => { const app = new App(); const lambdaStack = new Stack(app, 'lambdaStack'); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts index a32d448d8e448..e1e18c43f49aa 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { Stack } from '@aws-cdk/core'; @@ -140,4 +140,34 @@ describe('HttpNlbIntegration', () => { }, }); }); + + test('paramaterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpNlbIntegration({ + listener, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts index 4d3bef328a637..e037004cada0e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; import { Stack } from '@aws-cdk/core'; @@ -125,4 +125,38 @@ describe('HttpServiceDiscoveryIntegration', () => { }, }); }); + + test('parameterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'foobar.com', + vpc, + }); + const service = namespace.createService('Service'); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index cf0a2ab0552c7..cc6c6f48c5827 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -34,11 +34,13 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) + - [Mutual TLS](#mutual-tls-mtls) - [Managing access](#managing-access) - [Metrics](#metrics) - [VPC Link](#vpc-link) - [Private Integration](#private-integration) - [WebSocket API](#websocket-api) + - [Manage Connections Permission](#manage-connections-permission) ## Introduction @@ -74,25 +76,27 @@ As an early example, the following code snippet configures a route `GET /books` configures all other HTTP method calls to `/books` to a lambda proxy. ```ts +import { HttpProxyIntegration, LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + const getBooksIntegration = new HttpProxyIntegration({ url: 'https://get-books-proxy.myproxy.internal', }); -const booksDefaultFn = new lambda.Function(stack, 'BooksDefaultFn', { ... }); +declare const booksDefaultFn: lambda.Function; const booksDefaultIntegration = new LambdaProxyIntegration({ handler: booksDefaultFn, }); -const httpApi = new HttpApi(stack, 'HttpApi'); +const httpApi = new apigwv2.HttpApi(this, 'HttpApi'); httpApi.addRoutes({ path: '/books', - methods: [ HttpMethod.GET ], + methods: [ apigwv2.HttpMethod.GET ], integration: getBooksIntegration, }); httpApi.addRoutes({ path: '/books', - methods: [ HttpMethod.ANY ], + methods: [ apigwv2.HttpMethod.ANY ], integration: booksDefaultIntegration, }); ``` @@ -100,7 +104,7 @@ httpApi.addRoutes({ The URL to the endpoint can be retrieved via the `apiEndpoint` attribute. By default this URL is enabled for clients. Use `disableExecuteApiEndpoint` to disable it. ```ts -const httpApi = new HttpApi(stack, 'HttpApi', { +const httpApi = new apigwv2.HttpApi(this, 'HttpApi', { disableExecuteApiEndpoint: true, }); ``` @@ -109,7 +113,9 @@ The `defaultIntegration` option while defining HTTP APIs lets you create a defau matched when a client reaches a route that is not explicitly defined. ```ts -new HttpApi(stack, 'HttpProxyApi', { +import { HttpProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +new apigwv2.HttpApi(this, 'HttpProxyApi', { defaultIntegration: new HttpProxyIntegration({ url:'http://example.com', }), @@ -130,10 +136,15 @@ API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors. The `corsPreflight` option lets you specify a CORS configuration for an API. ```ts -new HttpApi(stack, 'HttpProxyApi', { +new apigwv2.HttpApi(this, 'HttpProxyApi', { corsPreflight: { allowHeaders: ['Authorization'], - allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.HEAD, CorsHttpMethod.OPTIONS, CorsHttpMethod.POST], + allowMethods: [ + apigwv2.CorsHttpMethod.GET, + apigwv2.CorsHttpMethod.HEAD, + apigwv2.CorsHttpMethod.OPTIONS, + apigwv2.CorsHttpMethod.POST, + ], allowOrigins: ['*'], maxAge: Duration.days(10), }, @@ -150,7 +161,9 @@ Use `HttpStage` to create a Stage resource for HTTP APIs. The following code set `https://{api_id}.execute-api.{region}.amazonaws.com/beta`. ```ts -new HttpStage(stack, 'Stage', { +declare const api: apigwv2.HttpApi; + +new apigwv2.HttpStage(this, 'Stage', { httpApi: api, stageName: 'beta', }); @@ -169,15 +182,19 @@ The code snippet below creates a custom domain and configures a default domain m custom domain to the `$default` stage of the API. ```ts +import * as acm from '@aws-cdk/aws-certificatemanager'; +import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; const domainName = 'example.com'; -const dn = new DomainName(stack, 'DN', { +const dn = new apigwv2.DomainName(this, 'DN', { domainName, - certificate: acm.Certificate.fromCertificateArn(stack, 'cert', certArn), + certificate: acm.Certificate.fromCertificateArn(this, 'cert', certArn), }); -const api = new HttpApi(stack, 'HttpProxyProdApi', { +declare const handler: lambda.Function; +const api = new apigwv2.HttpApi(this, 'HttpProxyProdApi', { defaultIntegration: new LambdaProxyIntegration({ handler }), // https://${dn.domainName}/foo goes to prodApi $default stage defaultDomainMapping: { @@ -190,6 +207,9 @@ const api = new HttpApi(stack, 'HttpProxyProdApi', { To associate a specific `Stage` to a custom domain mapping - ```ts +declare const api: apigwv2.HttpApi; +declare const dn: apigwv2.DomainName; + api.addStage('beta', { stageName: 'beta', autoDeploy: true, @@ -204,7 +224,12 @@ api.addStage('beta', { The same domain name can be associated with stages across different `HttpApi` as so - ```ts -const apiDemo = new HttpApi(stack, 'DemoApi', { +import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const handler: lambda.Function; +declare const dn: apigwv2.DomainName; + +const apiDemo = new apigwv2.HttpApi(this, 'DemoApi', { defaultIntegration: new LambdaProxyIntegration({ handler }), // https://${dn.domainName}/demo goes to apiDemo $default stage defaultDomainMapping: { @@ -227,9 +252,33 @@ with 3 API mapping resources across different APIs and Stages. You can retrieve the full domain URL with mapping key using the `domainUrl` property as so - ```ts -const demoDomainUrl = apiDemo.defaultStage.domainUrl; // returns "https://example.com/demo" +declare const apiDemo: apigwv2.HttpApi; +const demoDomainUrl = apiDemo.defaultStage?.domainUrl; // returns "https://example.com/demo" ``` +## 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 +import * as s3 from '@aws-cdk/aws-s3'; +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; +const domainName = 'example.com'; +const bucket = new s3.Bucket.fromBucketName(stack, 'TrustStoreBucket', ...); + +new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.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/) + ### Managing access API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP @@ -245,7 +294,7 @@ The APIs with the `metric` prefix can be used to get reference to specific metri the method below refers to the client side errors metric for this API. ```ts -const api = new apigw.HttpApi(stack, 'my-api'); +const api = new apigwv2.HttpApi(this, 'my-api'); const clientErrorMetric = api.metricClientError(); ``` @@ -253,9 +302,9 @@ Please note that this will return a metric for all the stages defined in the api the `metric` methods from the `Stage` construct. ```ts -const api = new apigw.HttpApi(stack, 'my-api'); -const stage = new HttpStage(stack, 'Stage', { - httpApi: api, +const api = new apigwv2.HttpApi(this, 'my-api'); +const stage = new apigwv2.HttpStage(this, 'Stage', { + httpApi: api, }); const clientErrorMetric = stage.metricClientError(); ``` @@ -267,14 +316,22 @@ Load Balancers, Network Load Balancers or a Cloud Map service. The `VpcLink` con The following code creates a `VpcLink` to a private VPC. ```ts -const vpc = new ec2.Vpc(stack, 'VPC'); -const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); +import * as ec2 from '@aws-cdk/aws-ec2'; + +const vpc = new ec2.Vpc(this, 'VPC'); +const vpcLink = new apigwv2.VpcLink(this, 'VpcLink', { vpc }); ``` -Any existing `VpcLink` resource can be imported into the CDK app via the `VpcLink.fromVpcLinkId()`. +Any existing `VpcLink` resource can be imported into the CDK app via the `VpcLink.fromVpcLinkAttributes()`. ```ts -const awesomeLink = VpcLink.fromVpcLinkId(stack, 'awesome-vpc-link', 'us-east-1_oiuR12Abd'); +import * as ec2 from '@aws-cdk/aws-ec2'; + +declare const vpc: ec2.Vpc; +const awesomeLink = apigwv2.VpcLink.fromVpcLinkAttributes(this, 'awesome-vpc-link', { + vpcLinkId: 'us-east-1_oiuR12Abd', + vpc, +}); ``` ### Private Integration @@ -304,13 +361,19 @@ Integrations are available in the `aws-apigatewayv2-integrations` module and mor To add the default WebSocket routes supported by API Gateway (`$connect`, `$disconnect` and `$default`), configure them as part of api props: ```ts -const webSocketApi = new WebSocketApi(stack, 'mywsapi', { +import { LambdaWebSocketIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const connectHandler: lambda.Function; +declare const disconnectHandler: lambda.Function; +declare const defaultHandler: lambda.Function; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi', { connectRouteOptions: { integration: new LambdaWebSocketIntegration({ handler: connectHandler }) }, - disconnectRouteOptions: { integration: new LambdaWebSocketIntegration({ handler: disconnetHandler }) }, + disconnectRouteOptions: { integration: new LambdaWebSocketIntegration({ handler: disconnectHandler }) }, defaultRouteOptions: { integration: new LambdaWebSocketIntegration({ handler: defaultHandler }) }, }); -new WebSocketStage(stack, 'mystage', { +new apigwv2.WebSocketStage(this, 'mystage', { webSocketApi, stageName: 'dev', autoDeploy: true, @@ -320,6 +383,8 @@ new WebSocketStage(stack, 'mystage', { To retrieve a websocket URL and a callback URL: ```ts +declare const webSocketStage: apigwv2.WebSocketStage; + const webSocketURL = webSocketStage.url; // wss://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath} const callbackURL = webSocketStage.callbackUrl; @@ -329,10 +394,32 @@ const callbackURL = webSocketStage.callbackUrl; To add any other route: ```ts -const webSocketApi = new WebSocketApi(stack, 'mywsapi'); +import { LambdaWebSocketIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; + +declare const messageHandler: lambda.Function; +const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi'); webSocketApi.addRoute('sendmessage', { integration: new LambdaWebSocketIntegration({ handler: messageHandler, }), }); ``` + +### Manage Connections Permission + +Grant permission to use API Gateway Management API of a WebSocket API by calling the `grantManageConnections` API. +You can use Management API to send a callback message to a connected client, get connection information, or disconnect the client. Learn more at [Use @connections commands in your backend service](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html). + +```ts +const lambda = new lambda.Function(this, 'lambda', { /* ... */ }); + +const webSocketApi = new WebSocketApi(stack, 'mywsapi'); +const stage = new WebSocketStage(stack, 'mystage', { + webSocketApi, + stageName: 'dev', +}); +// per stage permission +stage.grantManageConnections(lambda); +// for all the stages permission +webSocketApi.grantManageConnections(lambda); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts index 006df3f83cd2b..26d9ca64e391a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts @@ -21,7 +21,7 @@ export abstract class ApiBase extends Resource implements IApi { return new cloudwatch.Metric({ namespace: 'AWS/ApiGateway', metricName, - dimensions: { ApiId: this.apiId }, + dimensionsMap: { ApiId: this.apiId }, ...props, }).attachTo(this); } @@ -66,7 +66,7 @@ export abstract class StageBase extends Resource implements IStage { public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.baseApi.metric(metricName, props).with({ - dimensions: { ApiId: this.baseApi.apiId, Stage: this.stageName }, + dimensionsMap: { ApiId: this.baseApi.apiId, Stage: this.stageName }, }).attachTo(this); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts index 6b1123512c678..e550a21f915f3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts @@ -1,4 +1,5 @@ import { ICertificate } 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, CfnDomainNameProps } from '../apigatewayv2.generated'; @@ -59,6 +60,32 @@ export interface DomainNameProps { * The ACM certificate for this domain name */ readonly certificate: ICertificate; + /** + * The mutual TLS authentication configuration for a custom domain name. + * @default - mTLS is not configured. + */ + readonly mtls?: MTLSConfig +} + +/** + * 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; } /** @@ -88,6 +115,7 @@ export class DomainName extends Resource implements IDomainName { throw new Error('empty string for domainName not allowed'); } + const mtlsConfig = this.configureMTLS(props.mtls); const domainNameProps: CfnDomainNameProps = { domainName: props.domainName, domainNameConfigurations: [ @@ -96,10 +124,19 @@ export class DomainName extends Resource implements IDomainName { endpointType: 'REGIONAL', }, ], + mutualTlsAuthentication: mtlsConfig, }; const resource = new CfnDomainName(this, 'Resource', domainNameProps); this.name = resource.ref; this.regionalDomainName = Token.asString(resource.getAtt('RegionalDomainName')); this.regionalHostedZoneId = Token.asString(resource.getAtt('RegionalHostedZoneId')); } + + private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined { + if (!mtlsConfig) return undefined; + return { + truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key), + truststoreVersion: mtlsConfig.version, + }; + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 254a29ea6d28b..32dfe0a0c2120 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -305,6 +305,7 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th connectionType: config.connectionType, payloadFormatVersion: config.payloadFormatVersion, secureServerName: config.secureServerName, + parameterMapping: config.parameterMapping, }); this._integrationCache.saveIntegration(scope, config, integration); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index f832b5b7e3b21..df0cf84c13da0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -3,6 +3,7 @@ import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; +import { ParameterMapping } from '../parameter-mapping'; import { IHttpApi } from './api'; import { HttpMethod, IHttpRoute } from './route'; @@ -128,6 +129,13 @@ export interface HttpIntegrationProps { * @default undefined private integration traffic will use HTTP protocol */ readonly secureServerName?: string; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } /** @@ -149,6 +157,7 @@ export class HttpIntegration extends Resource implements IHttpIntegration { connectionId: props.connectionId, connectionType: props.connectionType, payloadFormatVersion: props.payloadFormatVersion?.version, + requestParameters: props.parameterMapping?.mappings, }); if (props.secureServerName) { @@ -237,4 +246,11 @@ export interface HttpRouteIntegrationConfig { * @default undefined private integration traffic will use HTTP protocol */ readonly secureServerName?: string; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index 709f207a04435..ca40349975ec1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -18,6 +18,11 @@ export interface IHttpStage extends IStage { */ readonly api: IHttpApi; + /** + * The custom domain URL to this stage + */ + readonly domainUrl: string; + /** * Metric for the number of client-side errors captured in a given period. * @@ -96,6 +101,7 @@ export interface HttpStageAttributes extends StageAttributes { } abstract class HttpStageBase extends StageBase implements IHttpStage { + public abstract readonly domainUrl: string; public abstract readonly api: IHttpApi; public metricClientError(props?: MetricOptions): Metric { @@ -140,6 +146,10 @@ export class HttpStage extends HttpStageBase { get url(): string { throw new Error('url is not available for imported stages.'); } + + get domainUrl(): string { + throw new Error('domainUrl is not available for imported stages.'); + } } return new Import(scope, id); } @@ -177,9 +187,6 @@ export class HttpStage extends HttpStageBase { return `https://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } - /** - * The custom domain URL to this stage - */ public get domainUrl(): string { if (!this._apiMapping) { throw new Error('domainUrl is not available when no API mapping is associated with the Stage'); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index 12dd8113f8b4c..81df171d98aa1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -2,3 +2,4 @@ export * from './apigatewayv2.generated'; export * from './common'; export * from './http'; export * from './websocket'; +export * from './parameter-mapping'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts new file mode 100644 index 0000000000000..deb967d572de2 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts @@ -0,0 +1,145 @@ +/** + * Represents a Mapping Value. + */ +export interface IMappingValue { + /** + * Represents a Mapping Value. + */ + readonly value: string; +}; + +/** + * Represents a Mapping Value. + */ +export class MappingValue implements IMappingValue { + /** + * Creates an empty mapping value. + */ + public static readonly NONE = new MappingValue(''); + + /** + * Creates a header mapping value. + */ + public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } + + /** + * Creates a query string mapping value. + */ + public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); } + + /** + * Creates a request body mapping value. + */ + public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); } + + /** + * Creates a request path mapping value. + */ + public static requestPath() { return new MappingValue('$request.path'); } + + /** + * Creates a request path parameter mapping value. + */ + public static requestPathParam(name: string) { return new MappingValue(`$request.path.${name}`); } + + /** + * Creates a context variable mapping value. + */ + public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); } + + /** + * Creates a stage variable mapping value. + */ + public static stageVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } + + /** + * Creates a custom mapping value. + */ + public static custom(value: string) { return new MappingValue(value); } + + /** + * Represents a Mapping Value. + */ + public readonly value: string + + protected constructor(value: string) { + this.value = value; + } +} + +/** + * Represents a Parameter Mapping. + */ +export class ParameterMapping { + /** + * Represents all created parameter mappings. + */ + public readonly mappings: { [key: string]: string } + constructor() { + this.mappings = {}; + } + + /** + * Creates a mapping to append a header. + */ + public appendHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:header.${name}`] = value.value; + return this; + } + + /** + * Creates a mapping to overwrite a header. + */ + public overwriteHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:header.${name}`] = value.value; + return this; + } + + /** + * Creates a mapping to remove a header. + */ + public removeHeader(name: string): ParameterMapping { + this.mappings[`remove:header.${name}`] = ''; + return this; + } + + /** + * Creates a mapping to append a query string. + */ + public appendQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:querystring.${name}`] = value.value; + return this; + } + + /** + * Creates a mapping to overwrite a querystring. + */ + public overwriteQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:querystring.${name}`] = value.value; + return this; + } + + /** + * Creates a mapping to remove a querystring. + */ + public removeQueryString(name: string): ParameterMapping { + this.mappings[`remove:querystring.${name}`] = ''; + return this; + } + + /** + * Creates a mapping to overwrite a path. + */ + public overwritePath(value: MappingValue): ParameterMapping { + this.mappings['overwrite:path'] = value.value; + return this; + } + + /** + * Creates a custom mapping. + */ + public custom(key: string, value: string): ParameterMapping { + this.mappings[key] = value; + return this; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts index f2f2653c94ee6..fdcfbdbce6d30 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts @@ -1,3 +1,5 @@ +import { Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApi } from '../apigatewayv2.generated'; import { IApi } from '../common/api'; @@ -21,7 +23,7 @@ export interface IWebSocketApi extends IApi { */ export interface WebSocketApiProps { /** - * Name for the WebSocket API resoruce + * Name for the WebSocket API resource * @default - id of the WebSocketApi construct. */ readonly apiName?: string; @@ -127,4 +129,23 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { ...options, }); } + + /** + * Grant access to the API Gateway management API for this WebSocket API to an IAM + * principal (Role/Group/User). + * + * @param identity The principal + */ + public grantManageConnections(identity: IGrantable): Grant { + const arn = Stack.of(this).formatArn({ + service: 'execute-api', + resource: this.apiId, + }); + + return Grant.addToPrincipal({ + grantee: identity, + actions: ['execute-api:ManageConnections'], + resourceArns: [`${arn}/*/POST/@connections/*`], + }); + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts index f6bc91909dcba..6d5cc8527fef0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts @@ -1,3 +1,4 @@ +import { Grant, IGrantable } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnStage } from '../apigatewayv2.generated'; @@ -114,4 +115,23 @@ export class WebSocketStage extends StageBase implements IWebSocketStage { const urlPath = this.stageName; return `https://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } + + /** + * Grant access to the API Gateway management API for this WebSocket API Stage to an IAM + * principal (Role/Group/User). + * + * @param identity The principal + */ + public grantManagementApiAccess(identity: IGrantable): Grant { + const arn = Stack.of(this.api).formatArn({ + service: 'execute-api', + resource: this.api.apiId, + }); + + return Grant.addToPrincipal({ + grantee: identity, + actions: ['execute-api:ManageConnections'], + resourceArns: [`${arn}/${this.stageName}/POST/@connections/*`], + }); + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 349c1ab9517cf..121096724bdfd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -82,13 +82,14 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, @@ -97,6 +98,7 @@ "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, diff --git a/packages/@aws-cdk/aws-apigatewayv2/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-apigatewayv2/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..3d0f887754334 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as apigwv2 from '@aws-cdk/aws-apigatewayv2'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} 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 5b7f1052bfe35..200933eefbca9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -1,9 +1,10 @@ import { Match, Template } from '@aws-cdk/assertions'; +import { Certificate } from '@aws-cdk/aws-certificatemanager'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import { Duration, Stack } from '@aws-cdk/core'; import { - CorsHttpMethod, + CorsHttpMethod, DomainName, HttpApi, HttpAuthorizer, HttpIntegrationType, HttpMethod, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, IHttpRouteIntegration, HttpNoneAuthorizer, PayloadFormatVersion, } from '../../lib'; @@ -374,6 +375,27 @@ describe('HttpApi', () => { expect(() => api.apiEndpoint).toThrow(/apiEndpoint is not configured/); }); + test('domainUrl can be retrieved for default stage', () => { + const stack = new Stack(); + const dn = new DomainName(stack, 'DN', { + domainName: 'example.com', + certificate: Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:111111111111:certificate'), + }); + + const api = new HttpApi(stack, 'Api', { + createDefaultStage: true, + defaultDomainMapping: { + domainName: dn, + }, + }); + + expect(stack.resolve(api.defaultStage?.domainUrl)).toEqual({ + 'Fn::Join': ['', [ + 'https://', { Ref: 'DNFDC76583' }, '/', + ]], + }); + }); + describe('default authorization settings', () => { test('can add default authorizer', () => { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts index 2d0d856c7ae15..9c60f7b5196e3 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; +import { Bucket } from '@aws-cdk/aws-s3'; import { Stack } from '@aws-cdk/core'; import { DomainName, HttpApi } from '../../lib'; @@ -168,4 +169,66 @@ describe('DomainName', () => { expect(t).toThrow('defaultDomainMapping not supported with createDefaultStage disabled'); }); + + test('accepts a mutual TLS configuration', () => { + // GIVEN + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); + + // WHEN + new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.pem', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + MutualTlsAuthentication: { + TruststoreUri: 's3://example-bucket/someca.pem', + }, + }); + }); + + test('mTLS should allow versions to be set on the s3 bucket', () => { + // GIVEN + const stack = new Stack(); + const bucket = Bucket.fromBucketName(stack, 'testBucket', 'example-bucket'); + + // WHEN + new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + mtls: { + bucket, + key: 'someca.pem', + version: 'version', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + MutualTlsAuthentication: { + TruststoreUri: 's3://example-bucket/someca.pem', + TruststoreVersion: 'version', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 9f64cfdfbd123..75d744b6b5bcc 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -1,8 +1,11 @@ import { Template } from '@aws-cdk/assertions'; import { Stack, App } from '@aws-cdk/core'; import { - HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteAuthorizerBindOptions, - HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, PayloadFormatVersion, + HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, + HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, + MappingValue, + ParameterMapping, + PayloadFormatVersion, } from '../../lib'; describe('HttpRoute', () => { @@ -174,6 +177,9 @@ describe('HttpRoute', () => { connectionType: HttpConnectionType.VPC_LINK, uri: 'some-target-arn', secureServerName: 'some-server-name', + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), }; } } @@ -201,6 +207,47 @@ describe('HttpRoute', () => { Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::VpcLink', 0); }); + test('configures private integration correctly when parameter mappings are passed', () => { + // GIVEN + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + class PrivateIntegration implements IHttpRouteIntegration { + public bind(): HttpRouteIntegrationConfig { + return { + method: HttpMethod.ANY, + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, + type: HttpIntegrationType.HTTP_PROXY, + uri: 'some-target-arn', + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }; + } + } + + // WHEN + new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new PrivateIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationMethod: 'ANY', + IntegrationUri: 'some-target-arn', + PayloadFormatVersion: '1.0', + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + + Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::VpcLink', 0); + }); + test('can create route with an authorizer attached', () => { const stack = new Stack(); const httpApi = new HttpApi(stack, 'HttpApi'); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts index 959555a5c2b7a..24337a3f7c3f2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; +import { User } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { IWebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType, @@ -80,6 +81,49 @@ describe('WebSocketApi', () => { RouteKey: '$default', }); }); + + describe('grantManageConnections', () => { + test('adds an IAM policy to the principal', () => { + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'api'); + const principal = new User(stack, 'user'); + + // WHEN + api.grantManageConnections(principal); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: 'execute-api:ManageConnections', + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':execute-api:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + Ref: 'apiC8550315', + }, + '/*/POST/@connections/*', + ]], + }, + }]), + }, + }); + }); + }); }); class DummyIntegration implements IWebSocketRouteIntegration { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts index d942eb6dc7a4e..b873f7fa74efa 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; +import { User } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { WebSocketApi, WebSocketStage } from '../../lib'; @@ -59,4 +60,51 @@ describe('WebSocketStage', () => { expect(defaultStage.callbackUrl.endsWith('/dev')).toBe(true); expect(defaultStage.callbackUrl.startsWith('https://')).toBe(true); }); + + describe('grantManageConnections', () => { + test('adds an IAM policy to the principal', () => { + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'Api'); + const defaultStage = new WebSocketStage(stack, 'Stage', { + webSocketApi: api, + stageName: 'dev', + }); + const principal = new User(stack, 'User'); + + // WHEN + defaultStage.grantManagementApiAccess(principal); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: 'execute-api:ManageConnections', + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':execute-api:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + Ref: 'ApiF70053CD', + }, + `/${defaultStage.stageName}/POST/@connections/*`, + ]], + }, + }]), + }, + }); + }); + }); }); diff --git a/packages/@aws-cdk/aws-appconfig/package.json b/packages/@aws-cdk/aws-appconfig/package.json index afa652c138049..17cf252cab7b8 100644 --- a/packages/@aws-cdk/aws-appconfig/package.json +++ b/packages/@aws-cdk/aws-appconfig/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-appflow/package.json b/packages/@aws-cdk/aws-appflow/package.json index 3e5d52954046c..fb4531d38dec4 100644 --- a/packages/@aws-cdk/aws-appflow/package.json +++ b/packages/@aws-cdk/aws-appflow/package.json @@ -75,7 +75,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-appintegrations/package.json b/packages/@aws-cdk/aws-appintegrations/package.json index d0129f5818d65..e95a579a6ed4e 100644 --- a/packages/@aws-cdk/aws-appintegrations/package.json +++ b/packages/@aws-cdk/aws-appintegrations/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-applicationautoscaling/README.md b/packages/@aws-cdk/aws-applicationautoscaling/README.md index c00dac29ece2c..77f3858b8e6f2 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/README.md +++ b/packages/@aws-cdk/aws-applicationautoscaling/README.md @@ -51,15 +51,19 @@ There are three ways to scale your capacity: The general pattern of autoscaling will look like this: ```ts +declare const resource: SomeScalableResource; + const capacity = resource.autoScaleCapacity({ minCapacity: 5, maxCapacity: 100 }); -// Enable a type of metric scaling and/or schedule scaling -capacity.scaleOnMetric(...); -capacity.scaleToTrackMetric(...); -capacity.scaleOnSchedule(...); +// Then call a method to enable metric scaling and/or schedule scaling +// (explained below): +// +// capacity.scaleOnMetric(...); +// capacity.scaleToTrackMetric(...); +// capacity.scaleOnSchedule(...); ``` ## Step Scaling @@ -82,8 +86,11 @@ a possible one. You will have to determine what thresholds are right for you). You would configure it like this: ```ts +declare const capacity: ScalableAttribute; +declare const cpuUtilization: cloudwatch.Metric; + capacity.scaleOnMetric('ScaleToCPU', { - metric: service.metricCpuUtilization(), + metric: cpuUtilization, scalingSteps: [ { upper: 10, change: -1 }, { lower: 50, change: +1 }, @@ -92,7 +99,7 @@ capacity.scaleOnMetric('ScaleToCPU', { // Change this to AdjustmentType.PercentChangeInCapacity to interpret the // 'change' numbers before as percentages instead of capacity counts. - adjustmentType: autoscaling.AdjustmentType.CHANGE_IN_CAPACITY, + adjustmentType: appscaling.AdjustmentType.CHANGE_IN_CAPACITY, }); ``` @@ -111,6 +118,10 @@ The following example configures the read capacity of a DynamoDB table to be around 60% utilization: ```ts +import * as dynamodb from '@aws-cdk/aws-dynamodb'; + +declare const table: dynamodb.Table; + const readCapacity = table.autoScaleReadCapacity({ minCapacity: 10, maxCapacity: 1000 @@ -145,18 +156,20 @@ The following example scales the fleet out in the morning, and lets natural scaling take over at night: ```ts +declare const resource: SomeScalableResource; + const capacity = resource.autoScaleCapacity({ minCapacity: 1, maxCapacity: 50, }); capacity.scaleOnSchedule('PrescaleInTheMorning', { - schedule: autoscaling.Schedule.cron({ hour: '8', minute: '0' }), + schedule: appscaling.Schedule.cron({ hour: '8', minute: '0' }), minCapacity: 20, }); capacity.scaleOnSchedule('AllowDownscalingAtNight', { - schedule: autoscaling.Schedule.cron({ hour: '20', minute: '0' }), + schedule: appscaling.Schedule.cron({ hour: '20', minute: '0' }), minCapacity: 1 }); ``` @@ -166,35 +179,30 @@ capacity.scaleOnSchedule('AllowDownscalingAtNight', { ### Lambda Provisioned Concurrency Auto Scaling ```ts - const handler = new lambda.Function(this, 'MyFunction', { - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - code: new lambda.InlineCode(` -import json, time -def handler(event, context): - time.sleep(1) - return { - 'statusCode': 200, - 'body': json.dumps('Hello CDK from Lambda!') - }`), - reservedConcurrentExecutions: 2, - }); - - const fnVer = handler.addVersion('CDKLambdaVersion', undefined, 'demo alias', 10); - - new apigateway.LambdaRestApi(this, 'API', { handler: fnVer }) - - const target = new applicationautoscaling.ScalableTarget(this, 'ScalableTarget', { - serviceNamespace: applicationautoscaling.ServiceNamespace.LAMBDA, - maxCapacity: 100, - minCapacity: 10, - resourceId: `function:${handler.functionName}:${fnVer.version}`, - scalableDimension: 'lambda:function:ProvisionedConcurrency', - }) -s - target.scaleToTrackMetric('PceTracking', { - targetValue: 0.9, - predefinedMetric: applicationautoscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION, - }) - } - ``` +import * as lambda from '@aws-cdk/aws-lambda'; + +declare const code: lambda.Code; + +const handler = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + code, + + reservedConcurrentExecutions: 2, +}); + +const fnVer = handler.addVersion('CDKLambdaVersion', undefined, 'demo alias', 10); + +const target = new appscaling.ScalableTarget(this, 'ScalableTarget', { + serviceNamespace: appscaling.ServiceNamespace.LAMBDA, + maxCapacity: 100, + minCapacity: 10, + resourceId: `function:${handler.functionName}:${fnVer.version}`, + scalableDimension: 'lambda:function:ProvisionedConcurrency', +}) + +target.scaleToTrackMetric('PceTracking', { + targetValue: 0.9, + predefinedMetric: appscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION, +}) +``` diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts index 95d2a19c8a17d..6bb821240c6ff 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts @@ -86,7 +86,7 @@ export abstract class BaseScalableAttribute extends CoreConstruct { } /** - * Properties for enabling DynamoDB capacity scaling + * Properties for enabling Application Auto Scaling */ export interface EnableScalingProps { /** diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index ca5f973a8fa50..7937b331c018a 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -39,7 +39,8 @@ export interface ScalableTargetProps { * * This string consists of the resource type and unique identifier. * - * @example service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH + * Example value: `service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH` + * * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_RegisterScalableTarget.html */ readonly resourceId: string; @@ -49,7 +50,7 @@ export interface ScalableTargetProps { * * Specify the service namespace, resource type, and scaling property. * - * @example ecs:service:DesiredCount + * Example value: `ecs:service:DesiredCount` * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_ScalingPolicy.html */ readonly scalableDimension: string; @@ -82,7 +83,8 @@ export class ScalableTarget extends Resource implements IScalableTarget { /** * ID of the Scalable Target * - * @example service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH|ecs:service:DesiredCount|ecs + * Example value: `service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH|ecs:service:DesiredCount|ecs` + * * @attribute */ public readonly scalableTargetId: string; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index f7ccaff153ffe..de48ab5fd21e7 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -88,7 +88,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Only used for predefined metric ALBRequestCountPerTarget. * - * @example app///targetgroup// + * Example value: `app///targetgroup//` * * @default - No resource label. */ diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index f1bf7ba4c5d95..17bfd09967f88 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -76,9 +83,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "fast-check": "^2.17.0", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "fast-check": "^2.19.0", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-autoscaling-common": "0.0.0", diff --git a/packages/@aws-cdk/aws-applicationautoscaling/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-applicationautoscaling/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..9a75db0061b9d --- /dev/null +++ b/packages/@aws-cdk/aws-applicationautoscaling/rosetta/default.ts-fixture @@ -0,0 +1,48 @@ +// Fixture with packages imported, but nothing else +import { Construct, Node } from 'constructs'; +import { Aspects, CfnOutput, Stack, Duration, Resource, SecretValue } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as iam from '@aws-cdk/aws-iam'; + + +interface UtilizationScalingProps { + readonly targetUtilizationPercent: number; +} + +class ScalableAttribute { + public scaleOnSchedule(id: string, action: appscaling.ScalingSchedule) { + Array.isArray(id); + Array.isArray(action); + } + public scaleOnUtilization(props: UtilizationScalingProps) { + Array.isArray(props); + } + public scaleOnMetric(id: string, props: appscaling.BasicStepScalingPolicyProps) { + Array.isArray(id); + Array.isArray(props); + } +} + +interface Caps { + readonly minCapacity: number; + readonly maxCapacity: number; +} + +class SomeScalableResource { + public autoScaleCapacity(caps: Caps) { + return new ScalableAttribute(); + } +} + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + + + diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts index 8628a8624468c..508ae3bb2c9e2 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as appscaling from '../lib'; import { createScalableTarget } from './util'; @@ -26,8 +27,6 @@ describe('scalable target', () => { MinCapacity: 1, MaxCapacity: 20, }); - - }); test('validation does not fail when using Tokens', () => { @@ -51,8 +50,6 @@ describe('scalable target', () => { MinCapacity: 10, MaxCapacity: 1, }); - - }); test('add scheduled scaling', () => { @@ -80,8 +77,6 @@ describe('scalable target', () => { }, ], }); - - }); test('step scaling on MathExpression', () => { @@ -136,21 +131,99 @@ describe('scalable target', () => { ], Threshold: 49, }); + }); + test('test service namespace enum', () => { + expect(appscaling.ServiceNamespace.APPSTREAM).toEqual('appstream'); + expect(appscaling.ServiceNamespace.COMPREHEND).toEqual('comprehend'); + expect(appscaling.ServiceNamespace.CUSTOM_RESOURCE).toEqual('custom-resource'); + expect(appscaling.ServiceNamespace.DYNAMODB).toEqual('dynamodb'); + expect(appscaling.ServiceNamespace.EC2).toEqual('ec2'); + expect(appscaling.ServiceNamespace.ECS).toEqual('ecs'); + expect(appscaling.ServiceNamespace.ELASTIC_MAP_REDUCE).toEqual('elasticmapreduce'); + expect(appscaling.ServiceNamespace.LAMBDA).toEqual('lambda'); + expect(appscaling.ServiceNamespace.RDS).toEqual('rds'); + expect(appscaling.ServiceNamespace.SAGEMAKER).toEqual('sagemaker'); + }); + test('create scalable target with negative minCapacity throws error', () => { + const stack = new cdk.Stack(); + expect(() => { + new appscaling.ScalableTarget(stack, 'Target', { + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, + scalableDimension: 'test:TestCount', + resourceId: 'test:this/test', + minCapacity: -1, + maxCapacity: 20, + }); + }).toThrow('minCapacity cannot be negative, got: -1'); }); - test('test service namespace enum', () => { - expect(appscaling.ServiceNamespace.APPSTREAM).toEqual( 'appstream'); - expect(appscaling.ServiceNamespace.COMPREHEND).toEqual( 'comprehend'); - expect(appscaling.ServiceNamespace.CUSTOM_RESOURCE).toEqual( 'custom-resource'); - expect(appscaling.ServiceNamespace.DYNAMODB).toEqual( 'dynamodb'); - expect(appscaling.ServiceNamespace.EC2).toEqual( 'ec2'); - expect(appscaling.ServiceNamespace.ECS).toEqual( 'ecs'); - expect(appscaling.ServiceNamespace.ELASTIC_MAP_REDUCE).toEqual( 'elasticmapreduce'); - expect(appscaling.ServiceNamespace.LAMBDA).toEqual( 'lambda'); - expect(appscaling.ServiceNamespace.RDS).toEqual( 'rds'); - expect(appscaling.ServiceNamespace.SAGEMAKER).toEqual( 'sagemaker'); + test('create scalable target with negative maxCapacity throws error', () => { + const stack = new cdk.Stack(); + expect(() => { + new appscaling.ScalableTarget(stack, 'Target', { + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, + scalableDimension: 'test:TestCount', + resourceId: 'test:this/test', + minCapacity: 1, + maxCapacity: -1, + }); + }).toThrow('maxCapacity cannot be negative, got: -1'); + }); + + test('create scalable target with maxCapacity less than minCapacity throws error', () => { + const stack = new cdk.Stack(); + expect(() => { + new appscaling.ScalableTarget(stack, 'Target', { + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, + scalableDimension: 'test:TestCount', + resourceId: 'test:this/test', + minCapacity: 2, + maxCapacity: 1, + }); + }).toThrow('minCapacity (2) should be lower than maxCapacity (1)'); + }); + test('create scalable target with custom role', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new appscaling.ScalableTarget(stack, 'Target', { + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, + scalableDimension: 'test:TestCount', + resourceId: 'test:this/test', + minCapacity: 1, + maxCapacity: 20, + role: new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('test.amazonaws.com'), + }), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + ServiceNamespace: 'dynamodb', + ScalableDimension: 'test:TestCount', + ResourceId: 'test:this/test', + MinCapacity: 1, + MaxCapacity: 20, + RoleARN: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }); + }); + + test('add scheduled scaling with neither of min/maxCapacity defined throws error', () => { + const stack = new cdk.Stack(); + const target = createScalableTarget(stack); + expect(() => { + target.scaleOnSchedule('ScaleUp', { + schedule: appscaling.Schedule.rate(cdk.Duration.minutes(1)), + }); + }).toThrow(/You must supply at least one of minCapacity or maxCapacity, got/); }); }); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/cron.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/schedule.test.ts similarity index 79% rename from packages/@aws-cdk/aws-applicationautoscaling/test/cron.test.ts rename to packages/@aws-cdk/aws-applicationautoscaling/test/schedule.test.ts index b3c337883146c..976c1863ca991 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/cron.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/schedule.test.ts @@ -11,7 +11,9 @@ describe('cron', () => { expect(appscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString).toEqual('cron(24 18 * * ? *)'); }); +}); +describe('rate', () => { test('rate must be whole number of minutes', () => { expect(() => { appscaling.Schedule.rate(Duration.minutes(0.13456)); @@ -53,3 +55,16 @@ describe('cron', () => { }); }); + +describe('expression', () => { + test('test using a literal schedule expression', () => { + expect(appscaling.Schedule.expression('cron(0 18 * * ? *)').expressionString).toEqual('cron(0 18 * * ? *)'); + + }); +}); + +describe('at', () => { + test('test using at with a specific Date', () => { + expect(appscaling.Schedule.at(new Date(Date.UTC(2021, 10, 26))).expressionString).toEqual('at(2021-11-26T00:00:00)'); + }); +}); diff --git a/packages/@aws-cdk/aws-applicationinsights/package.json b/packages/@aws-cdk/aws-applicationinsights/package.json index 28045bdba0613..fd197aa6c9dd3 100644 --- a/packages/@aws-cdk/aws-applicationinsights/package.json +++ b/packages/@aws-cdk/aws-applicationinsights/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index b1779dffe6f8a..54bc79fade4c3 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -35,7 +35,7 @@ After you create your service mesh, you can create virtual services, virtual nod The following example creates the `AppMesh` service mesh with the default egress filter of `DROP_ALL`. See [the AWS CloudFormation `EgressFilter` resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-egressfilter.html) for more info on egress filters. ```ts -const mesh = new Mesh(stack, 'AppMesh', { +const mesh = new appmesh.Mesh(this, 'AppMesh', { meshName: 'myAwsMesh', }); ``` @@ -43,9 +43,9 @@ const mesh = new Mesh(stack, 'AppMesh', { The mesh can instead be created with the `ALLOW_ALL` egress filter by providing the `egressFilter` property. ```ts -const mesh = new Mesh(stack, 'AppMesh', { +const mesh = new appmesh.Mesh(this, 'AppMesh', { meshName: 'myAwsMesh', - egressFilter: MeshFilterType.ALLOW_ALL, + egressFilter: appmesh.MeshFilterType.ALLOW_ALL, }); ``` @@ -57,8 +57,9 @@ Virtual routers handle traffic for one or more virtual services within your mesh After you create a virtual router, you can create and associate routes to your virtual router that direct incoming requests to different virtual nodes. ```ts +declare const mesh: appmesh.Mesh; const router = mesh.addVirtualRouter('router', { - listeners: [ VirtualRouterListener.http(8080) ], + listeners: [appmesh.VirtualRouterListener.http(8080)], }); ``` @@ -68,16 +69,19 @@ The router can also be created using the `VirtualRouter` constructor (passing in This is particularly useful when splitting your resources between many stacks: for example, defining the mesh itself as part of an infrastructure stack, but defining the other resources, such as routers, in the application stack: ```ts -const mesh = new Mesh(infraStack, 'AppMesh', { +declare const infraStack: cdk.Stack; +declare const appStack: cdk.Stack; + +const mesh = new appmesh.Mesh(infraStack, 'AppMesh', { meshName: 'myAwsMesh', - egressFilter: MeshFilterType.ALLOW_ALL, + egressFilter: appmesh.MeshFilterType.ALLOW_ALL, }); // the VirtualRouter will belong to 'appStack', // even though the Mesh belongs to 'infraStack' -const router = new VirtualRouter(appStack, 'router', { +const router = new appmesh.VirtualRouter(appStack, 'router', { mesh, // notice that mesh is a required property when creating a router with the 'new' statement - listeners: [VirtualRouterListener.http(8081)], + listeners: [appmesh.VirtualRouterListener.http(8081)], }); ``` @@ -102,18 +106,22 @@ When creating a virtual service: Adding a virtual router as the provider: ```ts -new VirtualService(stack, 'virtual-service', { +declare const router: appmesh.VirtualRouter; + +new appmesh.VirtualService(this, 'virtual-service', { virtualServiceName: 'my-service.default.svc.cluster.local', // optional - virtualServiceProvider: VirtualServiceProvider.virtualRouter(router), + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(router), }); ``` Adding a virtual node as the provider: ```ts -new VirtualService(stack, 'virtual-service', { +declare const node: appmesh.VirtualNode; + +new appmesh.VirtualService(this, 'virtual-service', { virtualServiceName: `my-service.default.svc.cluster.local`, // optional - virtualServiceProvider: VirtualServiceProvider.virtualNode(node), + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualNode(node), }); ``` @@ -129,18 +137,19 @@ The response metadata for your new virtual node contains the Amazon Resource Nam > If you require your Envoy stats or tracing to use a different name, you can override the `node.cluster` value that is set by `APPMESH_VIRTUAL_NODE_NAME` with the `APPMESH_VIRTUAL_NODE_CLUSTER` environment variable. ```ts -const vpc = new ec2.Vpc(stack, 'vpc'); -const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'test-namespace', { +const vpc = new ec2.Vpc(this, 'vpc'); +const namespace = new cloudmap.PrivateDnsNamespace(this, 'test-namespace', { vpc, name: 'domain.local', }); const service = namespace.createService('Svc'); +declare const mesh: appmesh.Mesh; const node = mesh.addVirtualNode('virtual-node', { - serviceDiscovery: ServiceDiscovery.cloudMap(service), - listeners: [VirtualNodeListener.http({ + serviceDiscovery: appmesh.ServiceDiscovery.cloudMap(service), + listeners: [appmesh.VirtualNodeListener.http({ port: 8081, - healthCheck: HealthCheck.http({ + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: cdk.Duration.seconds(5), // minimum path: '/health-check-path', @@ -148,19 +157,22 @@ const node = mesh.addVirtualNode('virtual-node', { unhealthyThreshold: 2, }), })], - accessLog: AccessLog.fromFilePath('/dev/stdout'), + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); ``` Create a `VirtualNode` with the constructor and add tags. ```ts -const node = new VirtualNode(stack, 'node', { +declare const mesh: appmesh.Mesh; +declare const service: cloudmap.Service; + +const node = new appmesh.VirtualNode(this, 'node', { mesh, - serviceDiscovery: ServiceDiscovery.cloudMap(service), - listeners: [VirtualNodeListener.http({ + serviceDiscovery: appmesh.ServiceDiscovery.cloudMap(service), + listeners: [appmesh.VirtualNodeListener.http({ port: 8080, - healthCheck: HealthCheck.http({ + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: cdk.Duration.seconds(5), path: '/ping', @@ -174,11 +186,11 @@ const node = new VirtualNode(stack, 'node', { backendDefaults: { tlsClientPolicy: { validation: { - trust: TlsValidationTrust.file('/keys/local_cert_chain.pem'), + trust: appmesh.TlsValidationTrust.file('/keys/local_cert_chain.pem'), }, }, }, - accessLog: AccessLog.fromFilePath('/dev/stdout'), + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); cdk.Tags.of(node).add('Environment', 'Dev'); @@ -187,12 +199,16 @@ cdk.Tags.of(node).add('Environment', 'Dev'); Create a `VirtualNode` with the constructor and add backend virtual service. ```ts -const node = new VirtualNode(stack, 'node', { +declare const mesh: appmesh.Mesh; +declare const router: appmesh.VirtualRouter; +declare const service: cloudmap.Service; + +const node = new appmesh.VirtualNode(this, 'node', { mesh, - serviceDiscovery: ServiceDiscovery.cloudMap(service), - listeners: [VirtualNodeListener.http({ + serviceDiscovery: appmesh.ServiceDiscovery.cloudMap(service), + listeners: [appmesh.VirtualNodeListener.http({ port: 8080, - healthCheck: HealthCheck.http({ + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: cdk.Duration.seconds(5), path: '/ping', @@ -203,15 +219,15 @@ const node = new VirtualNode(stack, 'node', { idle: cdk.Duration.seconds(5), }, })], - accessLog: AccessLog.fromFilePath('/dev/stdout'), + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); -const virtualService = new VirtualService(stack, 'service-1', { - virtualServiceProvider: VirtualServiceProvider.virtualRouter(router), +const virtualService = new appmesh.VirtualService(this, 'service-1', { + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(router), virtualServiceName: 'service1.domain.local', }); -node.addBackend(Backend.virtualService(virtualService)); +node.addBackend(appmesh.Backend.virtualService(virtualService)); ``` The `listeners` property can be left blank and added later with the `node.addListener()` method. The `serviceDiscovery` property must be specified when specifying a listener. @@ -231,45 +247,44 @@ Provide the TLS certificate to the proxy in one of the following ways: - A certificate provided by a Secrets Discovery Service (SDS) endpoint over local Unix Domain Socket (specify its `secretName`). -```typescript -import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; - +```ts // A Virtual Node with listener TLS from an ACM provided certificate -const cert = new certificatemanager.Certificate(this, 'cert', {...}); +declare const cert: certificatemanager.Certificate; +declare const mesh: appmesh.Mesh; -const node = new VirtualNode(stack, 'node', { +const node = new appmesh.VirtualNode(this, 'node', { mesh, - serviceDiscovery: ServiceDiscovery.dns('node'), - listeners: [VirtualNodeListener.grpc({ + serviceDiscovery: appmesh.ServiceDiscovery.dns('node'), + listeners: [appmesh.VirtualNodeListener.grpc({ port: 80, tls: { - mode: TlsMode.STRICT, - certificate: TlsCertificate.acm(cert), + mode: appmesh.TlsMode.STRICT, + certificate: appmesh.TlsCertificate.acm(cert), }, })], }); // A Virtual Gateway with listener TLS from a customer provided file certificate -const gateway = new VirtualGateway(this, 'gateway', { - mesh: mesh, - listeners: [VirtualGatewayListener.grpc({ +const gateway = new appmesh.VirtualGateway(this, 'gateway', { + mesh, + listeners: [appmesh.VirtualGatewayListener.grpc({ port: 8080, tls: { - mode: TlsMode.STRICT, - certificate: TlsCertificate.file('path/to/certChain', 'path/to/privateKey'), + mode: appmesh.TlsMode.STRICT, + certificate: appmesh.TlsCertificate.file('path/to/certChain', 'path/to/privateKey'), }, })], virtualGatewayName: 'gateway', }); // A Virtual Gateway with listener TLS from a SDS provided certificate -const gateway2 = new VirtualGateway(this, 'gateway2', { - mesh: mesh, - listeners: [VirtualGatewayListener.http2({ +const gateway2 = new appmesh.VirtualGateway(this, 'gateway2', { + mesh, + listeners: [appmesh.VirtualGatewayListener.http2({ port: 8080, tls: { - mode: TlsMode.STRICT, - certificate: TlsCertificate.sds('secrete_certificate'), + mode: appmesh.TlsMode.STRICT, + certificate: appmesh.TlsCertificate.sds('secrete_certificate'), }, })], virtualGatewayName: 'gateway2', @@ -290,38 +305,39 @@ To enable mutual TLS authentication, add the `mutualTlsCertificate` property to > **Note** > Currently, a certificate from AWS Certificate Manager (ACM) cannot be used for mutual TLS authentication. -```typescript -import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; +```ts +declare const mesh: appmesh.Mesh; -const node1 = new VirtualNode(stack, 'node1', { +const node1 = new appmesh.VirtualNode(this, 'node1', { mesh, - serviceDiscovery: ServiceDiscovery.dns('node'), - listeners: [VirtualNodeListener.grpc({ + serviceDiscovery: appmesh.ServiceDiscovery.dns('node'), + listeners: [appmesh.VirtualNodeListener.grpc({ port: 80, tls: { - mode: TlsMode.STRICT, - certificate: TlsCertificate.file('path/to/certChain', 'path/to/privateKey'), + mode: appmesh.TlsMode.STRICT, + certificate: appmesh.TlsCertificate.file('path/to/certChain', 'path/to/privateKey'), // Validate a file client certificates to enable mutual TLS authentication when a client provides a certificate. mutualTlsValidation: { - trust: TlsValidationTrust.file('path-to-certificate'), + trust: appmesh.TlsValidationTrust.file('path-to-certificate'), }, }, })], }); -const node2 = new VirtualNode(stack, 'node2', { +const certificateAuthorityArn = 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012'; +const node2 = new appmesh.VirtualNode(this, 'node2', { mesh, - serviceDiscovery: ServiceDiscovery.dns('node2'), + serviceDiscovery: appmesh.ServiceDiscovery.dns('node2'), backendDefaults: { tlsClientPolicy: { ports: [8080, 8081], validation: { - subjectAlternativeNames: SubjectAlternativeNames.matchingExactly('mesh-endpoint.apps.local'), - trust: TlsValidationTrust.acm([ - acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)]), + subjectAlternativeNames: appmesh.SubjectAlternativeNames.matchingExactly('mesh-endpoint.apps.local'), + trust: appmesh.TlsValidationTrust.acm([ + acmpca.CertificateAuthority.fromCertificateAuthorityArn(this, 'certificate', certificateAuthorityArn)]), }, // Provide a SDS client certificate when a server requests it and enable mutual TLS authentication. - mutualTlsCertificate: TlsCertificate.sds('secret_certificate'), + mutualTlsCertificate: appmesh.TlsCertificate.sds('secret_certificate'), }, }, }); @@ -332,18 +348,19 @@ const node2 = new VirtualNode(stack, 'node2', { The `outlierDetection` property adds outlier detection to a Virtual Node listener. The properties `baseEjectionDuration`, `interval`, `maxEjectionPercent`, and `maxServerErrors` are required. -```typescript +```ts // Cloud Map service discovery is currently required for host ejection by outlier detection -const vpc = new ec2.Vpc(stack, 'vpc'); -const namespace = new servicediscovery.PrivateDnsNamespace(this, 'test-namespace', { +const vpc = new ec2.Vpc(this, 'vpc'); +const namespace = new cloudmap.PrivateDnsNamespace(this, 'test-namespace', { vpc, name: 'domain.local', }); const service = namespace.createService('Svc'); +declare const mesh: appmesh.Mesh; const node = mesh.addVirtualNode('virtual-node', { - serviceDiscovery: ServiceDiscovery.cloudMap(service), - listeners: [VirtualNodeListener.http({ + serviceDiscovery: appmesh.ServiceDiscovery.cloudMap(service), + listeners: [appmesh.VirtualNodeListener.http({ outlierDetection: { baseEjectionDuration: cdk.Duration.seconds(10), interval: cdk.Duration.seconds(30), @@ -358,16 +375,17 @@ const node = mesh.addVirtualNode('virtual-node', { The `connectionPool` property can be added to a Virtual Node listener or Virtual Gateway listener to add a request connection pool. Each listener protocol type has its own connection pool properties. -```typescript +```ts // A Virtual Node with a gRPC listener with a connection pool set -const node = new VirtualNode(stack, 'node', { +declare const mesh: appmesh.Mesh; +const node = new appmesh.VirtualNode(this, 'node', { mesh, // DNS service discovery can optionally specify the DNS response type as either LOAD_BALANCER or ENDPOINTS. // LOAD_BALANCER means that the DNS resolver returns a loadbalanced set of endpoints, // whereas ENDPOINTS means that the DNS resolver is returning all the endpoints. // By default, the response type is assumed to be LOAD_BALANCER - serviceDiscovery: ServiceDiscovery.dns('node', DnsResponseType.ENDPOINTS), - listeners: [VirtualNodeListener.http({ + serviceDiscovery: appmesh.ServiceDiscovery.dns('node', appmesh.DnsResponseType.ENDPOINTS), + listeners: [appmesh.VirtualNodeListener.http({ port: 80, connectionPool: { maxConnections: 100, @@ -377,9 +395,9 @@ const node = new VirtualNode(stack, 'node', { }); // A Virtual Gateway with a gRPC listener with a connection pool set -const gateway = new VirtualGateway(stack, 'gateway', { +const gateway = new appmesh.VirtualGateway(this, 'gateway', { mesh, - listeners: [VirtualGatewayListener.grpc({ + listeners: [appmesh.VirtualGatewayListener.grpc({ port: 8080, connectionPool: { maxRequests: 10, @@ -406,8 +424,11 @@ When specifying the method name, the service name must also be specified. For example, here's how to add an HTTP route that matches based on a prefix of the URL path: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-http', { - routeSpec: RouteSpec.http({ + routeSpec: appmesh.RouteSpec.http({ weightedTargets: [ { virtualNode: node, @@ -415,7 +436,7 @@ router.addRoute('route-http', { ], match: { // Path that is passed to this method must start with '/'. - path: HttpRoutePathMatch.startsWith('/path-to-app'), + path: appmesh.HttpRoutePathMatch.startsWith('/path-to-app'), }, }), }); @@ -424,25 +445,28 @@ router.addRoute('route-http', { Add an HTTP2 route that matches based on exact path, method, scheme, headers, and query parameters: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-http2', { - routeSpec: RouteSpec.http2({ + routeSpec: appmesh.RouteSpec.http2({ weightedTargets: [ { virtualNode: node, }, ], match: { - path: HttpRoutePathMatch.exactly('/exact'), - method: HttpRouteMethod.POST, - protocol: HttpRouteProtocol.HTTPS, + path: appmesh.HttpRoutePathMatch.exactly('/exact'), + method: appmesh.HttpRouteMethod.POST, + protocol: appmesh.HttpRouteProtocol.HTTPS, headers: [ // All specified headers must match for the route to match. - HeaderMatch.valueIs('Content-Type', 'application/json'), - HeaderMatch.valueIsNot('Content-Type', 'application/json'), + appmesh.HeaderMatch.valueIs('Content-Type', 'application/json'), + appmesh.HeaderMatch.valueIsNot('Content-Type', 'application/json'), ], queryParameters: [ // All specified query parameters must match for the route to match. - QueryParameterMatch.valueIs('query-field', 'value') + appmesh.QueryParameterMatch.valueIs('query-field', 'value') ], }, }), @@ -452,8 +476,11 @@ router.addRoute('route-http2', { Add a single route with two targets and split traffic 50/50: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-http', { - routeSpec: RouteSpec.http({ + routeSpec: appmesh.RouteSpec.http({ weightedTargets: [ { virtualNode: node, @@ -465,7 +492,7 @@ router.addRoute('route-http', { }, ], match: { - path: HttpRoutePathMatch.startsWith('/path-to-app'), + path: appmesh.HttpRoutePathMatch.startsWith('/path-to-app'), }, }), }); @@ -474,14 +501,17 @@ router.addRoute('route-http', { Add an http2 route with retries: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-http2-retry', { - routeSpec: RouteSpec.http2({ + routeSpec: appmesh.RouteSpec.http2({ weightedTargets: [{ virtualNode: node }], retryPolicy: { // Retry if the connection failed - tcpRetryEvents: [TcpRetryEvent.CONNECTION_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], // Retry if HTTP responds with a gateway error (502, 503, 504) - httpRetryEvents: [HttpRetryEvent.GATEWAY_ERROR], + httpRetryEvents: [appmesh.HttpRetryEvent.GATEWAY_ERROR], // Retry five times retryAttempts: 5, // Use a 1 second timeout per retry @@ -494,19 +524,22 @@ router.addRoute('route-http2-retry', { Add a gRPC route with retries: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-grpc-retry', { - routeSpec: RouteSpec.grpc({ + routeSpec: appmesh.RouteSpec.grpc({ weightedTargets: [{ virtualNode: node }], match: { serviceName: 'servicename' }, retryPolicy: { - tcpRetryEvents: [TcpRetryEvent.CONNECTION_ERROR], - httpRetryEvents: [HttpRetryEvent.GATEWAY_ERROR], + tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], + httpRetryEvents: [appmesh.HttpRetryEvent.GATEWAY_ERROR], // Retry if gRPC responds that the request was cancelled, a resource // was exhausted, or if the service is unavailable grpcRetryEvents: [ - GrpcRetryEvent.CANCELLED, - GrpcRetryEvent.RESOURCE_EXHAUSTED, - GrpcRetryEvent.UNAVAILABLE, + appmesh.GrpcRetryEvent.CANCELLED, + appmesh.GrpcRetryEvent.RESOURCE_EXHAUSTED, + appmesh.GrpcRetryEvent.UNAVAILABLE, ], retryAttempts: 5, retryTimeout: cdk.Duration.seconds(1), @@ -518,8 +551,11 @@ router.addRoute('route-grpc-retry', { Add an gRPC route that matches based on method name and metadata: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-grpc-retry', { - routeSpec: RouteSpec.grpc({ + routeSpec: appmesh.RouteSpec.grpc({ weightedTargets: [{ virtualNode: node }], match: { // When method name is specified, service name must be also specified. @@ -527,8 +563,8 @@ router.addRoute('route-grpc-retry', { serviceName: 'servicename', metadata: [ // All specified metadata must match for the route to match. - HeaderMatch.valueStartsWith('Content-Type', 'application/'), - HeaderMatch.valueDoesNotStartWith('Content-Type', 'text/'), + appmesh.HeaderMatch.valueStartsWith('Content-Type', 'application/'), + appmesh.HeaderMatch.valueDoesNotStartWith('Content-Type', 'text/'), ], }, }), @@ -538,8 +574,11 @@ router.addRoute('route-grpc-retry', { Add a gRPC route with timeout: ```ts +declare const router: appmesh.VirtualRouter; +declare const node: appmesh.VirtualNode; + router.addRoute('route-http', { - routeSpec: RouteSpec.grpc({ + routeSpec: appmesh.RouteSpec.grpc({ weightedTargets: [ { virtualNode: node, @@ -569,13 +608,14 @@ using rules defined in gateway routes which can be added to your virtual gateway Create a virtual gateway with the constructor: ```ts +declare const mesh: appmesh.Mesh; const certificateAuthorityArn = 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012'; -const gateway = new VirtualGateway(stack, 'gateway', { +const gateway = new appmesh.VirtualGateway(this, 'gateway', { mesh: mesh, - listeners: [VirtualGatewayListener.http({ + listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: HealthCheck.http({ + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), }), })], @@ -583,12 +623,12 @@ const gateway = new VirtualGateway(stack, 'gateway', { tlsClientPolicy: { ports: [8080, 8081], validation: { - trust: TlsValidationTrust.acm([ - acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)]), + trust: appmesh.TlsValidationTrust.acm([ + acmpca.CertificateAuthority.fromCertificateAuthorityArn(this, 'certificate', certificateAuthorityArn)]), }, }, }, - accessLog: AccessLog.fromFilePath('/dev/stdout'), + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), virtualGatewayName: 'virtualGateway', }); ``` @@ -596,12 +636,14 @@ const gateway = new VirtualGateway(stack, 'gateway', { Add a virtual gateway directly to the mesh: ```ts +declare const mesh: appmesh.Mesh; + const gateway = mesh.addVirtualGateway('gateway', { - accessLog: AccessLog.fromFilePath('/dev/stdout'), + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), virtualGatewayName: 'virtualGateway', - listeners: [VirtualGatewayListener.http({ + listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: HealthCheck.http({ + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), }), })], @@ -622,11 +664,14 @@ path (prefix, exact, or regex), HTTP method, host name, HTTP headers, and query By default, HTTP-based gateway routes match all requests. ```ts +declare const gateway: appmesh.VirtualGateway; +declare const virtualService: appmesh.VirtualService; + gateway.addGatewayRoute('gateway-route-http', { - routeSpec: GatewayRouteSpec.http({ + routeSpec: appmesh.GatewayRouteSpec.http({ routeTarget: virtualService, match: { - path: HttpGatewayRoutePathMatch.regex('regex'), + path: appmesh.HttpGatewayRoutePathMatch.regex('regex'), }, }), }); @@ -635,11 +680,14 @@ gateway.addGatewayRoute('gateway-route-http', { For gRPC-based gateway routes, the `match` field can be used to match on service name, host name, and metadata. ```ts +declare const gateway: appmesh.VirtualGateway; +declare const virtualService: appmesh.VirtualService; + gateway.addGatewayRoute('gateway-route-grpc', { - routeSpec: GatewayRouteSpec.grpc({ + routeSpec: appmesh.GatewayRouteSpec.grpc({ routeTarget: virtualService, match: { - hostname: GatewayRouteHostnameMatch.endsWith('.example.com'), + hostname: appmesh.GatewayRouteHostnameMatch.endsWith('.example.com'), }, }), }); @@ -649,23 +697,26 @@ For HTTP based gateway routes, App Mesh automatically rewrites the matched prefi This automatic rewrite configuration can be overwritten in following ways: ```ts +declare const gateway: appmesh.VirtualGateway; +declare const virtualService: appmesh.VirtualService; + gateway.addGatewayRoute('gateway-route-http', { - routeSpec: GatewayRouteSpec.http({ + routeSpec: appmesh.GatewayRouteSpec.http({ routeTarget: virtualService, match: { // This disables the default rewrite to '/', and retains original path. - path: HttpGatewayRoutePathMatch.startsWith('/path-to-app/', ''), + path: appmesh.HttpGatewayRoutePathMatch.startsWith('/path-to-app/', ''), }, }), }); gateway.addGatewayRoute('gateway-route-http-1', { - routeSpec: GatewayRouteSpec.http({ + routeSpec: appmesh.GatewayRouteSpec.http({ routeTarget: virtualService, match: { // If the request full path is '/path-to-app/xxxxx', this rewrites the path to '/rewrittenUri/xxxxx'. // Please note both `prefixPathMatch` and `rewriteTo` must start and end with the `/` character. - path: HttpGatewayRoutePathMatch.startsWith('/path-to-app/', '/rewrittenUri/'), + path: appmesh.HttpGatewayRoutePathMatch.startsWith('/path-to-app/', '/rewrittenUri/'), }, }), }); @@ -675,12 +726,15 @@ If matching other path (exact or regex), only specific rewrite path can be speci Unlike `startsWith()` method above, no default rewrite is performed. ```ts +declare const gateway: appmesh.VirtualGateway; +declare const virtualService: appmesh.VirtualService; + gateway.addGatewayRoute('gateway-route-http-2', { - routeSpec: GatewayRouteSpec.http({ + routeSpec: appmesh.GatewayRouteSpec.http({ routeTarget: virtualService, match: { // This rewrites the path from '/test' to '/rewrittenPath'. - path: HttpGatewayRoutePathMatch.exactly('/test', '/rewrittenPath'), + path: appmesh.HttpGatewayRoutePathMatch.exactly('/test', '/rewrittenPath'), }, }), }); @@ -691,11 +745,14 @@ the original request received at the Virtual Gateway to the destination Virtual This default host name rewrite can be configured by specifying the rewrite rule as one of the `match` property: ```ts +declare const gateway: appmesh.VirtualGateway; +declare const virtualService: appmesh.VirtualService; + gateway.addGatewayRoute('gateway-route-grpc', { - routeSpec: GatewayRouteSpec.grpc({ + routeSpec: appmesh.GatewayRouteSpec.grpc({ routeTarget: virtualService, match: { - hostname: GatewayRouteHostnameMatch.exactly('example.com'), + hostname: appmesh.GatewayRouteHostnameMatch.exactly('example.com'), // This disables the default rewrite to virtual service name and retain original request. rewriteRequestHostname: false, }, @@ -710,12 +767,13 @@ These imported resources can be used with other resources in your mesh as if the ```ts const arn = 'arn:aws:appmesh:us-east-1:123456789012:mesh/testMesh/virtualNode/testNode'; -VirtualNode.fromVirtualNodeArn(stack, 'importedVirtualNode', arn); +appmesh.VirtualNode.fromVirtualNodeArn(this, 'importedVirtualNode', arn); ``` ```ts -VirtualNode.fromVirtualNodeAttributes(stack, 'imported-virtual-node', { - mesh: Mesh.fromMeshName(stack, 'Mesh', 'testMesh'), +const virtualNodeName = 'my-virtual-node'; +appmesh.VirtualNode.fromVirtualNodeAttributes(this, 'imported-virtual-node', { + mesh: appmesh.Mesh.fromMeshName(this, 'Mesh', 'testMesh'), virtualNodeName: virtualNodeName, }); ``` @@ -724,11 +782,11 @@ To import a mesh, again there are two static methods, `fromMeshArn` and `fromMes ```ts const arn = 'arn:aws:appmesh:us-east-1:123456789012:mesh/testMesh'; -Mesh.fromMeshArn(stack, 'imported-mesh', arn); +appmesh.Mesh.fromMeshArn(this, 'imported-mesh', arn); ``` ```ts -Mesh.fromMeshName(stack, 'imported-mesh', 'abc'); +appmesh.Mesh.fromMeshName(this, 'imported-mesh', 'abc'); ``` ## IAM Grants @@ -737,8 +795,9 @@ Mesh.fromMeshName(stack, 'imported-mesh', 'abc'); Envoy access to stream generated config from App Mesh. ```ts -const gateway = new VirtualGateway(stack, 'testGateway', { mesh: mesh }); -const envoyUser = new iam.User(stack, 'envoyUser'); +declare const mesh: appmesh.Mesh; +const gateway = new appmesh.VirtualGateway(this, 'testGateway', { mesh }); +const envoyUser = new iam.User(this, 'envoyUser'); /** * This will grant `grantStreamAggregatedResources` ONLY for this gateway. @@ -754,10 +813,10 @@ A shared mesh allows resources created by different accounts to communicate with // This is the ARN for the mesh from different AWS IAM account ID. // Ensure mesh is properly shared with your account. For more details, see: https://github.com/aws/aws-cdk/issues/15404 const arn = 'arn:aws:appmesh:us-east-1:123456789012:mesh/testMesh'; -sharedMesh = Mesh.fromMeshArn(stack, 'imported-mesh', arn); +const sharedMesh = appmesh.Mesh.fromMeshArn(this, 'imported-mesh', arn); // This VirtualNode resource can communicate with the resources in the mesh from different AWS IAM account ID. -new VirtualNode(stack, 'test-node', { +new appmesh.VirtualNode(this, 'test-node', { mesh: sharedMesh, }); ``` diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts index 2c34c1e3f6f46..088067d06763d 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts @@ -68,7 +68,7 @@ export class GatewayRoute extends cdk.Resource implements IGatewayRoute { public static fromGatewayRouteArn(scope: Construct, id: string, gatewayRouteArn: string): IGatewayRoute { return new class extends cdk.Resource implements IGatewayRoute { readonly gatewayRouteArn = gatewayRouteArn; - readonly gatewayRouteName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(gatewayRouteArn).resourceName!)); + readonly gatewayRouteName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(gatewayRouteArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); readonly virtualGateway = VirtualGateway.fromVirtualGatewayArn(this, 'virtualGateway', gatewayRouteArn); }(scope, id); } @@ -105,7 +105,7 @@ export class GatewayRoute extends cdk.Resource implements IGatewayRoute { constructor(scope: Construct, id: string, props: GatewayRouteProps) { super(scope, id, { - physicalName: props.gatewayRouteName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.gatewayRouteName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); this.virtualGateway = props.virtualGateway; diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index 30bb9cee8243a..3983a58c25742 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -136,7 +136,7 @@ export class Mesh extends MeshBase { * Import an existing mesh by arn */ public static fromMeshArn(scope: Construct, id: string, meshArn: string): IMesh { - const parts = cdk.Stack.of(scope).parseArn(meshArn); + const parts = cdk.Stack.of(scope).splitArn(meshArn, cdk.ArnFormat.SLASH_RESOURCE_NAME); class Import extends MeshBase { public meshName = parts.resourceName || ''; diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 49b101acfdf9f..3e604f6d97511 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -75,7 +75,7 @@ export class Route extends cdk.Resource implements IRoute { return new class extends cdk.Resource implements IRoute { readonly routeArn = routeArn; readonly virtualRouter = VirtualRouter.fromVirtualRouterArn(this, 'VirtualRouter', routeArn); - readonly routeName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(routeArn).resourceName!)); + readonly routeName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(routeArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); }(scope, id); } diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts index e287027c7e946..a99f014df8c63 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts @@ -134,7 +134,7 @@ export class VirtualGateway extends VirtualGatewayBase { */ public static fromVirtualGatewayArn(scope: Construct, id: string, virtualGatewayArn: string): IVirtualGateway { return new class extends VirtualGatewayBase { - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualGatewayArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualGatewayArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); readonly virtualGatewayArn = virtualGatewayArn; readonly virtualGatewayName = cdk.Fn.select(2, this.parsedArn); @@ -175,7 +175,7 @@ export class VirtualGateway extends VirtualGatewayBase { constructor(scope: Construct, id: string, props: VirtualGatewayProps) { super(scope, id, { - physicalName: props.virtualGatewayName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.virtualGatewayName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); this.mesh = props.mesh; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index bba83feb0b3d1..c2dda44d7a7de 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -140,7 +140,7 @@ export class VirtualNode extends VirtualNodeBase { public static fromVirtualNodeArn(scope: Construct, id: string, virtualNodeArn: string): IVirtualNode { return new class extends VirtualNodeBase { readonly virtualNodeArn = virtualNodeArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualNodeArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualNodeArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); readonly virtualNodeName = cdk.Fn.select(2, this.parsedArn); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index bc2023f19d028..f5dda554bd387 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -102,7 +102,7 @@ export class VirtualRouter extends VirtualRouterBase { public static fromVirtualRouterArn(scope: Construct, id: string, virtualRouterArn: string): IVirtualRouter { return new class extends VirtualRouterBase { readonly virtualRouterArn = virtualRouterArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualRouterArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualRouterArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly virtualRouterName = cdk.Fn.select(2, this.parsedArn); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index cb561eff19308..8dc31c2a56f66 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -40,7 +40,7 @@ export interface VirtualServiceProps { * It is recommended this follows the fully-qualified domain name format, * such as "my-service.default.svc.cluster.local". * - * @example service.domain.local + * Example value: `service.domain.local` * @default - A name is automatically generated */ readonly virtualServiceName?: string; @@ -65,7 +65,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { public static fromVirtualServiceArn(scope: Construct, id: string, virtualServiceArn: string): IVirtualService { return new class extends cdk.Resource implements IVirtualService { readonly virtualServiceArn = virtualServiceArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualServiceArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualServiceArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly virtualServiceName = cdk.Fn.select(2, this.parsedArn); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 989db40a2844f..0bf1fc94a030d 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -82,8 +89,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-acmpca": "0.0.0", diff --git a/packages/@aws-cdk/aws-appmesh/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-appmesh/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..09e9b7277edcb --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/rosetta/default.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import cdk = require('@aws-cdk/core'); +import acmpca = require('@aws-cdk/aws-acmpca'); +import appmesh = require('@aws-cdk/aws-appmesh'); +import certificatemanager = require('@aws-cdk/aws-certificatemanager'); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); +import ec2 = require('@aws-cdk/aws-ec2'); +import iam = require('@aws-cdk/aws-iam'); + +class Fixture extends cdk.Stack { + constructor(scope: cdk.Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-apprunner/package.json b/packages/@aws-cdk/aws-apprunner/package.json index 8b236cbfb01f5..89e2e15bd2e4b 100644 --- a/packages/@aws-cdk/aws-apprunner/package.json +++ b/packages/@aws-cdk/aws-apprunner/package.json @@ -82,7 +82,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ecr": "0.0.0", diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index 90e8f1938488c..2f99c4ac8f6b5 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index a191b51a86483..8e78c92bd4b7b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -24,6 +24,10 @@ The `@aws-cdk/aws-appsync` package contains constructs for building flexible APIs that use GraphQL. +```ts nofixture +import * as appsync from '@aws-cdk/aws-appsync'; +``` + ## Example ### DynamoDB @@ -52,24 +56,21 @@ type Mutation { CDK stack file `app-stack.ts`: ```ts -import * as appsync from '@aws-cdk/aws-appsync'; -import * as db from '@aws-cdk/aws-dynamodb'; - -const api = new appsync.GraphqlApi(stack, 'Api', { +const api = new appsync.GraphqlApi(this, 'Api', { name: 'demo', - schema: appsync.Schema.fromAsset(join(__dirname, 'schema.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'schema.graphql')), authorizationConfig: { defaultAuthorization: { - authorizationType: appsync.AuthorizationType.IAM + authorizationType: appsync.AuthorizationType.IAM, }, }, xrayEnabled: true, }); -const demoTable = new db.Table(stack, 'DemoTable', { +const demoTable = new dynamodb.Table(this, 'DemoTable', { partitionKey: { name: 'id', - type: db.AttributeType.STRING, + type: dynamodb.AttributeType.STRING, }, }); @@ -89,7 +90,7 @@ demoDS.createResolver({ fieldName: 'addDemo', requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem( appsync.PrimaryKey.partition('id').auto(), - appsync.Values.projecting('input') + appsync.Values.projecting('input'), ), responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), }); @@ -103,15 +104,15 @@ against the Data API with GraphQL queries, mutations, and subscriptions. ```ts // Create username and password secret for DB Cluster -const secret = new rds.DatabaseSecret(stack, 'AuroraSecret', { +const secret = new rds.DatabaseSecret(this, 'AuroraSecret', { username: 'clusteradmin', }); // The VPC to place the cluster in -const vpc = new ec2.Vpc(stack, 'AuroraVpc'); +const vpc = new ec2.Vpc(this, 'AuroraVpc'); // Create the serverless cluster, provide all values needed to customise the database. -const cluster = new rds.ServerlessCluster(stack, 'AuroraCluster', { +const cluster = new rds.ServerlessCluster(this, 'AuroraCluster', { engine: rds.DatabaseClusterEngine.AURORA_MYSQL, vpc, credentials: { username: 'clusteradmin' }, @@ -120,13 +121,14 @@ const cluster = new rds.ServerlessCluster(stack, 'AuroraCluster', { }); // Build a data source for AppSync to access the database. +declare const api: appsync.GraphqlApi; const rdsDS = api.addRdsDataSource('rds', cluster, secret, 'demos'); // Set up a resolver for an RDS query. rdsDS.createResolver({ typeName: 'Query', fieldName: 'getDemosRds', - requestMappingTemplate: MappingTemplate.fromString(` + requestMappingTemplate: appsync.MappingTemplate.fromString(` { "version": "2018-05-29", "statements": [ @@ -134,7 +136,7 @@ rdsDS.createResolver({ ] } `), - responseMappingTemplate: MappingTemplate.fromString(` + responseMappingTemplate: appsync.MappingTemplate.fromString(` $utils.toJson($utils.rds.toJsonObject($ctx.result)[0]) `), }); @@ -143,7 +145,7 @@ rdsDS.createResolver({ rdsDS.createResolver({ typeName: 'Mutation', fieldName: 'addDemoRds', - requestMappingTemplate: MappingTemplate.fromString(` + requestMappingTemplate: appsync.MappingTemplate.fromString(` { "version": "2018-05-29", "statements": [ @@ -156,7 +158,7 @@ rdsDS.createResolver({ } } `), - responseMappingTemplate: MappingTemplate.fromString(` + responseMappingTemplate: appsync.MappingTemplate.fromString(` $utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0]) `), }); @@ -212,11 +214,9 @@ GraphQL response mapping template `response.vtl`: CDK stack file `app-stack.ts`: ```ts -import * as appsync from '@aws-cdk/aws-appsync'; - -const api = new appsync.GraphqlApi(scope, 'api', { +const api = new appsync.GraphqlApi(this, 'api', { name: 'api', - schema: appsync.Schema.fromFile(join(__dirname, 'schema.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'schema.graphql')), }); const httpDs = api.addHttpDataSource( @@ -227,7 +227,7 @@ const httpDs = api.addHttpDataSource( description: 'from appsync to StepFunctions Workflow', authorizationConfig: { signingRegion: 'us-east-1', - signingServiceName: 'states' + signingServiceName: 'states', } } ); @@ -235,8 +235,8 @@ const httpDs = api.addHttpDataSource( httpDs.createResolver({ typeName: 'Mutation', fieldName: 'callStepFunction', - requestMappingTemplate: MappingTemplate.fromFile('request.vtl'), - responseMappingTemplate: MappingTemplate.fromFile('response.vtl') + requestMappingTemplate: appsync.MappingTemplate.fromFile('request.vtl'), + responseMappingTemplate: appsync.MappingTemplate.fromFile('response.vtl'), }); ``` @@ -247,16 +247,19 @@ through your AWS account. You can use AppSync resolvers to perform GraphQL opera such as queries, mutations, and subscriptions. ```ts -const user = new User(stack, 'User'); -const domain = new es.Domain(stack, 'Domain', { +import * as es from '@aws-cdk/aws-elasticsearch'; + +const user = new iam.User(this, 'User'); +const domain = new es.Domain(this, 'Domain', { version: es.ElasticsearchVersion.V7_1, - removalPolicy: cdk.RemovalPolicy.DESTROY, + removalPolicy: RemovalPolicy.DESTROY, fineGrainedAccessControl: { masterUserArn: user.userArn }, encryptionAtRest: { enabled: true }, nodeToNodeEncryption: true, enforceHttps: true, }); +declare const api: appsync.GraphqlApi; const ds = api.addElasticsearchDataSource('ds', domain); ds.createResolver({ @@ -293,23 +296,23 @@ When declaring your GraphQL Api, CDK defaults to a code-first approach if the `schema` property is not configured. ```ts -const api = new appsync.GraphqlApi(stack, 'api', { name: 'myApi' }); +const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi' }); ``` CDK will declare a `Schema` class that will give your Api access functions to -define your schema code-first: `addType`, `addObjectType`, `addToSchema`, etc. +define your schema code-first: `addType`, `addToSchema`, etc. You can also declare your `Schema` class outside of your CDK stack, to define your schema externally. ```ts const schema = new appsync.Schema(); -schema.addObjectType('demo', { +schema.addType(new appsync.ObjectType('demo', { definition: { id: appsync.GraphqlType.id() }, -}); -const api = new appsync.GraphqlApi(stack, 'api', { +})); +const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', - schema + schema, }); ``` @@ -321,9 +324,9 @@ You can define your GraphQL Schema from a file on disk. For convenience, use the `appsync.Schema.fromAsset` to specify the file representing your schema. ```ts -const api = appsync.GraphqlApi(stack, 'api', { +const api = new appsync.GraphqlApi(this, 'api', { name: 'myApi', - schema: appsync.Schema.fromAsset(join(__dirname, 'schema.graphl')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'schema.graphl')), }); ``` @@ -334,9 +337,11 @@ another stack into your CDK app. Utilizing the `fromXxx` function, you have the ability to add data sources and resolvers through a `IGraphqlApi` interface. ```ts -const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'IApi', { +declare const api: appsync.GraphqlApi; +declare const table: dynamodb.Table; +const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(this, 'IApi', { graphqlApiId: api.apiId, - graphqlArn: api.arn, + graphqlApiArn: api.arn, }); importedApi.addDynamoDbDataSource('TableDataSource', table); ``` @@ -362,9 +367,10 @@ authorization mode to finish defining your authorization. For example, this is a with AWS Lambda Authorization. ```ts -authFunction = new lambda.Function(stack, 'auth-function', {}); +import * as lambda from '@aws-cdk/aws-lambda'; +declare const authFunction: lambda.Function; -new appsync.GraphqlApi(stack, 'api', { +new appsync.GraphqlApi(this, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -390,7 +396,7 @@ for `IAM` authorized access you would configure the following. In `schema.graphql`: -```ts +```gql type Mutation { updateExample(...): ... @aws_iam @@ -401,18 +407,18 @@ In `IAM`: ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "appsync:GraphQL" - ], - "Resource": [ - "arn:aws:appsync:REGION:ACCOUNT_ID:apis/GRAPHQL_ID/types/Mutation/fields/updateExample" - ] - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "appsync:GraphQL" + ], + "Resource": [ + "arn:aws:appsync:REGION:ACCOUNT_ID:apis/GRAPHQL_ID/types/Mutation/fields/updateExample" + ] + } + ] } ``` @@ -423,14 +429,12 @@ To make this easier, CDK provides `grant` API. Use the `grant` function for more granular authorization. ```ts -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); -const api = new appsync.GraphqlApi(stack, 'API', { - definition -}); +declare const api: appsync.GraphqlApi; -api.grant(role, appsync.IamResource.custom('types/Mutation/fields/updateExample'), 'appsync:GraphQL') +api.grant(role, appsync.IamResource.custom('types/Mutation/fields/updateExample'), 'appsync:GraphQL'); ``` ### IamResource @@ -454,6 +458,9 @@ These include: - grantSubscription (use to grant access to Subscription fields) ```ts +declare const api: appsync.GraphqlApi; +declare const role: iam.Role; + // For generic types api.grantMutation(role, 'updateExample'); @@ -468,10 +475,12 @@ backend data source. Developers can compose operations (Functions) and execute them in sequence with Pipeline Resolvers. ```ts -const appsyncFunction = new appsync.AppsyncFunction(stack, 'function', { +declare const api: appsync.GraphqlApi; + +const appsyncFunction = new appsync.AppsyncFunction(this, 'function', { name: 'appsync_function', - api: api, - dataSource: apiDataSource, + api, + dataSource: api.addNoneDataSource('none'), requestMappingTemplate: appsync.MappingTemplate.fromFile('request.vtl'), responseMappingTemplate: appsync.MappingTemplate.fromFile('response.vtl'), }); @@ -481,10 +490,14 @@ AppSync Functions are used in tandem with pipeline resolvers to compose multiple operations. ```ts -const pipelineResolver = new appsync.Resolver(stack, 'pipeline', { - name: 'pipeline_resolver', - api: api, - dataSource: apiDataSource, +declare const api: appsync.GraphqlApi; +declare const appsyncFunction: appsync.AppsyncFunction; + +const pipelineResolver = new appsync.Resolver(this, 'pipeline', { + api, + dataSource: api.addNoneDataSource('none'), + typeName: 'typeName', + fieldName: 'fieldName', requestMappingTemplate: appsync.MappingTemplate.fromFile('beforeRequest.vtl'), pipelineConfig: [appsyncFunction], responseMappingTemplate: appsync.MappingTemplate.fromFile('afterResponse.vtl'), @@ -537,48 +550,38 @@ Above we see a schema that allows for generating paginated responses. For exampl we can query `allFilms(first: 100)` since `FilmConnection` acts as an intermediary for holding `FilmEdges` we can write a resolver to return the first 100 films. -In a separate file, we can declare our scalar types: `scalar-types.ts`. - -```ts -import { GraphqlType } from '@aws-cdk/aws-appsync'; - -export const string = appsync.GraphqlType.string(); -export const int = appsync.GraphqlType.int(); -``` - -In another separate file, we can declare our object types and related functions. +In a separate file, we can declare our object types and related functions. We will call this file `object-types.ts` and we will have created it in a way that allows us to generate other `XxxConnection` and `XxxEdges` in the future. -```ts -const pluralize = require('pluralize'); -import * as scalar from './scalar-types.ts'; +```ts nofixture import * as appsync from '@aws-cdk/aws-appsync'; +const pluralize = require('pluralize'); export const args = { - after: scalar.string, - first: scalar.int, - before: scalar.string, - last: scalar.int, + after: appsync.GraphqlType.string(), + first: appsync.GraphqlType.int(), + before: appsync.GraphqlType.string(), + last: appsync.GraphqlType.int(), }; export const Node = new appsync.InterfaceType('Node', { - definition: { id: scalar.string } + definition: { id: appsync.GraphqlType.string() } }); -export const FilmNode = new appsync.ObjectType.implementInterface('FilmNode', { +export const FilmNode = new appsync.ObjectType('FilmNode', { interfaceTypes: [Node], - definition: { filmName: scalar.string } + definition: { filmName: appsync.GraphqlType.string() } }); export function generateEdgeAndConnection(base: appsync.ObjectType) { const edge = new appsync.ObjectType(`${base.name}Edge`, { - definition: { node: base.attribute(), cursor: scalar.string } + definition: { node: base.attribute(), cursor: appsync.GraphqlType.string() } }); const connection = new appsync.ObjectType(`${base.name}Connection`, { definition: { - edges: edges.attribute({ isList: true }), + edges: edge.attribute({ isList: true }), [pluralize(base.name)]: base.attribute({ isList: true }), - totalCount: scalar.int, + totalCount: appsync.GraphqlType.int(), } }); return { edge: edge, connection: connection }; @@ -588,29 +591,30 @@ export function generateEdgeAndConnection(base: appsync.ObjectType) { Finally, we will go to our `cdk-stack` and combine everything together to generate our schema. -```ts -import * as appsync from '@aws-cdk/aws-appsync'; -import * as schema from './object-types'; +```ts fixture=with-objects +declare const dummyRequest: appsync.MappingTemplate; +declare const dummyResponse: appsync.MappingTemplate; -const api = new appsync.GraphqlApi(stack, 'Api', { +const api = new appsync.GraphqlApi(this, 'Api', { name: 'demo', }); -this.objectTypes = [ schema.Node, schema.Film ]; +const objectTypes = [ Node, FilmNode ]; -const filmConnections = schema.generateEdgeAndConnection(schema.Film); +const filmConnections = generateEdgeAndConnection(FilmNode); api.addQuery('allFilms', new appsync.ResolvableField({ - returnType: filmConnections.connection.attribute(), - args: schema.args, - dataSource: dummyDataSource, - requestMappingTemplate: dummyRequest, - responseMappingTemplate: dummyResponse, - }), -}); + returnType: filmConnections.connection.attribute(), + args: args, + dataSource: api.addNoneDataSource('none'), + requestMappingTemplate: dummyRequest, + responseMappingTemplate: dummyResponse, +})); -this.objectTypes.map((t) => api.addType(t)); -Object.keys(filmConnections).forEach((key) => api.addType(filmConnections[key])); +api.addType(Node); +api.addType(FilmNode); +api.addType(filmConnections.edge); +api.addType(filmConnections.connection); ``` Notice how we can utilize the `generateEdgeAndConnection` function to generate @@ -701,6 +705,9 @@ type Info { The CDK code required would be: ```ts +declare const api: appsync.GraphqlApi; +declare const dummyRequest: appsync.MappingTemplate; +declare const dummyResponse: appsync.MappingTemplate; const info = new appsync.ObjectType('Info', { definition: { node: new appsync.ResolvableField({ @@ -729,6 +736,9 @@ type Query { The CDK code required would be: ```ts +declare const api: appsync.GraphqlApi; +declare const dummyRequest: appsync.MappingTemplate; +declare const dummyResponse: appsync.MappingTemplate; const query = new appsync.ObjectType('Query', { definition: { get: new appsync.ResolvableField({ @@ -784,12 +794,12 @@ To learn more about **Interface Types**, read the docs [here](https://graphql.or the `demo` variable is an **Object Type**. **Object Types** are defined by GraphQL Types and are only usable when linked to a GraphQL Api. -You can create Object Types in three ways: +You can create Object Types in two ways: 1. Object Types can be created ***externally***. ```ts - const api = new appsync.GraphqlApi(stack, 'Api', { + const api = new appsync.GraphqlApi(this, 'Api', { name: 'demo', }); const demo = new appsync.ObjectType('Demo', { @@ -799,34 +809,28 @@ You can create Object Types in three ways: }, }); - api.addType(object); + api.addType(demo); ``` > This method allows for reusability and modularity, ideal for larger projects. For example, imagine moving all Object Type definition outside the stack. - `scalar-types.ts` - a file for scalar type definitions - - ```ts - export const required_string = appsync.GraphqlType.string({ isRequired: true }); - ``` - `object-types.ts` - a file for object type definitions - ```ts - import { required_string } from './scalar-types'; + ```ts nofixture + import * as appsync from '@aws-cdk/aws-appsync'; export const demo = new appsync.ObjectType('Demo', { definition: { - id: required_string, - version: required_string, + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), }, }); ``` `cdk-stack.ts` - a file containing our cdk stack - ```ts - import { demo } from './object-types'; + ```ts fixture=with-objects + declare const api: appsync.GraphqlApi; api.addType(demo); ``` @@ -869,6 +873,7 @@ enum Episode { The above GraphQL Enumeration Type can be expressed in CDK as the following: ```ts +declare const api: appsync.GraphqlApi; const episode = new appsync.EnumType('Episode', { definition: [ 'NEWHOPE', @@ -896,10 +901,11 @@ input Review { The above GraphQL Input Type can be expressed in CDK as the following: ```ts +declare const api: appsync.GraphqlApi; const review = new appsync.InputType('Review', { definition: { - stars: GraphqlType.int({ isRequired: true }), - commentary: GraphqlType.string(), + stars: appsync.GraphqlType.int({ isRequired: true }), + commentary: appsync.GraphqlType.string(), }, }); api.addType(review); @@ -923,6 +929,7 @@ The above GraphQL Union Type encompasses the Object Types of Human, Droid and St can be expressed in CDK as the following: ```ts +declare const api: appsync.GraphqlApi; const string = appsync.GraphqlType.string(); const human = new appsync.ObjectType('Human', { definition: { name: string } }); const droid = new appsync.ObjectType('Droid', { definition: { name: string } }); @@ -945,6 +952,11 @@ To add fields for these queries, we can simply run the `addQuery` function to ad to the schema's `Query` type. ```ts +declare const api: appsync.GraphqlApi; +declare const filmConnection: appsync.InterfaceType; +declare const dummyRequest: appsync.MappingTemplate; +declare const dummyResponse: appsync.MappingTemplate; + const string = appsync.GraphqlType.string(); const int = appsync.GraphqlType.int(); api.addQuery('allFilms', new appsync.ResolvableField({ @@ -968,10 +980,15 @@ To add fields for these mutations, we can simply run the `addMutation` function to the schema's `Mutation` type. ```ts +declare const api: appsync.GraphqlApi; +declare const filmNode: appsync.ObjectType; +declare const dummyRequest: appsync.MappingTemplate; +declare const dummyResponse: appsync.MappingTemplate; + const string = appsync.GraphqlType.string(); const int = appsync.GraphqlType.int(); api.addMutation('addFilm', new appsync.ResolvableField({ - returnType: film.attribute(), + returnType: filmNode.attribute(), args: { name: string, film_number: int }, dataSource: api.addNoneDataSource('none'), requestMappingTemplate: dummyRequest, @@ -994,10 +1011,13 @@ To add fields for these subscriptions, we can simply run the `addSubscription` f to the schema's `Subscription` type. ```ts +declare const api: appsync.GraphqlApi; +declare const film: appsync.InterfaceType; + api.addSubscription('addedFilm', new appsync.Field({ returnType: film.attribute(), args: { id: appsync.GraphqlType.id({ isRequired: true }) }, - directive: [appsync.Directive.subscribe('addFilm')], + directives: [appsync.Directive.subscribe('addFilm')], })); ``` diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 93c9078e32358..a63b757ba6b0b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,7 +1,7 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; -import { CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; +import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; @@ -150,7 +150,7 @@ export interface OpenIdConnectConfig { /** * The client identifier of the Relying party at the OpenID identity provider. * A regular expression can be specified so AppSync can validate against multiple client identifiers at a time. - * @example - 'ABCD|CDEF' where ABCD and CDEF are two different clientId + * @example - 'ABCD|CDEF' // where ABCD and CDEF are two different clientId * @default - * (All) */ readonly clientId?: string; @@ -347,7 +347,7 @@ export class IamResource { return this.arns.map((arn) => Stack.of(api).formatArn({ service: 'appsync', resource: `apis/${api.apiId}`, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${arn}`, })); } diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index a471666ffe98d..814bfb98e9600 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,8 +85,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cognito": "0.0.0", diff --git a/packages/@aws-cdk/aws-appsync/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-appsync/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..2b84336958cc7 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct, RemovalPolicy, Stack } from '@aws-cdk/core'; +import appsync = require('@aws-cdk/aws-appsync'); +import ec2 = require('@aws-cdk/aws-ec2'); +import dynamodb = require('@aws-cdk/aws-dynamodb'); +import iam = require('@aws-cdk/aws-iam'); +import rds = require('@aws-cdk/aws-rds'); +import path = require('path'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-appsync/rosetta/with-objects.ts-fixture b/packages/@aws-cdk/aws-appsync/rosetta/with-objects.ts-fixture new file mode 100644 index 0000000000000..1251aad728423 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/rosetta/with-objects.ts-fixture @@ -0,0 +1,49 @@ +// Fixture with packages imported, but nothing else +import { Construct, Stack } from '@aws-cdk/core'; +import appsync = require('@aws-cdk/aws-appsync'); +const pluralize = require('pluralize'); + +const args = { + after: appsync.GraphqlType.string(), + first: appsync.GraphqlType.int(), + before: appsync.GraphqlType.string(), + last: appsync.GraphqlType.int(), +}; + +const Node = new appsync.InterfaceType('Node', { + definition: { id: appsync.GraphqlType.string() } +}); + +const FilmNode = new appsync.ObjectType('FilmNode', { + interfaceTypes: [Node], + definition: { filmName: appsync.GraphqlType.string() } +}); + +function generateEdgeAndConnection(base: appsync.ObjectType) { + const edge = new appsync.ObjectType(`${base.name}Edge`, { + definition: { node: base.attribute(), cursor: appsync.GraphqlType.string() } + }); + const connection = new appsync.ObjectType(`${base.name}Connection`, { + definition: { + edges: edge.attribute({ isList: true }), + [pluralize(base.name)]: base.attribute({ isList: true }), + totalCount: appsync.GraphqlType.int(), + } + }); + return { edge: edge, connection: connection }; +} + +const demo = new appsync.ObjectType('Demo', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, +}); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-aps/package.json b/packages/@aws-cdk/aws-aps/package.json index 2e69665a8fb54..43022f3b9104d 100644 --- a/packages/@aws-cdk/aws-aps/package.json +++ b/packages/@aws-cdk/aws-aps/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index 468ce471247d7..6c69107b932ea 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -77,8 +77,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-auditmanager/package.json b/packages/@aws-cdk/aws-auditmanager/package.json index a787fca8f6ec3..35f9b576f80c2 100644 --- a/packages/@aws-cdk/aws-auditmanager/package.json +++ b/packages/@aws-cdk/aws-auditmanager/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 64bc1d528f2fa..a089ff8919749 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -68,9 +68,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "fast-check": "^2.17.0", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "fast-check": "^2.19.0", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index ac5aeabab06ca..b481a4de56eab 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -70,8 +70,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-autoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-autoscaling/README.md b/packages/@aws-cdk/aws-autoscaling/README.md index 67e55eee91a9f..11c67226afdb4 100644 --- a/packages/@aws-cdk/aws-autoscaling/README.md +++ b/packages/@aws-cdk/aws-autoscaling/README.md @@ -20,8 +20,7 @@ An `AutoScalingGroup` represents a number of instances on which you run your cod pick the size of the fleet, the instance type and the OS image: ```ts -import * as autoscaling from '@aws-cdk/aws-autoscaling'; -import * as ec2 from '@aws-cdk/aws-ec2'; +declare const vpc: ec2.Vpc; new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, @@ -36,7 +35,9 @@ your instances to be able to start arbitrary connections. Alternatively, you can group to attach to the instances that are launched, rather than have the group create a new one. ```ts -const mySecurityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {...}); +declare const vpc: ec2.Vpc; + +const mySecurityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), @@ -89,24 +90,31 @@ There are three ways to scale your capacity: The general pattern of autoscaling will look like this: ```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + minCapacity: 5, maxCapacity: 100 // ... }); -// Step scaling -autoScalingGroup.scaleOnMetric(...); - -// Target tracking scaling -autoScalingGroup.scaleOnCpuUtilization(...); -autoScalingGroup.scaleOnIncomingBytes(...); -autoScalingGroup.scaleOnOutgoingBytes(...); -autoScalingGroup.scaleOnRequestCount(...); -autoScalingGroup.scaleToTrackMetric(...); - -// Scheduled scaling -autoScalingGroup.scaleOnSchedule(...); +// Then call one of the scaling methods (explained below) +// +// autoScalingGroup.scaleOnMetric(...); +// +// autoScalingGroup.scaleOnCpuUtilization(...); +// autoScalingGroup.scaleOnIncomingBytes(...); +// autoScalingGroup.scaleOnOutgoingBytes(...); +// autoScalingGroup.scaleOnRequestCount(...); +// autoScalingGroup.scaleToTrackMetric(...); +// +// autoScalingGroup.scaleOnSchedule(...); ``` ### Step Scaling @@ -132,12 +140,14 @@ metric representing your worker utilization from your instances. After that, you would configure the scaling something like this: ```ts +declare const autoScalingGroup: autoscaling.AutoScalingGroup; + const workerUtilizationMetric = new cloudwatch.Metric({ namespace: 'MyService', metricName: 'WorkerUtilization' }); -capacity.scaleOnMetric('ScaleToCPU', { +autoScalingGroup.scaleOnMetric('ScaleToCPU', { metric: workerUtilizationMetric, scalingSteps: [ { upper: 10, change: -1 }, @@ -170,6 +180,8 @@ The following example scales to keep the CPU usage of your instances around 50% utilization: ```ts +declare const autoScalingGroup: autoscaling.AutoScalingGroup; + autoScalingGroup.scaleOnCpuUtilization('KeepSpareCPU', { targetUtilizationPercent: 50 }); @@ -178,10 +190,12 @@ autoScalingGroup.scaleOnCpuUtilization('KeepSpareCPU', { To scale on average network traffic in and out of your instances: ```ts +declare const autoScalingGroup: autoscaling.AutoScalingGroup; + autoScalingGroup.scaleOnIncomingBytes('LimitIngressPerInstance', { targetBytesPerSecond: 10 * 1024 * 1024 // 10 MB/s }); -autoScalingGroup.scaleOnOutcomingBytes('LimitEgressPerInstance', { +autoScalingGroup.scaleOnOutgoingBytes('LimitEgressPerInstance', { targetBytesPerSecond: 10 * 1024 * 1024 // 10 MB/s }); ``` @@ -191,6 +205,8 @@ AutoScalingGroups that have been attached to Application Load Balancers): ```ts +declare const autoScalingGroup: autoscaling.AutoScalingGroup; + autoScalingGroup.scaleOnRequestCount('LimitRPS', { targetRequestsPerSecond: 1000 }); @@ -214,6 +230,8 @@ The following example scales the fleet out in the morning, going back to natural scaling (all the way down to 1 instance if necessary) at night: ```ts +declare const autoScalingGroup: autoscaling.AutoScalingGroup; + autoScalingGroup.scaleOnSchedule('PrescaleInTheMorning', { schedule: autoscaling.Schedule.cron({ hour: '8', minute: '0' }), minCapacity: 20, @@ -246,7 +264,15 @@ Here's an example of using CloudFormation Init to write a file to the instance hosts on startup: ```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + // ... init: ec2.CloudFormationInit.fromElements( @@ -347,16 +373,30 @@ See [EC2 docs](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance To enable group metrics monitoring using the `groupMetrics` property: ```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + // Enable monitoring of all group metrics -new autoscaling.AutoScalingGroup(stack, 'ASG', { - groupMetrics: [GroupMetrics.all()], +new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + // ... + + groupMetrics: [autoscaling.GroupMetrics.all()], }); // Enable monitoring for a subset of group metrics -new autoscaling.AutoScalingGroup(stack, 'ASG', { - groupMetrics: [new autoscaling.GroupMetrics(GroupMetric.MIN_SIZE, GroupMetric.MAX_SIZE)], +new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + // ... + + groupMetrics: [new autoscaling.GroupMetrics(autoscaling.GroupMetric.MIN_SIZE, autoscaling.GroupMetric.MAX_SIZE)], }); ``` @@ -372,12 +412,56 @@ terminated. EC2 Capacity Providers for Amazon ECS requires this attribute be set to `true`. ```ts -new autoscaling.AutoScalingGroup(stack, 'ASG', { +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + +new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + + // ... + newInstancesProtectedFromScaleIn: true, +}); +``` + +## Configuring Instance Metadata Service (IMDS) + +### Toggling IMDSv1 + +You can configure [EC2 Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) options to either +allow both IMDSv1 and IMDSv2 or enforce IMDSv2 when interacting with the IMDS. + +To do this for a single `AutoScalingGroup`, you can use set the `requireImdsv2` property. +The example below demonstrates IMDSv2 being required on a single `AutoScalingGroup`: + +```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + +new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + // ... + + requireImdsv2: true, }); ``` +You can also use `AutoScalingGroupRequireImdsv2Aspect` to apply the operation to multiple AutoScalingGroups. +The example below demonstrates the `AutoScalingGroupRequireImdsv2Aspect` being used to require IMDSv2 for all AutoScalingGroups in a stack: + +```ts +const aspect = new autoscaling.AutoScalingGroupRequireImdsv2Aspect(); + +Aspects.of(this).add(aspect); +``` + ## Future work * [ ] CloudWatch Events (impossible to add currently as the AutoScalingGroup ARN is diff --git a/packages/@aws-cdk/aws-autoscaling/lib/aspects/index.ts b/packages/@aws-cdk/aws-autoscaling/lib/aspects/index.ts new file mode 100644 index 0000000000000..31fc534776144 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/lib/aspects/index.ts @@ -0,0 +1 @@ +export * from './require-imdsv2-aspect'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts b/packages/@aws-cdk/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts new file mode 100644 index 0000000000000..e399dce585d79 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/lib/aspects/require-imdsv2-aspect.ts @@ -0,0 +1,38 @@ +import * as cdk from '@aws-cdk/core'; +import { AutoScalingGroup } from '../auto-scaling-group'; +import { CfnLaunchConfiguration } from '../autoscaling.generated'; + +/** + * Aspect that makes IMDSv2 required on instances deployed by AutoScalingGroups. + */ +export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect { + constructor() { + } + + public visit(node: cdk.IConstruct): void { + if (!(node instanceof AutoScalingGroup)) { + return; + } + + const launchConfig = node.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration; + if (cdk.isResolvableObject(launchConfig.metadataOptions)) { + this.warn(node, 'CfnLaunchConfiguration.MetadataOptions field is a CDK token.'); + return; + } + + launchConfig.metadataOptions = { + ...launchConfig.metadataOptions, + httpTokens: 'required', + }; + } + + /** + * Adds a warning annotation to a node. + * + * @param node The scope to add the warning to. + * @param message The warning message. + */ + protected warn(node: cdk.IConstruct, message: string) { + cdk.Annotations.of(node).addWarning(`${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + } +} diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 027034249c4dd..45fd06c478dfc 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -7,6 +7,7 @@ import * as sns from '@aws-cdk/aws-sns'; import { Annotations, + Aspects, Aws, CfnAutoScalingRollingUpdate, CfnCreationPolicy, CfnUpdatePolicy, Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack, Tags, @@ -14,6 +15,7 @@ import { Tokenization, withResolved, } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { AutoScalingGroupRequireImdsv2Aspect } from './aspects'; import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated'; import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook'; import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action'; @@ -384,6 +386,13 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps { * @default - default options */ readonly initOptions?: ApplyCloudFormationInitOptions; + + /** + * Whether IMDSv2 should be required on launched instances. + * + * @default - false + */ + readonly requireImdsv2?: boolean; } /** @@ -1065,6 +1074,10 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements } this.spotPrice = props.spotPrice; + + if (props.requireImdsv2) { + Aspects.of(this).add(new AutoScalingGroupRequireImdsv2Aspect()); + } } /** diff --git a/packages/@aws-cdk/aws-autoscaling/lib/index.ts b/packages/@aws-cdk/aws-autoscaling/lib/index.ts index 69fede92e300b..186d1a3058fae 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/index.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/index.ts @@ -1,3 +1,4 @@ +export * from './aspects'; export * from './auto-scaling-group'; export * from './schedule'; export * from './lifecycle-hook'; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts index 71fde34af72cd..fa5d431bcf47b 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts @@ -8,14 +8,23 @@ import { Schedule } from './schedule'; * Properties for a scheduled scaling action */ export interface BasicScheduledActionProps { + /** + * Specifies the time zone for a cron expression. If a time zone is not provided, UTC is used by default. + * + * Valid values are the canonical names of the IANA time zones, derived from the IANA Time Zone Database (such as Etc/GMT+9 or Pacific/Tahiti). + * + * For more information, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + * + * @default - UTC + * + */ + readonly timeZone?: string; /** * When to perform this action. * * Supports cron expressions. * * For more information about cron expressions, see https://en.wikipedia.org/wiki/Cron. - * - * @example 0 8 * * ? */ readonly schedule: Schedule; @@ -96,6 +105,7 @@ export class ScheduledAction extends Resource { maxSize: props.maxCapacity, desiredCapacity: props.desiredCapacity, recurrence: props.schedule.expressionString, + timeZone: props.timeZone, }); } } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/volume.ts b/packages/@aws-cdk/aws-autoscaling/lib/volume.ts index cbe08bac7c6ab..1c5f5da6dc659 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/volume.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/volume.ts @@ -9,7 +9,7 @@ export interface BlockDevice { /** * The device name exposed to the EC2 instance * - * @example '/dev/sdh', 'xvdh' + * Supply a value like `/dev/sdh`, `xvdh`. * * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html */ @@ -18,8 +18,7 @@ export interface BlockDevice { /** * Defines the block device volume, to be either an Amazon EBS volume or an ephemeral instance store volume * - * @example BlockDeviceVolume.ebs(15), BlockDeviceVolume.ephemeral(0) - * + * Supply a value like `BlockDeviceVolume.ebs(15)`, `BlockDeviceVolume.ephemeral(0)`. */ readonly volume: BlockDeviceVolume; diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 0a04e34718096..dfdb08ad1ac2b 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,8 +86,8 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-autoscaling-common": "0.0.0", diff --git a/packages/@aws-cdk/aws-autoscaling/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-autoscaling/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..314520288f9cb --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/rosetta/default.ts-fixture @@ -0,0 +1,18 @@ +// Fixture with packages imported, but nothing else +import { Construct, Node } from 'constructs'; +import { Aspects, CfnOutput, Stack, Duration, Resource, SecretValue } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + + + diff --git a/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts b/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts new file mode 100644 index 0000000000000..22a58f097a98b --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts @@ -0,0 +1,79 @@ +import { + expect as expectCDK, + haveResourceLike, +} from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import { + AutoScalingGroup, + AutoScalingGroupRequireImdsv2Aspect, + CfnLaunchConfiguration, +} from '../../lib'; + +describe('AutoScalingGroupRequireImdsv2Aspect', () => { + let app: cdk.App; + let stack: cdk.Stack; + let vpc: ec2.Vpc; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'Stack'); + vpc = new ec2.Vpc(stack, 'Vpc'); + }); + + test('warns when metadataOptions is a token', () => { + // GIVEN + const asg = new AutoScalingGroup(stack, 'AutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }); + const launchConfig = asg.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration; + launchConfig.metadataOptions = fakeToken(); + const aspect = new AutoScalingGroupRequireImdsv2Aspect(); + + // WHEN + cdk.Aspects.of(stack).add(aspect); + + // THEN + expectCDK(stack).notTo(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + MetadataOptions: { + HttpTokens: 'required', + }, + })); + expect(asg.node.metadataEntry).toContainEqual({ + data: expect.stringContaining('CfnLaunchConfiguration.MetadataOptions field is a CDK token.'), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + + test('requires IMDSv2', () => { + // GIVEN + new AutoScalingGroup(stack, 'AutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }); + const aspect = new AutoScalingGroupRequireImdsv2Aspect(); + + // WHEN + cdk.Aspects.of(stack).add(aspect); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + MetadataOptions: { + HttpTokens: 'required', + }, + })); + }); +}); + +function fakeToken(): cdk.IResolvable { + return { + creationStack: [], + resolve: (_c) => {}, + toString: () => '', + }; +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 64795593e8ec4..25e84eeacaecf 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -4,6 +4,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as autoscaling from '../lib'; @@ -336,7 +337,7 @@ describe('auto scaling group', () => { }); - test('can configure replacing update', () => { + testDeprecated('can configure replacing update', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -367,7 +368,7 @@ describe('auto scaling group', () => { }); - test('can configure rolling update', () => { + testDeprecated('can configure rolling update', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -399,7 +400,7 @@ describe('auto scaling group', () => { }); - test('can configure resource signals', () => { + testDeprecated('can configure resource signals', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -506,11 +507,7 @@ describe('auto scaling group', () => { instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, - updateType: autoscaling.UpdateType.ROLLING_UPDATE, - rollingUpdateConfiguration: { - minSuccessfulInstancesPercent: 50, - pauseTime: cdk.Duration.seconds(345), - }, + updatePolicy: autoscaling.UpdatePolicy.rollingUpdate(), }); cdk.Tags.of(asg).add('superfood', 'acai'); @@ -960,8 +957,8 @@ describe('auto scaling group', () => { }); // THEN - expect(asg.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(asg.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -987,8 +984,8 @@ describe('auto scaling group', () => { }); // THEN - expect(asg.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(asg.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -1225,7 +1222,7 @@ describe('auto scaling group', () => { }); - test('throw if notification and notificationsTopics are both configured', () => { + testDeprecated('throw if notification and notificationsTopics are both configured', () => { // GIVEN const stack = new cdk.Stack(); const vpc = mockVpc(stack); @@ -1283,7 +1280,7 @@ describe('auto scaling group', () => { }); - test('setting notificationTopic configures all non test NotificationType', () => { + testDeprecated('setting notificationTopic configures all non test NotificationType', () => { // GIVEN const stack = new cdk.Stack(); const vpc = mockVpc(stack); @@ -1364,6 +1361,27 @@ describe('auto scaling group', () => { }); + + test('requires imdsv2', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + requireImdsv2: true, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + MetadataOptions: { + HttpTokens: 'required', + }, + }); + }); }); function mockVpc(stack: cdk.Stack) { diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json index 34f240a76559d..236be6f46be9e 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -671,6 +671,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "instance", "VpcId": { "Ref": "VPCB9E5F0B4" diff --git a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts index 8216968f406c1..a497efab73135 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts @@ -185,7 +185,7 @@ describe('scaling', () => { metric: new cloudwatch.Metric({ metricName: 'Henk', namespace: 'Test', - dimensions: { + dimensionsMap: { Mustache: 'Bushy', }, }), @@ -220,7 +220,7 @@ describe('scaling', () => { metric: new cloudwatch.Metric({ metricName: 'Legs', namespace: 'Henk', - dimensions: { Mustache: 'Bushy' }, + dimensionsMap: { Mustache: 'Bushy' }, }), // Adjust the number of legs to be closer to 2 scalingSteps: [ diff --git a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts index 3afbd887b45ae..57789d5ae36fe 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts @@ -1,11 +1,12 @@ import '@aws-cdk/assert-internal/jest'; import { expect, haveResource, MatchStyle } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as autoscaling from '../lib'; -describe('scheduled action', () => { +describeDeprecated('scheduled action', () => { test('can schedule an action', () => { // GIVEN const stack = new cdk.Stack(); @@ -46,6 +47,27 @@ describe('scheduled action', () => { }); + test('have timezone property', () => { + // GIVEN + const stack = new cdk.Stack(); + const asg = makeAutoScalingGroup(stack); + + // WHEN + asg.scaleOnSchedule('ScaleOutAtMiddaySeoul', { + schedule: autoscaling.Schedule.cron({ hour: '12', minute: '0' }), + minCapacity: 12, + timeZone: 'Asia/Seoul', + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::ScheduledAction', { + MinSize: 12, + Recurrence: '0 12 * * *', + TimeZone: 'Asia/Seoul', + })); + + }); + test('autoscaling group has recommended updatepolicy for scheduled actions', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index cd0c974c03acf..302cfbebd1dc2 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts b/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts index c363ab6424812..85d807b67b353 100644 --- a/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts +++ b/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts @@ -2,7 +2,7 @@ import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as efs from '@aws-cdk/aws-efs'; import * as rds from '@aws-cdk/aws-rds'; -import { IAspect, IConstruct, Stack } from '@aws-cdk/core'; +import { ArnFormat, IAspect, IConstruct, Stack } from '@aws-cdk/core'; export class BackupableResourcesCollector implements IAspect { public readonly resources: string[] = []; @@ -44,7 +44,7 @@ export class BackupableResourcesCollector implements IAspect { this.resources.push(Stack.of(node).formatArn({ service: 'rds', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: node.ref, })); } diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index 5f4f0d05c6001..059b6ed6bb17f 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { IResource, Lazy, Names, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBackupVault } from './backup.generated'; @@ -168,7 +168,7 @@ export class BackupVault extends BackupVaultBase { service: 'backup', resource: 'backup-vault', resourceName: backupVaultName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); return BackupVault.fromBackupVaultArn(scope, id, backupVaultArn); @@ -178,7 +178,7 @@ export class BackupVault extends BackupVaultBase { * Import an existing backup vault by arn */ public static fromBackupVaultArn(scope: Construct, id: string, backupVaultArn: string): IBackupVault { - const parsedArn = Stack.of(scope).parseArn(backupVaultArn); + const parsedArn = Stack.of(scope).splitArn(backupVaultArn, ArnFormat.SLASH_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error(`Backup Vault Arn ${backupVaultArn} does not have a resource name.`); diff --git a/packages/@aws-cdk/aws-backup/package.json b/packages/@aws-cdk/aws-backup/package.json index fa3dd9c9f3693..459075165dc90 100644 --- a/packages/@aws-cdk/aws-backup/package.json +++ b/packages/@aws-cdk/aws-backup/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-dynamodb": "0.0.0", diff --git a/packages/@aws-cdk/aws-backup/test/vault.test.ts b/packages/@aws-cdk/aws-backup/test/vault.test.ts index e72b039e75c63..ea63fb16323f2 100644 --- a/packages/@aws-cdk/aws-backup/test/vault.test.ts +++ b/packages/@aws-cdk/aws-backup/test/vault.test.ts @@ -2,7 +2,7 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { BackupVault, BackupVaultEvents } from '../lib'; let stack: Stack; @@ -267,7 +267,7 @@ test('import from arn', () => { service: 'backup', resource: 'backup-vault', resourceName: 'myVaultName', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); const vault = BackupVault.fromBackupVaultArn(stack, 'Vault', vaultArn); @@ -287,7 +287,7 @@ test('import from name', () => { service: 'backup', resource: 'backup-vault', resourceName: 'myVaultName', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); }); diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index f2900da8cda0f..67d31ea468b41 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -44,17 +44,17 @@ In **MANAGED** mode, AWS will handle the provisioning of compute resources to ac Below is an example of each available type of compute environment: ```ts -const defaultVpc = new ec2.Vpc(this, 'VPC'); +declare const vpc: ec2.Vpc; // default is managed -const awsManagedEnvironment = new batch.ComputeEnvironment(stack, 'AWS-Managed-Compute-Env', { +const awsManagedEnvironment = new batch.ComputeEnvironment(this, 'AWS-Managed-Compute-Env', { computeResources: { - vpc + vpc, } }); -const customerManagedEnvironment = new batch.ComputeEnvironment(stack, 'Customer-Managed-Compute-Env', { - managed: false // unmanaged environment +const customerManagedEnvironment = new batch.ComputeEnvironment(this, 'Customer-Managed-Compute-Env', { + managed: false, // unmanaged environment }); ``` @@ -65,7 +65,7 @@ It is possible to have AWS Batch submit spotfleet requests for obtaining compute ```ts const vpc = new ec2.Vpc(this, 'VPC'); -const spotEnvironment = new batch.ComputeEnvironment(stack, 'MySpotEnvironment', { +const spotEnvironment = new batch.ComputeEnvironment(this, 'MySpotEnvironment', { computeResources: { type: batch.ComputeResourceType.SPOT, bidPercentage: 75, // Bids for resources at 75% of the on-demand price @@ -81,7 +81,7 @@ It is possible to have AWS Batch submit jobs to be run on Fargate compute resour ```ts const vpc = new ec2.Vpc(this, 'VPC'); -const fargateSpotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', { +const fargateSpotEnvironment = new batch.ComputeEnvironment(this, 'MyFargateEnvironment', { computeResources: { type: batch.ComputeResourceType.FARGATE_SPOT, vpc, @@ -119,7 +119,8 @@ The alternative would be to use the `BEST_FIT_PROGRESSIVE` strategy in order for Simply define your Launch Template: -```ts +```text +// This example is only available in TypeScript const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { launchTemplateName: 'extra-storage-template', launchTemplateData: { @@ -129,17 +130,20 @@ const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { ebs: { encrypted: true, volumeSize: 100, - volumeType: 'gp2' - } - } - ] - } + volumeType: 'gp2', + }, + }, + ], + }, }); ``` and use it: ```ts +declare const vpc: ec2.Vpc; +declare const myLaunchTemplate: ec2.CfnLaunchTemplate; + const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { launchTemplate: { @@ -168,6 +172,7 @@ Occasionally, you will need to deviate from the default processing AMI. ECS Optimized Amazon Linux 2 example: ```ts +declare const vpc: ec2.Vpc; const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { image: new ecs.EcsOptimizedAmi({ @@ -181,11 +186,12 @@ const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { Custom based AMI example: ```ts +declare const vpc: ec2.Vpc; const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { image: ec2.MachineImage.genericLinux({ "[aws-region]": "[ami-ID]", - }) + }), vpc, } }); @@ -196,7 +202,8 @@ const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { Jobs are always submitted to a specific queue. This means that you have to create a queue before you can start submitting jobs. Each queue is mapped to at least one (and no more than three) compute environment. When the job is scheduled for execution, AWS Batch will select the compute environment based on ordinal priority and available capacity in each environment. ```ts -const jobQueue = new batch.JobQueue(stack, 'JobQueue', { +declare const computeEnvironment: batch.ComputeEnvironment; +const jobQueue = new batch.JobQueue(this, 'JobQueue', { computeEnvironments: [ { // Defines a collection of compute resources to handle assigned batch jobs @@ -213,13 +220,20 @@ const jobQueue = new batch.JobQueue(stack, 'JobQueue', { Sometimes you might have jobs that are more important than others, and when submitted, should take precedence over the existing jobs. To achieve this, you can create a priority based execution strategy, by assigning each queue its own priority: ```ts -const highPrioQueue = new batch.JobQueue(stack, 'JobQueue', { - computeEnvironments: sharedComputeEnvs, +declare const sharedComputeEnvs: batch.ComputeEnvironment; +const highPrioQueue = new batch.JobQueue(this, 'JobQueue', { + computeEnvironments: [{ + computeEnvironment: sharedComputeEnvs, + order: 1, + }], priority: 2, }); -const lowPrioQueue = new batch.JobQueue(stack, 'JobQueue', { - computeEnvironments: sharedComputeEnvs, +const lowPrioQueue = new batch.JobQueue(this, 'JobQueue', { + computeEnvironments: [{ + computeEnvironment: sharedComputeEnvs, + order: 1, + }], priority: 1, }); ``` @@ -241,9 +255,11 @@ const jobQueue = batch.JobQueue.fromJobQueueArn(this, 'imported-job-queue', 'arn A Batch Job definition helps AWS Batch understand important details about how to run your application in the scope of a Batch Job. This involves key information like resource requirements, what containers to run, how the compute environment should be prepared, and more. Below is a simple example of how to create a job definition: ```ts -const repo = ecr.Repository.fromRepositoryName(stack, 'batch-job-repo', 'todo-list'); +import * as ecr from '@aws-cdk/aws-ecr'; -new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { +const repo = ecr.Repository.fromRepositoryName(this, 'batch-job-repo', 'todo-list'); + +new batch.JobDefinition(this, 'batch-job-def-from-ecr', { container: { image: new ecs.EcrImage(repo, 'latest'), }, @@ -255,7 +271,7 @@ new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { Below is an example of how you can create a Batch Job Definition from a local Docker application. ```ts -new batch.JobDefinition(stack, 'batch-job-def-from-local', { +new batch.JobDefinition(this, 'batch-job-def-from-local', { container: { // todo-list is a directory containing a Dockerfile to build the application image: ecs.ContainerImage.fromAsset('../todo-list'), @@ -268,14 +284,16 @@ new batch.JobDefinition(stack, 'batch-job-def-from-local', { You can provide custom log driver and its configuration for the container. ```ts -new batch.JobDefinition(stack, 'job-def', { +import * as ssm from '@aws-cdk/aws-ssm'; + +new batch.JobDefinition(this, 'job-def', { container: { image: ecs.EcrImage.fromRegistry('docker/whalesay'), logConfiguration: { logDriver: batch.LogDriver.AWSLOGS, options: { 'awslogs-region': 'us-east-1' }, secretOptions: [ - batch.ExposedSecret.fromParametersStore('xyz', ssm.StringParameter.fromStringParameterName(stack, 'parameter', 'xyz')), + batch.ExposedSecret.fromParametersStore('xyz', ssm.StringParameter.fromStringParameterName(this, 'parameter', 'xyz')), ], }, }, @@ -303,8 +321,8 @@ Below is an example: ```ts // Without revision -const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition'); +const job1 = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition'); // With revision -const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition:3'); +const job2 = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition:3'); ``` diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 408e16c7bb98a..80f2b2c4e1e6d 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnComputeEnvironment } from './batch.generated'; @@ -325,7 +325,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment */ public static fromComputeEnvironmentArn(scope: Construct, id: string, computeEnvironmentArn: string): IComputeEnvironment { const stack = Stack.of(scope); - const computeEnvironmentName = stack.parseArn(computeEnvironmentArn).resourceName!; + const computeEnvironmentName = stack.splitArn(computeEnvironmentArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IComputeEnvironment { public readonly computeEnvironmentArn = computeEnvironmentArn; diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index dab8515acb6d1..025dea4516252 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; -import { Duration, IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Duration, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnJobDefinition } from './batch.generated'; import { ExposedSecret } from './exposed-secret'; @@ -382,7 +382,7 @@ export class JobDefinition extends Resource implements IJobDefinition { */ public static fromJobDefinitionArn(scope: Construct, id: string, jobDefinitionArn: string): IJobDefinition { const stack = Stack.of(scope); - const jobDefName = stack.parseArn(jobDefinitionArn).resourceName!; + const jobDefName = stack.splitArn(jobDefinitionArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IJobDefinition { public readonly jobDefinitionArn = jobDefinitionArn; @@ -405,7 +405,7 @@ export class JobDefinition extends Resource implements IJobDefinition { const jobDefArn = stack.formatArn({ service: 'batch', resource: 'job-definition', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: jobDefinitionName, }); diff --git a/packages/@aws-cdk/aws-batch/lib/job-queue.ts b/packages/@aws-cdk/aws-batch/lib/job-queue.ts index 0b5943b3fac91..8fb265d0ce86a 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-queue.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-queue.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnJobQueue } from './batch.generated'; import { IComputeEnvironment } from './compute-environment'; @@ -93,7 +93,7 @@ export class JobQueue extends Resource implements IJobQueue { */ public static fromJobQueueArn(scope: Construct, id: string, jobQueueArn: string): IJobQueue { const stack = Stack.of(scope); - const jobQueueName = stack.parseArn(jobQueueArn).resourceName!; + const jobQueueName = stack.splitArn(jobQueueArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IJobQueue { public readonly jobQueueArn = jobQueueArn; diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index 1c385bf9b0d91..68faf56d0e656 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,8 +84,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6fcfe8682a3ff --- /dev/null +++ b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as batch from '@aws-cdk/aws-batch'; +import * as ecs from '@aws-cdk/aws-ecs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index cabef6dd2e5f1..19686d9d20009 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-cassandra/package.json b/packages/@aws-cdk/aws-cassandra/package.json index bf0aa5a1e3a1e..a98df550ff629 100644 --- a/packages/@aws-cdk/aws-cassandra/package.json +++ b/packages/@aws-cdk/aws-cassandra/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-ce/package.json b/packages/@aws-cdk/aws-ce/package.json index 088987329b0f8..dc1b5be1a0743 100644 --- a/packages/@aws-cdk/aws-ce/package.json +++ b/packages/@aws-cdk/aws-ce/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index 7068591d9c076..331d40ad27276 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -113,6 +113,21 @@ new acm.DnsValidatedCertificate(this, 'CrossRegionCertificate', { }); ``` +## Requesting private certificates + +AWS Certificate Manager can create [private certificates](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-private.html) issued by [Private Certificate Authority (PCA)](https://docs.aws.amazon.com/acm-pca/latest/userguide/PcaWelcome.html). Validation of private certificates is not necessary. + +```ts +import * as acmpca from '@aws-cdk/aws-acmpca'; + +new acm.PrivateCertificate(stack, 'PrivateCertificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['cool.example.com', 'test.example.net'], // optional + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', + 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'), +}); +``` + ## Importing If you want to import an existing certificate, you can do so from its ARN: diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json index 5c0502d0f9c54..287da60856a44 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -29,21 +29,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.83", + "@types/aws-lambda": "^8.10.85", "@types/sinon": "^9.0.11", "@aws-cdk/cdk-build-tools": "0.0.0", "aws-sdk": "^2.596.0", "aws-sdk-mock": "^5.4.0", "eslint": "^7.32.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.24.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "lambda-tester": "^3.6.0", "sinon": "^9.2.4", - "nock": "^13.1.3", - "ts-jest": "^26.5.6" + "nock": "^13.2.1", + "ts-jest": "^27.0.7" } } diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts index b66a845429abf..de36b51a4ef00 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts @@ -22,7 +22,7 @@ export abstract class CertificateBase extends Resource implements ICertificate { return new cloudwatch.Metric({ period: Duration.days(1), ...props, - dimensions: { CertificateArn: this.certificateArn }, + dimensionsMap: { CertificateArn: this.certificateArn }, metricName: 'DaysToExpiry', namespace: 'AWS/CertificateManager', region: this.region, diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/index.ts b/packages/@aws-cdk/aws-certificatemanager/lib/index.ts index ee15ab71b8d4b..60be9ebed1c5c 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/index.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/index.ts @@ -1,5 +1,6 @@ export * from './certificate'; export * from './dns-validated-certificate'; +export * from './private-certificate'; export * from './util'; // AWS::CertificateManager CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/private-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/private-certificate.ts new file mode 100644 index 0000000000000..366b3ea623ac6 --- /dev/null +++ b/packages/@aws-cdk/aws-certificatemanager/lib/private-certificate.ts @@ -0,0 +1,66 @@ +import * as acmpca from '@aws-cdk/aws-acmpca'; +import { Construct } from 'constructs'; +import { ICertificate } from './certificate'; +import { CertificateBase } from './certificate-base'; +import { CfnCertificate } from './certificatemanager.generated'; + +/** + * Properties for your private certificate + */ +export interface PrivateCertificateProps { + /** + * Fully-qualified domain name to request a private certificate for. + * + * May contain wildcards, such as ``*.domain.com``. + */ + readonly domainName: string; + + /** + * Alternative domain names on your private certificate. + * + * Use this to register alternative domain names that represent the same site. + * + * @default - No additional FQDNs will be included as alternative domain names. + */ + readonly subjectAlternativeNames?: string[]; + + /** + * Private certificate authority (CA) that will be used to issue the certificate. + */ + readonly certificateAuthority: acmpca.ICertificateAuthority; +} + +/** + * A private certificate managed by AWS Certificate Manager + * + * @resource AWS::CertificateManager::Certificate + */ +export class PrivateCertificate extends CertificateBase implements ICertificate { + /** + * Import a certificate + */ + public static fromCertificateArn(scope: Construct, id: string, certificateArn: string): ICertificate { + class Import extends CertificateBase { + public readonly certificateArn = certificateArn; + } + + return new Import(scope, id); + } + + /** + * The certificate's ARN + */ + public readonly certificateArn: string; + + constructor(scope: Construct, id: string, props: PrivateCertificateProps) { + super(scope, id); + + const cert = new CfnCertificate(this, 'Resource', { + domainName: props.domainName, + subjectAlternativeNames: props.subjectAlternativeNames, + certificateAuthorityArn: props.certificateAuthority.certificateAuthorityArn, + }); + + this.certificateArn = cert.ref; + } +} diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts index 219bac1fd6a80..e917213d36a06 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts @@ -1,4 +1,4 @@ -import { Arn, Stack, Token } from '@aws-cdk/core'; +import { Arn, ArnFormat, Stack, Token } from '@aws-cdk/core'; import { ICertificate } from './certificate'; import { DnsValidatedCertificate } from './dns-validated-certificate'; import { publicSuffixes } from './public-suffixes'; @@ -40,7 +40,7 @@ export function getCertificateRegion(cert: ICertificate): string | undefined { } { - const { region } = Arn.parse(certificateArn); + const { region } = Arn.split(certificateArn, ArnFormat.SLASH_RESOURCE_NAME); if (region && !Token.isUnresolved(region)) { return region; diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 990c09fc3aa55..8faea6a4fdd7f 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -76,9 +76,10 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { + "@aws-cdk/aws-acmpca": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", @@ -88,6 +89,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-acmpca": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", @@ -101,7 +103,8 @@ "awslint": { "exclude": [ "props-physical-name:@aws-cdk/aws-certificatemanager.CertificateProps", - "props-physical-name:@aws-cdk/aws-certificatemanager.DnsValidatedCertificateProps" + "props-physical-name:@aws-cdk/aws-certificatemanager.DnsValidatedCertificateProps", + "props-physical-name:@aws-cdk/aws-certificatemanager.PrivateCertificateProps" ] }, "stability": "stable", diff --git a/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts index 22ebc72917b2b..8edcdf95b8158 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as route53 from '@aws-cdk/aws-route53'; import { Duration, Lazy, Stack } from '@aws-cdk/core'; -import { Certificate, CertificateValidation, ValidationMethod } from '../lib'; +import { Certificate, CertificateValidation } from '../lib'; test('apex domain selection by default', () => { const stack = new Stack(); @@ -43,9 +43,9 @@ test('validation domain can be overridden', () => { new Certificate(stack, 'Certificate', { domainName: 'test.example.com', - validationDomains: { + validation: CertificateValidation.fromEmail({ 'test.example.com': 'test.example.com', - }, + }), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { @@ -72,7 +72,7 @@ test('can configure validation method', () => { new Certificate(stack, 'Certificate', { domainName: 'test.example.com', - validationMethod: ValidationMethod.DNS, + validation: CertificateValidation.fromDns(), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { @@ -98,9 +98,9 @@ test('validationdomains can be given for a Token', () => { const domainName = Lazy.string({ produce: () => 'my.example.com' }); new Certificate(stack, 'Certificate', { domainName, - validationDomains: { + validation: CertificateValidation.fromEmail({ [domainName]: 'example.com', - }, + }), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { diff --git a/packages/@aws-cdk/aws-certificatemanager/test/private-certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/private-certificate.test.ts new file mode 100644 index 0000000000000..e43a4ca005691 --- /dev/null +++ b/packages/@aws-cdk/aws-certificatemanager/test/private-certificate.test.ts @@ -0,0 +1,102 @@ +import '@aws-cdk/assert-internal/jest'; +import * as acmpca from '@aws-cdk/aws-acmpca'; +import { Duration, Lazy, Stack } from '@aws-cdk/core'; +import { PrivateCertificate } from '../lib'; + +test('private certificate authority', () => { + const stack = new Stack(); + + new PrivateCertificate(stack, 'Certificate', { + domainName: 'test.example.com', + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', + 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'), + }); + + expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77', + }); +}); + +test('private certificate authority with subjectAlternativeNames', () => { + const stack = new Stack(); + + new PrivateCertificate(stack, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['extra.example.com'], + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', + 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'), + }); + + expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + SubjectAlternativeNames: ['extra.example.com'], + CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77', + }); +}); + +test('private certificate authority with multiple subjectAlternativeNames', () => { + const stack = new Stack(); + + new PrivateCertificate(stack, 'Certificate', { + domainName: 'test.example.com', + subjectAlternativeNames: ['*.test.example.com', '*.foo.test.example.com', 'bar.test.example.com'], + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', + 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'), + }); + + expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + SubjectAlternativeNames: ['*.test.example.com', '*.foo.test.example.com', 'bar.test.example.com'], + CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77', + }); +}); + +test('private certificate authority with tokens', () => { + const stack = new Stack(); + + const certificateAuthority = Lazy.string({ + produce: () => 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77', + }); + + const domainName = Lazy.string({ + produce: () => 'test.example.com', + }); + + const domainNameAlternative = Lazy.string({ + produce: () => 'extra.example.com', + }); + + new PrivateCertificate(stack, 'Certificate', { + domainName, + subjectAlternativeNames: [domainNameAlternative], + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', certificateAuthority), + }); + + expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { + DomainName: 'test.example.com', + SubjectAlternativeNames: ['extra.example.com'], + CertificateAuthorityArn: 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77', + }); +}); + +test('metricDaysToExpiry', () => { + const stack = new Stack(); + + const certificate = new PrivateCertificate(stack, 'Certificate', { + domainName: 'test.example.com', + certificateAuthority: acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'CA', + 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/023077d8-2bfa-4eb0-8f22-05c96deade77'), + }); + + expect(stack.resolve(certificate.metricDaysToExpiry().toMetricConfig())).toEqual({ + metricStat: { + dimensions: [{ name: 'CertificateArn', value: stack.resolve(certificate.certificateArn) }], + metricName: 'DaysToExpiry', + namespace: 'AWS/CertificateManager', + period: Duration.days(1), + statistic: 'Minimum', + }, + renderingProperties: expect.anything(), + }); +}); diff --git a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts index d7d48f9efc9f2..3960c3675f389 100644 --- a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts +++ b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts @@ -173,7 +173,7 @@ abstract class SlackChannelConfigurationBase extends cdk.Resource implements ISl return new cloudwatch.Metric({ namespace: 'AWS/Chatbot', region: 'us-east-1', - dimensions: { + dimensionsMap: { ConfigurationName: this.slackChannelConfigurationName, }, metricName, diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index 353033b0e9224..d71dea40ebb2a 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts index 01aa5d88c2c5d..a5d2b443ed486 100644 --- a/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts +++ b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts @@ -194,7 +194,7 @@ describe('SlackChannelConfiguration', () => { expect(metric).toEqual(new cloudwatch.Metric({ namespace: 'AWS/Chatbot', region: 'us-east-1', - dimensions: { + dimensionsMap: { ConfigurationName: 'ConfigurationName', }, metricName: 'MetricName', diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index 82d3f7d1b25dd..874f3d618f1f6 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-codecommit": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index c5145ca7127b9..60046c3442b29 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -79,9 +79,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts b/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts index 50014ed1b10e4..0354c7e9e4fa8 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { testDeprecated, describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnResource, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { NestedStack } from '../lib'; @@ -25,7 +26,7 @@ describe('resource dependencies', () => { }); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in the parent stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in the parent stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const parent = new Stack(undefined, 'root'); const nested = new NestedStack(parent, 'Nested'); @@ -43,7 +44,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in a grandparent stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in a grandparent stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const grantparent = new Stack(undefined, 'Grandparent'); const parent = new NestedStack(grantparent, 'Parent'); @@ -61,7 +62,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in parent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in parent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const parent = new Stack(undefined, 'root'); const nested = new NestedStack(parent, 'Nested'); @@ -78,7 +79,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in grantparent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in grantparent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const grandparent = new Stack(undefined, 'Grandparent'); const parent = new NestedStack(grandparent, 'Parent'); @@ -96,7 +97,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in sibling stack depends on a resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in sibling stack depends on a resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -118,7 +119,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in sibling stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in sibling stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -140,7 +141,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in nested sibling stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in nested sibling stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack = new Stack(app, 'Stack1'); @@ -178,7 +179,7 @@ describe('stack dependencies', () => { assertNoDependsOn(assembly, stack); }); - test('nested stack depends on itself', () => { + testDeprecated('nested stack depends on itself', () => { // GIVEN const app = new App(); const parent = new Stack(app, 'Parent'); @@ -191,7 +192,7 @@ describe('stack dependencies', () => { assertNoDependsOn(app.synth(), parent); }); - test('nested stack cannot depend on any of its parents', () => { + testDeprecated('nested stack cannot depend on any of its parents', () => { // GIVEN const root = new Stack(); const nested1 = new NestedStack(root, 'Nested1'); @@ -203,7 +204,7 @@ describe('stack dependencies', () => { expect(() => nested2.addDependency(root)).toThrow(/Nested stack 'Default\/Nested1\/Nested2' cannot depend on a parent stack 'Default'/); }); - test('any parent stack is by definition dependent on the nested stack so dependency is ignored', () => { + testDeprecated('any parent stack is by definition dependent on the nested stack so dependency is ignored', () => { // GIVEN const root = new Stack(); const nested1 = new NestedStack(root, 'Nested1'); @@ -215,7 +216,7 @@ describe('stack dependencies', () => { nested1.addDependency(nested2); }); - test('sibling nested stacks transfer to resources', () => { + testDeprecated('sibling nested stacks transfer to resources', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -230,7 +231,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('nested stack depends on a deeply nested stack', () => { + testDeprecated('nested stack depends on a deeply nested stack', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -246,7 +247,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('deeply nested stack depends on a parent nested stack', () => { + testDeprecated('deeply nested stack depends on a parent nested stack', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -262,7 +263,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('top-level stack depends on a nested stack within a sibling', () => { + testDeprecated('top-level stack depends on a nested stack within a sibling', () => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -281,7 +282,7 @@ describe('stack dependencies', () => { assertNoDependsOn(assembly, nested1); }); - test('nested stack within a sibling depends on top-level stack', () => { + testDeprecated('nested stack within a sibling depends on top-level stack', () => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts index 3b3a26346c2be..fdb5097463440 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts @@ -2,8 +2,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sns_subscriptions from '@aws-cdk/aws-sns-subscriptions'; import * as sqs from '@aws-cdk/aws-sqs'; -import { App, CfnParameter, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, CfnParameter, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -18,7 +17,7 @@ interface MyNestedStackProps { readonly topicNamePrefix: string; } -class MyNestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string, props: MyNestedStackProps) { const topicNamePrefixLogicalId = 'TopicNamePrefix'; diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts index 996ef461b0d2c..dc839504ef0e2 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts @@ -1,8 +1,7 @@ /// !cdk-integ pragma:ignore-assets import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -10,7 +9,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class NestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); @@ -26,7 +25,7 @@ class ParentStack extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - new NestedStack(this, 'Nested'); + new MyNestedStack(this, 'Nested'); } } diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts index b64a4d488e956..e810da5b1f1e5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts @@ -1,7 +1,6 @@ /// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import { NestedStack } from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; const app = new App(); const top = new Stack(app, 'nested-stacks-multi-refs'); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts index 768f69cb93209..f16fd9a9cbad3 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts @@ -1,7 +1,6 @@ /// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -9,7 +8,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class YourNestedStack extends cfn.NestedStack { +class YourNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); @@ -17,7 +16,7 @@ class YourNestedStack extends cfn.NestedStack { } } -class MyNestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts index 8d99bb75fc8af..b5ad58fbb6c57 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts @@ -1,14 +1,13 @@ /// !cdk-integ Stack1 Stack2 import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; const app = new App(); const stack1 = new Stack(app, 'Stack1'); const stack2 = new Stack(app, 'Stack2'); -const nestedUnderStack1 = new cfn.NestedStack(stack1, 'NestedUnderStack1'); +const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); const topicInNestedUnderStack1 = new sns.Topic(nestedUnderStack1, 'TopicInNestedUnderStack1'); new sns.Topic(stack2, 'TopicInStack2', { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts index 11ab60d5c80cf..74b2c50f321fc 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts @@ -5,14 +5,13 @@ /* eslint-disable @aws-cdk/no-core-construct */ import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct } from '@aws-cdk/core'; -class ConsumerNestedStack extends cfn.NestedStack { +class ConsumerNestedStack extends NestedStack { constructor(scope: Construct, id: string, topic: sns.Topic) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts index 03bfc0998a30e..43d8c9e89edfb 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts @@ -1,7 +1,6 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -11,7 +10,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class ProducerNestedStack extends cfn.NestedStack { +class ProducerNestedStack extends NestedStack { public readonly topic: sns.Topic; constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts index 3266d08e027f0..c055e3c77137c 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts @@ -1,7 +1,6 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -11,7 +10,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class ProducerNestedStack extends cfn.NestedStack { +class ProducerNestedStack extends NestedStack { public readonly topic: sns.Topic; constructor(scope: Construct, id: string) { @@ -21,7 +20,7 @@ class ProducerNestedStack extends cfn.NestedStack { } } -class ConsumerNestedStack extends cfn.NestedStack { +class ConsumerNestedStack extends NestedStack { constructor(scope: Construct, id: string, topic: sns.Topic) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py deleted file mode 100644 index f51514d0d3585..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py +++ /dev/null @@ -1,26 +0,0 @@ -def main(event, context): - import logging as log - import cfnresponse - log.getLogger().setLevel(log.INFO) - - # This needs to change if there are to be multiple resources in the same stack - physical_id = 'TheOnlyCustomResource' - - try: - log.info('Input event: %s', event) - - # Check if this is a Create and we're failing Creates - if event['RequestType'] == 'Create' and event['ResourceProperties'].get('FailCreate', False): - raise RuntimeError('Create failure requested') - - # Do the thing - message = event['ResourceProperties']['Message'] - attributes = { - 'Response': 'You said "%s"' % message - } - - cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id) - except Exception as e: - log.exception(e) - # cfnresponse's error message is always "see CloudWatch" - cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id) diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json deleted file mode 100644 index f033100eeec5a..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "Resources": { - "DemoResource5B5C546C": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebc492C6E5C", - "Arn" - ] - }, - "Message": "CustomResource says hello" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04": { - "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" - ] - ] - } - ] - } - }, - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebc492C6E5C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "def main(event, context):\n import logging as log\n import cfnresponse\n log.getLogger().setLevel(log.INFO)\n\n # This needs to change if there are to be multiple resources in the same stack\n physical_id = 'TheOnlyCustomResource'\n\n try:\n log.info('Input event: %s', event)\n\n # Check if this is a Create and we're failing Creates\n if event['RequestType'] == 'Create' and event['ResourceProperties'].get('FailCreate', False):\n raise RuntimeError('Create failure requested')\n\n # Do the thing\n message = event['ResourceProperties']['Message']\n attributes = {\n 'Response': 'You said \"%s\"' % message\n }\n\n cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id)\n except Exception as e:\n log.exception(e)\n # cfnresponse's error message is always \"see CloudWatch\"\n cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)\n" - }, - "Handler": "index.main", - "Role": { - "Fn::GetAtt": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04", - "Arn" - ] - }, - "Runtime": "python2.7", - "Timeout": 300 - }, - "DependsOn": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04" - ] - } - }, - "Outputs": { - "ResponseMessage": { - "Description": "The message that came back from the Custom Resource", - "Value": { - "Fn::GetAtt": [ - "DemoResource5B5C546C", - "Response" - ] - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts deleted file mode 100644 index e88b7113cb113..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as fs from 'fs'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import { CustomResource, CustomResourceProvider } from '../lib'; - -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; - -/* eslint-disable @aws-cdk/no-core-construct */ - -interface DemoResourceProps { - /** - * Message to echo - */ - message: string; - - /** - * Set this to true to fail the CREATE invocation - */ - failCreate?: boolean; -} - -class DemoResource extends Construct { - public readonly response: string; - - constructor(scope: Construct, id: string, props: DemoResourceProps) { - super(scope, id); - - const resource = new CustomResource(this, 'Resource', { - provider: CustomResourceProvider.fromLambda(new lambda.SingletonFunction(this, 'Singleton', { - uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc', - // This makes the demo only work as top-level TypeScript program, but that's fine for now - code: new lambda.InlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })), - handler: 'index.main', - timeout: cdk.Duration.minutes(5), - runtime: lambda.Runtime.PYTHON_2_7, - })), - properties: props, - }); - - this.response = resource.getAtt('Response').toString(); - } -} - -/** - * A stack that only sets up the CustomResource and shows how to get an attribute from it - */ -class SucceedingStack extends cdk.Stack { - constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - const resource = new DemoResource(this, 'DemoResource', { - message: 'CustomResource says hello', - }); - - // Publish the custom resource output - new cdk.CfnOutput(this, 'ResponseMessage', { - description: 'The message that came back from the Custom Resource', - value: resource.response, - }); - } -} -const app = new cdk.App(); - -new SucceedingStack(app, 'SucceedingStack'); - -app.synth(); diff --git a/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts b/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts index 90c39268ec7bb..07a43586971bd 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts @@ -4,6 +4,7 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnParameter, CfnResource, ContextProvider, LegacyStackSynthesizer, Names, Stack } from '@aws-cdk/core'; import { NestedStack } from '../lib/nested-stack'; @@ -14,1070 +15,1074 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ /* eslint-disable max-len */ -test('fails if defined as a root', () => { - // THEN - expect(() => new NestedStack(undefined as any, 'boom')).toThrow(/Nested stacks cannot be defined as a root construct/); -}); - -test('fails if defined without a parent stack', () => { - // GIVEN - const app = new App(); - const group = new Construct(app, 'group'); - - // THEN - expect(() => new NestedStack(app, 'boom')).toThrow(/must be defined within scope of another non-nested stack/); - expect(() => new NestedStack(group, 'bam')).toThrow(/must be defined within scope of another non-nested stack/); -}); +describeDeprecated('NestedStack', () => { -test('can be defined as a direct child or an indirect child of a Stack', () => { - // GIVEN - const parent = new Stack(); - - // THEN - expect(() => new NestedStack(parent, 'direct')).not.toThrow(); - expect(() => new NestedStack(new Construct(parent, 'group'), 'indirect')).not.toThrow(); -}); + test('fails if defined as a root', () => { + // THEN + expect(() => new NestedStack(undefined as any, 'boom')).toThrow(/Nested stacks cannot be defined as a root construct/); + }); -test('nested stack is not synthesized as a stack artifact into the assembly', () => { - // GIVEN - const app = new App(); - const parentStack = new Stack(app, 'parent-stack'); - new NestedStack(parentStack, 'nested-stack'); + test('fails if defined without a parent stack', () => { + // GIVEN + const app = new App(); + const group = new Construct(app, 'group'); - // WHEN - const assembly = app.synth(); + // THEN + expect(() => new NestedStack(app, 'boom')).toThrow(/must be defined within scope of another non-nested stack/); + expect(() => new NestedStack(group, 'bam')).toThrow(/must be defined within scope of another non-nested stack/); + }); - // THEN - expect(assembly.artifacts.length).toEqual(2); -}); + test('can be defined as a direct child or an indirect child of a Stack', () => { + // GIVEN + const parent = new Stack(); -test('the template of the nested stack is synthesized into the cloud assembly', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - - // WHEN - const assembly = app.synth(); - - // THEN - const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${Names.uniqueId(nested)}.nested.template.json`), 'utf-8')); - expect(template).toEqual({ - Resources: { - ResourceInNestedStack: { - Type: 'AWS::Resource::Nested', - }, - }, + // THEN + expect(() => new NestedStack(parent, 'direct')).not.toThrow(); + expect(() => new NestedStack(new Construct(parent, 'group'), 'indirect')).not.toThrow(); }); -}); -test('file asset metadata is associated with the parent stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - - // WHEN - const assembly = app.synth(); - - // THEN - expect(assembly.getStackByName(parent.stackName).assets).toEqual([{ - path: 'parentstacknestedstack844892C0.nested.template.json', - id: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', - packaging: 'file', - sourceHash: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', - s3BucketParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', - s3KeyParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - artifactHashParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7', - }]); -}); + test('nested stack is not synthesized as a stack artifact into the assembly', () => { + // GIVEN + const app = new App(); + const parentStack = new Stack(app, 'parent-stack'); + new NestedStack(parentStack, 'nested-stack'); -test('aws::cloudformation::stack is synthesized in the parent scope', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); + // WHEN + const assembly = app.synth(); - // WHEN - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + // THEN + expect(assembly.artifacts.length).toEqual(2); + }); - // THEN - const assembly = app.synth(); + test('the template of the nested stack is synthesized into the cloud assembly', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + + // WHEN + const assembly = app.synth(); + + // THEN + const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${Names.uniqueId(nested)}.nested.template.json`), 'utf-8')); + expect(template).toEqual({ + Resources: { + ResourceInNestedStack: { + Type: 'AWS::Resource::Nested', + }, + }, + }); + }); - // assembly has one stack (the parent) - expect(assembly.stacks.length).toEqual(1); + test('file asset metadata is associated with the parent stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - // but this stack has an asset that points to the synthesized template - expect(assembly.stacks[0].assets[0].path).toEqual('parentstacknestedstack844892C0.nested.template.json'); + // WHEN + const assembly = app.synth(); - // the template includes our resource - const filePath = path.join(assembly.directory, assembly.stacks[0].assets[0].path); - expect(JSON.parse(fs.readFileSync(filePath).toString('utf-8'))).toEqual({ - Resources: { ResourceInNestedStack: { Type: 'AWS::Resource::Nested' } }, + // THEN + expect(assembly.getStackByName(parent.stackName).assets).toEqual([{ + path: 'parentstacknestedstack844892C0.nested.template.json', + id: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', + packaging: 'file', + sourceHash: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', + s3BucketParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', + s3KeyParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + artifactHashParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7', + }]); }); - // the parent template includes the parameters and the nested stack resource which points to the s3 url - expect(parent).toMatchTemplate({ - Resources: { - nestedstackNestedStacknestedstackNestedStackResource71CDD241: { - Type: 'AWS::CloudFormation::Stack', - DeletionPolicy: 'Delete', - UpdateReplacePolicy: 'Delete', - Properties: { - TemplateURL: { - 'Fn::Join': [ - '', - [ - 'https://s3.', - { - Ref: 'AWS::Region', - }, - '.', - { - Ref: 'AWS::URLSuffix', - }, - '/', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', - }, - '/', - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - }, - ], - }, - ], - }, + test('aws::cloudformation::stack is synthesized in the parent scope', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + + // WHEN + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + + // THEN + const assembly = app.synth(); + + // assembly has one stack (the parent) + expect(assembly.stacks.length).toEqual(1); + + // but this stack has an asset that points to the synthesized template + expect(assembly.stacks[0].assets[0].path).toEqual('parentstacknestedstack844892C0.nested.template.json'); + + // the template includes our resource + const filePath = path.join(assembly.directory, assembly.stacks[0].assets[0].path); + expect(JSON.parse(fs.readFileSync(filePath).toString('utf-8'))).toEqual({ + Resources: { ResourceInNestedStack: { Type: 'AWS::Resource::Nested' } }, + }); + + // the parent template includes the parameters and the nested stack resource which points to the s3 url + expect(parent).toMatchTemplate({ + Resources: { + nestedstackNestedStacknestedstackNestedStackResource71CDD241: { + Type: 'AWS::CloudFormation::Stack', + DeletionPolicy: 'Delete', + UpdateReplacePolicy: 'Delete', + Properties: { + TemplateURL: { + 'Fn::Join': [ + '', + [ + 'https://s3.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + '/', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', + }, + '/', + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + }, + ], + }, + ], + }, + ], ], - ], + }, }, }, }, - }, - Parameters: { - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345: { - Type: 'String', - Description: 'S3 bucket for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', - }, - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6: { - Type: 'String', - Description: 'S3 key for asset version "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', - }, - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7: { - Type: 'String', - Description: 'Artifact hash for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + Parameters: { + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345: { + Type: 'String', + Description: 'S3 bucket for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6: { + Type: 'String', + Description: 'S3 key for asset version "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7: { + Type: 'String', + Description: 'Artifact hash for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, }, - }, + }); }); -}); -test('Stack.of()', () => { - class MyNestedStack extends NestedStack { - public readonly stackOfChild: Stack; + test('Stack.of()', () => { + class MyNestedStack extends NestedStack { + public readonly stackOfChild: Stack; - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string) { + super(scope, id); - const param = new CfnParameter(this, 'param', { type: 'String' }); - this.stackOfChild = Stack.of(param); + const param = new CfnParameter(this, 'param', { type: 'String' }); + this.stackOfChild = Stack.of(param); + } } - } - const parent = new Stack(); - const nested = new MyNestedStack(parent, 'nested'); + const parent = new Stack(); + const nested = new MyNestedStack(parent, 'nested'); - expect(nested.stackOfChild).toEqual(nested); - expect(Stack.of(nested)).toEqual(nested); -}); + expect(nested.stackOfChild).toEqual(nested); + expect(Stack.of(nested)).toEqual(nested); + }); -test('references within the nested stack are not reported as cross stack references', () => { - class MyNestedStack extends NestedStack { - constructor(scope: Construct, id: string) { - super(scope, id); + test('references within the nested stack are not reported as cross stack references', () => { + class MyNestedStack extends NestedStack { + constructor(scope: Construct, id: string) { + super(scope, id); - const param = new CfnParameter(this, 'param', { type: 'String' }); - new CfnResource(this, 'resource', { - type: 'My::Resource', - properties: { - SomeProp: param.valueAsString, - }, - }); + const param = new CfnParameter(this, 'param', { type: 'String' }); + new CfnResource(this, 'resource', { + type: 'My::Resource', + properties: { + SomeProp: param.valueAsString, + }, + }); + } } - } - const app = new App(); - const parent = new Stack(app, 'parent'); + const app = new App(); + const parent = new Stack(app, 'parent'); - new MyNestedStack(parent, 'nested'); + new MyNestedStack(parent, 'nested'); - // references are added during "prepare" - const assembly = app.synth(); + // references are added during "prepare" + const assembly = app.synth(); - expect(assembly.stacks.length).toEqual(1); - expect(assembly.stacks[0].dependencies).toEqual([]); -}); + expect(assembly.stacks.length).toEqual(1); + expect(assembly.stacks[0].dependencies).toEqual([]); + }); -test('references to a resource from the parent stack in a nested stack is translated into a cfn parameter', () => { - // WHEN - class MyNestedStack extends NestedStack { + test('references to a resource from the parent stack in a nested stack is translated into a cfn parameter', () => { + // WHEN + class MyNestedStack extends NestedStack { - constructor(scope: Construct, id: string, resourceFromParent: CfnResource) { - super(scope, id); + constructor(scope: Construct, id: string, resourceFromParent: CfnResource) { + super(scope, id); - new CfnResource(this, 'resource', { - type: 'AWS::Child::Resource', - properties: { - ReferenceToResourceInParentStack: resourceFromParent.ref, - }, - }); + new CfnResource(this, 'resource', { + type: 'AWS::Child::Resource', + properties: { + ReferenceToResourceInParentStack: resourceFromParent.ref, + }, + }); - new CfnResource(this, 'resource2', { - type: 'My::Resource::2', - properties: { - Prop1: resourceFromParent.getAtt('Attr'), - Prop2: resourceFromParent.ref, - }, - }); + new CfnResource(this, 'resource2', { + type: 'My::Resource::2', + properties: { + Prop1: resourceFromParent.getAtt('Attr'), + Prop2: resourceFromParent.ref, + }, + }); + } } - } - const app = new App(); - const parentStack = new Stack(app, 'parent'); + const app = new App(); + const parentStack = new Stack(app, 'parent'); - const resource = new CfnResource(parentStack, 'parent-resource', { type: 'AWS::Parent::Resource' }); + const resource = new CfnResource(parentStack, 'parent-resource', { type: 'AWS::Parent::Resource' }); - const nested = new MyNestedStack(parentStack, 'nested', resource); + const nested = new MyNestedStack(parentStack, 'nested', resource); - // THEN - app.synth(); + // THEN + app.synth(); - // nested template should use a parameter to reference the resource from the parent stack - expect(nested).toMatchTemplate({ - Resources: - { - resource: - { - Type: 'AWS::Child::Resource', - Properties: - { ReferenceToResourceInParentStack: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' } }, - }, - resource2: + // nested template should use a parameter to reference the resource from the parent stack + expect(nested).toMatchTemplate({ + Resources: { - Type: 'My::Resource::2', - Properties: + resource: { - Prop1: { Ref: 'referencetoparentparentresourceD56EA8F7Attr' }, - Prop2: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' }, + Type: 'AWS::Child::Resource', + Properties: + { ReferenceToResourceInParentStack: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' } }, + }, + resource2: + { + Type: 'My::Resource::2', + Properties: + { + Prop1: { Ref: 'referencetoparentparentresourceD56EA8F7Attr' }, + Prop2: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' }, + }, }, }, - }, - Parameters: - { - referencetoparentparentresourceD56EA8F7Ref: { Type: 'String' }, - referencetoparentparentresourceD56EA8F7Attr: { Type: 'String' }, - }, - }); - - // parent template should pass in the value through the parameter - expect(parentStack).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoparentparentresourceD56EA8F7Ref: { - Ref: 'parentresource', + Parameters: + { + referencetoparentparentresourceD56EA8F7Ref: { Type: 'String' }, + referencetoparentparentresourceD56EA8F7Attr: { Type: 'String' }, }, - referencetoparentparentresourceD56EA8F7Attr: { - 'Fn::GetAtt': [ - 'parentresource', - 'Attr', - ], + }); + + // parent template should pass in the value through the parameter + expect(parentStack).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoparentparentresourceD56EA8F7Ref: { + Ref: 'parentresource', + }, + referencetoparentparentresourceD56EA8F7Attr: { + 'Fn::GetAtt': [ + 'parentresource', + 'Attr', + ], + }, }, - }, + }); }); -}); -test('references to a resource in the nested stack in the parent is translated into a cfn output', () => { - class MyNestedStack extends NestedStack { - public readonly resourceFromChild: CfnResource; + test('references to a resource in the nested stack in the parent is translated into a cfn output', () => { + class MyNestedStack extends NestedStack { + public readonly resourceFromChild: CfnResource; - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string) { + super(scope, id); - this.resourceFromChild = new CfnResource(this, 'resource', { - type: 'AWS::Child::Resource', - }); + this.resourceFromChild = new CfnResource(this, 'resource', { + type: 'AWS::Child::Resource', + }); + } } - } - const app = new App(); - const parentStack = new Stack(app, 'parent'); + const app = new App(); + const parentStack = new Stack(app, 'parent'); - const nested = new MyNestedStack(parentStack, 'nested'); + const nested = new MyNestedStack(parentStack, 'nested'); - new CfnResource(parentStack, 'another-parent-resource', { - type: 'AWS::Parent::Resource', - properties: { - RefToResourceInNestedStack: nested.resourceFromChild.ref, - }, - }); + new CfnResource(parentStack, 'another-parent-resource', { + type: 'AWS::Parent::Resource', + properties: { + RefToResourceInNestedStack: nested.resourceFromChild.ref, + }, + }); - // references are added during "prepare" - app.synth(); - - // nested template should use a parameter to reference the resource from the parent stack - expect(nested).toMatchTemplate({ - Resources: { - resource: { Type: 'AWS::Child::Resource' }, - }, - Outputs: { - parentnestedresource4D680677Ref: { Value: { Ref: 'resource' } }, - }, - }); + // references are added during "prepare" + app.synth(); - // parent template should pass in the value through the parameter - expect(parentStack).toHaveResource('AWS::Parent::Resource', { - RefToResourceInNestedStack: { - 'Fn::GetAtt': [ - 'nestedNestedStacknestedNestedStackResource3DD143BF', - 'Outputs.parentnestedresource4D680677Ref', - ], - }, - }); -}); + // nested template should use a parameter to reference the resource from the parent stack + expect(nested).toMatchTemplate({ + Resources: { + resource: { Type: 'AWS::Child::Resource' }, + }, + Outputs: { + parentnestedresource4D680677Ref: { Value: { Ref: 'resource' } }, + }, + }); -test('nested stack references a resource from another non-nested stack (not the parent)', () => { - // GIVEN - const app = new App(); - const stack1 = new Stack(app, 'Stack1'); - const stack2 = new Stack(app, 'Stack2'); - const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); - const resourceInStack2 = new CfnResource(stack2, 'ResourceInStack2', { type: 'MyResource' }); - - // WHEN - new CfnResource(nestedUnderStack1, 'ResourceInNestedStack1', { - type: 'Nested::Resource', - properties: { - RefToSibling: resourceInStack2.getAtt('MyAttribute'), - }, + // parent template should pass in the value through the parameter + expect(parentStack).toHaveResource('AWS::Parent::Resource', { + RefToResourceInNestedStack: { + 'Fn::GetAtt': [ + 'nestedNestedStacknestedNestedStackResource3DD143BF', + 'Outputs.parentnestedresource4D680677Ref', + ], + }, + }); }); - // THEN - const assembly = app.synth(); - - // producing stack should have an export - expect(stack2).toMatchTemplate({ - Resources: { - ResourceInStack2: { Type: 'MyResource' }, - }, - Outputs: { - ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009: { - Value: { 'Fn::GetAtt': ['ResourceInStack2', 'MyAttribute'] }, - Export: { Name: 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009' }, + test('nested stack references a resource from another non-nested stack (not the parent)', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); + const resourceInStack2 = new CfnResource(stack2, 'ResourceInStack2', { type: 'MyResource' }); + + // WHEN + new CfnResource(nestedUnderStack1, 'ResourceInNestedStack1', { + type: 'Nested::Resource', + properties: { + RefToSibling: resourceInStack2.getAtt('MyAttribute'), }, - }, - }); + }); + + // THEN + const assembly = app.synth(); - // nested stack uses Fn::ImportValue like normal - expect(nestedUnderStack1).toMatchTemplate({ - Resources: { - ResourceInNestedStack1: { - Type: 'Nested::Resource', - Properties: { - RefToSibling: { - 'Fn::ImportValue': 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009', + // producing stack should have an export + expect(stack2).toMatchTemplate({ + Resources: { + ResourceInStack2: { Type: 'MyResource' }, + }, + Outputs: { + ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009: { + Value: { 'Fn::GetAtt': ['ResourceInStack2', 'MyAttribute'] }, + Export: { Name: 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009' }, + }, + }, + }); + + // nested stack uses Fn::ImportValue like normal + expect(nestedUnderStack1).toMatchTemplate({ + Resources: { + ResourceInNestedStack1: { + Type: 'Nested::Resource', + Properties: { + RefToSibling: { + 'Fn::ImportValue': 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009', + }, }, }, }, - }, + }); + + // verify a depedency was established between the parents + const stack1Artifact = assembly.getStackByName(stack1.stackName); + const stack2Artifact = assembly.getStackByName(stack2.stackName); + expect(stack1Artifact.dependencies.length).toEqual(1); + expect(stack2Artifact.dependencies.length).toEqual(0); + expect(stack1Artifact.dependencies[0]).toEqual(stack2Artifact); }); - // verify a depedency was established between the parents - const stack1Artifact = assembly.getStackByName(stack1.stackName); - const stack2Artifact = assembly.getStackByName(stack2.stackName); - expect(stack1Artifact.dependencies.length).toEqual(1); - expect(stack2Artifact.dependencies.length).toEqual(0); - expect(stack1Artifact.dependencies[0]).toEqual(stack2Artifact); -}); + test('nested stack within a nested stack references a resource in a sibling top-level stack', () => { + // GIVEN + const app = new App(); + const consumerTopLevel = new Stack(app, 'ConsumerTopLevel'); + const consumerNested1 = new NestedStack(consumerTopLevel, 'ConsumerNested1'); + const consumerNested2 = new NestedStack(consumerNested1, 'ConsumerNested2'); + const producerTopLevel = new Stack(app, 'ProducerTopLevel'); + const producer = new CfnResource(producerTopLevel, 'Producer', { type: 'Producer' }); + + // WHEN + new CfnResource(consumerNested2, 'Consumer', { + type: 'Consumer', + properties: { + Ref: producer.ref, + }, + }); -test('nested stack within a nested stack references a resource in a sibling top-level stack', () => { - // GIVEN - const app = new App(); - const consumerTopLevel = new Stack(app, 'ConsumerTopLevel'); - const consumerNested1 = new NestedStack(consumerTopLevel, 'ConsumerNested1'); - const consumerNested2 = new NestedStack(consumerNested1, 'ConsumerNested2'); - const producerTopLevel = new Stack(app, 'ProducerTopLevel'); - const producer = new CfnResource(producerTopLevel, 'Producer', { type: 'Producer' }); - - // WHEN - new CfnResource(consumerNested2, 'Consumer', { - type: 'Consumer', - properties: { - Ref: producer.ref, - }, + // THEN + const manifest = app.synth(); + const consumerDeps = manifest.getStackArtifact(consumerTopLevel.artifactId).dependencies.map(d => d.id); + expect(consumerDeps).toEqual(['ProducerTopLevel']); }); - // THEN - const manifest = app.synth(); - const consumerDeps = manifest.getStackArtifact(consumerTopLevel.artifactId).dependencies.map(d => d.id); - expect(consumerDeps).toEqual(['ProducerTopLevel']); -}); - -test('another non-nested stack takes a reference on a resource within the nested stack (the parent exports)', () => { - // GIVEN - const app = new App(); - const stack1 = new Stack(app, 'Stack1'); - const stack2 = new Stack(app, 'Stack2'); - const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); - const resourceInNestedStack = new CfnResource(nestedUnderStack1, 'ResourceInNestedStack', { type: 'MyResource' }); - - // WHEN - new CfnResource(stack2, 'ResourceInStack2', { - type: 'JustResource', - properties: { - RefToSibling: resourceInNestedStack.getAtt('MyAttribute'), - }, - }); + test('another non-nested stack takes a reference on a resource within the nested stack (the parent exports)', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); + const resourceInNestedStack = new CfnResource(nestedUnderStack1, 'ResourceInNestedStack', { type: 'MyResource' }); + + // WHEN + new CfnResource(stack2, 'ResourceInStack2', { + type: 'JustResource', + properties: { + RefToSibling: resourceInNestedStack.getAtt('MyAttribute'), + }, + }); - // THEN - const assembly = app.synth(); + // THEN + const assembly = app.synth(); - // nested stack should output this value as if it was referenced by the parent (without the export) - expect(nestedUnderStack1).toMatchTemplate({ - Resources: { - ResourceInNestedStack: { - Type: 'MyResource', + // nested stack should output this value as if it was referenced by the parent (without the export) + expect(nestedUnderStack1).toMatchTemplate({ + Resources: { + ResourceInNestedStack: { + Type: 'MyResource', + }, }, - }, - Outputs: { - Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute: { - Value: { - 'Fn::GetAtt': [ - 'ResourceInNestedStack', - 'MyAttribute', - ], + Outputs: { + Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute: { + Value: { + 'Fn::GetAtt': [ + 'ResourceInNestedStack', + 'MyAttribute', + ], + }, }, }, - }, - }); + }); - // parent stack (stack1) should export this value - expect(assembly.getStackByName(stack1.stackName).template.Outputs).toEqual({ - ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3: { - Value: { 'Fn::GetAtt': ['NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B', 'Outputs.Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute'] }, - Export: { Name: 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3' }, - }, - }); - - // consuming stack should use ImportValue to import the value from the parent stack - expect(stack2).toMatchTemplate({ - Resources: { - ResourceInStack2: { - Type: 'JustResource', - Properties: { - RefToSibling: { - 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3', + // parent stack (stack1) should export this value + expect(assembly.getStackByName(stack1.stackName).template.Outputs).toEqual({ + ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3: { + Value: { 'Fn::GetAtt': ['NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B', 'Outputs.Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute'] }, + Export: { Name: 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3' }, + }, + }); + + // consuming stack should use ImportValue to import the value from the parent stack + expect(stack2).toMatchTemplate({ + Resources: { + ResourceInStack2: { + Type: 'JustResource', + Properties: { + RefToSibling: { + 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3', + }, }, }, }, - }, + }); + + expect(assembly.stacks.length).toEqual(2); + const stack1Artifact = assembly.getStackByName(stack1.stackName); + const stack2Artifact = assembly.getStackByName(stack2.stackName); + expect(stack1Artifact.dependencies.length).toEqual(0); + expect(stack2Artifact.dependencies.length).toEqual(1); + expect(stack2Artifact.dependencies[0]).toEqual(stack1Artifact); }); - expect(assembly.stacks.length).toEqual(2); - const stack1Artifact = assembly.getStackByName(stack1.stackName); - const stack2Artifact = assembly.getStackByName(stack2.stackName); - expect(stack1Artifact.dependencies.length).toEqual(0); - expect(stack2Artifact.dependencies.length).toEqual(1); - expect(stack2Artifact.dependencies[0]).toEqual(stack1Artifact); -}); - -test('references between sibling nested stacks should output from one and getAtt from the other', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'Parent'); - const nested1 = new NestedStack(parent, 'Nested1'); - const nested2 = new NestedStack(parent, 'Nested2'); - const resource1 = new CfnResource(nested1, 'Resource1', { type: 'Resource1' }); - - // WHEN - new CfnResource(nested2, 'Resource2', { - type: 'Resource2', - properties: { - RefToResource1: resource1.ref, - }, - }); + test('references between sibling nested stacks should output from one and getAtt from the other', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'Parent'); + const nested1 = new NestedStack(parent, 'Nested1'); + const nested2 = new NestedStack(parent, 'Nested2'); + const resource1 = new CfnResource(nested1, 'Resource1', { type: 'Resource1' }); + + // WHEN + new CfnResource(nested2, 'Resource2', { + type: 'Resource2', + properties: { + RefToResource1: resource1.ref, + }, + }); - // THEN - app.synth(); + // THEN + app.synth(); - // producing nested stack - expect(nested1).toMatchTemplate({ - Resources: { - Resource1: { - Type: 'Resource1', - }, - }, - Outputs: { - ParentNested1Resource15F3F0657Ref: { - Value: { - Ref: 'Resource1', + // producing nested stack + expect(nested1).toMatchTemplate({ + Resources: { + Resource1: { + Type: 'Resource1', }, }, - }, - }); - - // consuming nested stack - expect(nested2).toMatchTemplate({ - Resources: { - Resource2: { - Type: 'Resource2', - Properties: { - RefToResource1: { - Ref: 'referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref', + Outputs: { + ParentNested1Resource15F3F0657Ref: { + Value: { + Ref: 'Resource1', }, }, }, - }, - Parameters: { - referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { - Type: 'String', + }); + + // consuming nested stack + expect(nested2).toMatchTemplate({ + Resources: { + Resource2: { + Type: 'Resource2', + Properties: { + RefToResource1: { + Ref: 'referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref', + }, + }, + }, }, - }, - }); - - // parent - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { - 'Fn::GetAtt': [ - 'Nested1NestedStackNested1NestedStackResourceCD0AD36B', - 'Outputs.ParentNested1Resource15F3F0657Ref', - ], + Parameters: { + referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { + Type: 'String', + }, }, - }, - }); -}); - -test('stackId returns AWS::StackId when referenced from the context of the nested stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); + }); - // WHEN - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { MyStackId: nested.stackId }, - }); - - // THEN - expect(nested).toHaveResource('Nested::Resource', { - MyStackId: { Ref: 'AWS::StackId' }, - }); -}); - -test('stackId returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(parent, 'ParentResource', { - type: 'Parent::Resource', - properties: { NestedStackId: nested.stackId }, - }); - - // THEN - expect(parent).toHaveResource('Parent::Resource', { - NestedStackId: { Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD' }, - }); -}); - -test('stackName returns AWS::StackName when referenced from the context of the nested stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { MyStackName: nested.stackName }, - }); - - // THEN - expect(nested).toHaveResource('Nested::Resource', { - MyStackName: { Ref: 'AWS::StackName' }, - }); -}); - -test('stackName returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(parent, 'ParentResource', { - type: 'Parent::Resource', - properties: { NestedStackName: nested.stackName }, - }); - - // THEN - expect(parent).toHaveResource('Parent::Resource', { - NestedStackName: { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '/', - { - Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD', - }, + // parent + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { + 'Fn::GetAtt': [ + 'Nested1NestedStackNested1NestedStackResourceCD0AD36B', + 'Outputs.ParentNested1Resource15F3F0657Ref', ], }, - ], - }, + }, + }); }); -}); - -test('"account", "region" and "environment" are all derived from the parent', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); - - // WHEN - const nested = new NestedStack(parent, 'NestedStack'); - // THEN - expect(nested.environment).toEqual(parent.environment); - expect(nested.account).toEqual(parent.account); - expect(nested.region).toEqual(parent.region); -}); - -test('double-nested stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'stack'); - - // WHEN - const nested1 = new NestedStack(parent, 'Nested1'); - const nested2 = new NestedStack(nested1, 'Nested2'); - - new CfnResource(nested1, 'Resource1', { type: 'Resource::1' }); - new CfnResource(nested2, 'Resource2', { type: 'Resource::2' }); - - // THEN - const assembly = app.synth(); - - // nested2 is a "leaf", so it's just the resource - expect(nested2).toMatchTemplate({ - Resources: { - Resource2: { Type: 'Resource::2' }, - }, + test('stackId returns AWS::StackId when referenced from the context of the nested stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { MyStackId: nested.stackId }, + }); + + // THEN + expect(nested).toHaveResource('Nested::Resource', { + MyStackId: { Ref: 'AWS::StackId' }, + }); }); - const middleStackHash = '7c426f7299a739900279ac1ece040397c1913cdf786f5228677b289f4d5e4c48'; - const bucketSuffix = 'C706B101'; - const versionSuffix = '4B193AA5'; - const hashSuffix = 'E28F0693'; - - // nested1 wires the nested2 template through parameters, so we expect those - expect(nested1).toHaveResource('Resource::1'); - const nested2Template = SynthUtils.toCloudFormation(nested1); - expect(nested2Template.Parameters).toEqual({ - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Type: 'String' }, - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Type: 'String' }, + test('stackId returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(parent, 'ParentResource', { + type: 'Parent::Resource', + properties: { NestedStackId: nested.stackId }, + }); + + // THEN + expect(parent).toHaveResource('Parent::Resource', { + NestedStackId: { Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD' }, + }); }); - // parent stack should have two sets of parameters. one for the first nested stack and the second - // for the second nested stack, passed in as parameters to the first - const template = SynthUtils.toCloudFormation(parent); - expect(template.Parameters).toEqual({ - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6: { Type: 'String', Description: 'S3 bucket for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA: { Type: 'String', Description: 'S3 key for asset version "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cArtifactHash7DC546E0: { Type: 'String', Description: 'Artifact hash for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - [`AssetParameters${middleStackHash}S3Bucket${bucketSuffix}`]: { Type: 'String', Description: `S3 bucket for asset "${middleStackHash}"` }, - [`AssetParameters${middleStackHash}S3VersionKey${versionSuffix}`]: { Type: 'String', Description: `S3 key for asset version "${middleStackHash}"` }, - [`AssetParameters${middleStackHash}ArtifactHash${hashSuffix}`]: { Type: 'String', Description: `Artifact hash for asset "${middleStackHash}"` }, + test('stackName returns AWS::StackName when referenced from the context of the nested stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { MyStackName: nested.stackName }, + }); + + // THEN + expect(nested).toHaveResource('Nested::Resource', { + MyStackName: { Ref: 'AWS::StackName' }, + }); }); - // proxy asset params to nested stack - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6' }, - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA' }, - }, + test('stackName returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(parent, 'ParentResource', { + type: 'Parent::Resource', + properties: { NestedStackName: nested.stackName }, + }); + + // THEN + expect(parent).toHaveResource('Parent::Resource', { + NestedStackName: { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + { + Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD', + }, + ], + }, + ], + }, + }); }); - // parent stack should have 2 assets - expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); -}); + test('"account", "region" and "environment" are all derived from the parent', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); -test('reference resource in a double nested stack (#15155)', () => { - // GIVEN - const app = new App(); - const producerStack = new Stack(app, 'Producer'); - const nested2 = new NestedStack(new NestedStack(producerStack, 'Nested1'), 'Nested2'); - const producerResource = new CfnResource(nested2, 'Resource', { type: 'MyResource' }); - const consumerStack = new Stack(app, 'Consumer'); - - // WHEN - new CfnResource(consumerStack, 'ConsumingResource', { - type: 'YourResource', - properties: { RefToResource: producerResource.ref }, - }); + // WHEN + const nested = new NestedStack(parent, 'NestedStack'); - // THEN - const casm = app.synth(); // before #15155 was fixed this threw an error + // THEN + expect(nested.environment).toEqual(parent.environment); + expect(nested.account).toEqual(parent.account); + expect(nested.region).toEqual(parent.region); + }); - const producerTemplate = casm.getStackArtifact(producerStack.artifactId).template; - const consumerTemplate = casm.getStackArtifact(consumerStack.artifactId).template; + test('double-nested stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'stack'); - // check that the consuming resource references the expected export name - const outputName = 'ExportsOutputFnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsProducerNested1Nested2NestedStackNested2NestedStackResource1E6FA3C3OutputsProducerNested1Nested238A89CC5Ref2E9E52EA'; - const exportName = producerTemplate.Outputs[outputName].Export.Name; - const importName = consumerTemplate.Resources.ConsumingResource.Properties.RefToResource['Fn::ImportValue']; - expect(exportName).toEqual(importName); -}); + // WHEN + const nested1 = new NestedStack(parent, 'Nested1'); + const nested2 = new NestedStack(nested1, 'Nested2'); -test('assets within nested stacks are proxied from the parent', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'ParentStack'); - const nested = new NestedStack(parent, 'NestedStack'); + new CfnResource(nested1, 'Resource1', { type: 'Resource::1' }); + new CfnResource(nested2, 'Resource2', { type: 'Resource::2' }); - // WHEN - const asset = new s3_assets.Asset(nested, 'asset', { - path: path.join(__dirname, 'asset-fixture.txt'), - }); + // THEN + const assembly = app.synth(); - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { - AssetBucket: asset.s3BucketName, - AssetKey: asset.s3ObjectKey, - }, - }); + // nested2 is a "leaf", so it's just the resource + expect(nested2).toMatchTemplate({ + Resources: { + Resource2: { Type: 'Resource::2' }, + }, + }); + + const middleStackHash = '7c426f7299a739900279ac1ece040397c1913cdf786f5228677b289f4d5e4c48'; + const bucketSuffix = 'C706B101'; + const versionSuffix = '4B193AA5'; + const hashSuffix = 'E28F0693'; + + // nested1 wires the nested2 template through parameters, so we expect those + expect(nested1).toHaveResource('Resource::1'); + const nested2Template = SynthUtils.toCloudFormation(nested1); + expect(nested2Template.Parameters).toEqual({ + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Type: 'String' }, + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Type: 'String' }, + }); + + // parent stack should have two sets of parameters. one for the first nested stack and the second + // for the second nested stack, passed in as parameters to the first + const template = SynthUtils.toCloudFormation(parent); + expect(template.Parameters).toEqual({ + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6: { Type: 'String', Description: 'S3 bucket for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA: { Type: 'String', Description: 'S3 key for asset version "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cArtifactHash7DC546E0: { Type: 'String', Description: 'Artifact hash for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + [`AssetParameters${middleStackHash}S3Bucket${bucketSuffix}`]: { Type: 'String', Description: `S3 bucket for asset "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}S3VersionKey${versionSuffix}`]: { Type: 'String', Description: `S3 key for asset version "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}ArtifactHash${hashSuffix}`]: { Type: 'String', Description: `Artifact hash for asset "${middleStackHash}"` }, + }); + + // proxy asset params to nested stack + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6' }, + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA' }, + }, + }); - // THEN - const assembly = app.synth(); - const template = SynthUtils.toCloudFormation(parent); - - // two sets of asset parameters: one for the nested stack itself and one as a proxy for the asset within the stack - expect(template.Parameters).toEqual({ - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637: { Type: 'String', Description: 'S3 bucket for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2: { Type: 'String', Description: 'S3 key for asset version "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281ArtifactHash373B14D2: { Type: 'String', Description: 'Artifact hash for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3Bucket3C4265E9: { Type: 'String', Description: 'S3 bucket for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3VersionKey8E981535: { Type: 'String', Description: 'S3 key for asset version "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71ArtifactHash45A28583: { Type: 'String', Description: 'Artifact hash for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + // parent stack should have 2 assets + expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); }); - // asset proxy parameters are passed to the nested stack - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3Bucket82C55B96Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637' }, - referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyA43C3CC6Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2' }, - }, + test('reference resource in a double nested stack (#15155)', () => { + // GIVEN + const app = new App(); + const producerStack = new Stack(app, 'Producer'); + const nested2 = new NestedStack(new NestedStack(producerStack, 'Nested1'), 'Nested2'); + const producerResource = new CfnResource(nested2, 'Resource', { type: 'MyResource' }); + const consumerStack = new Stack(app, 'Consumer'); + + // WHEN + new CfnResource(consumerStack, 'ConsumingResource', { + type: 'YourResource', + properties: { RefToResource: producerResource.ref }, + }); + + // THEN + const casm = app.synth(); // before #15155 was fixed this threw an error + + const producerTemplate = casm.getStackArtifact(producerStack.artifactId).template; + const consumerTemplate = casm.getStackArtifact(consumerStack.artifactId).template; + + // check that the consuming resource references the expected export name + const outputName = 'ExportsOutputFnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsProducerNested1Nested2NestedStackNested2NestedStackResource1E6FA3C3OutputsProducerNested1Nested238A89CC5Ref2E9E52EA'; + const exportName = producerTemplate.Outputs[outputName].Export.Name; + const importName = consumerTemplate.Resources.ConsumingResource.Properties.RefToResource['Fn::ImportValue']; + expect(exportName).toEqual(importName); }); - // parent stack should have 2 assets - expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); -}); - -test('docker image assets are wired through the top-level stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'my-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - - // WHEN - const location = nested.synthesizer.addDockerImageAsset({ - directoryName: 'my-image', - dockerBuildArgs: { key: 'value', boom: 'bam' }, - dockerBuildTarget: 'buildTarget', - sourceHash: 'hash-of-source', - }); + test('assets within nested stacks are proxied from the parent', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'ParentStack'); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + const asset = new s3_assets.Asset(nested, 'asset', { + path: path.join(__dirname, 'asset-fixture.txt'), + }); + + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { + AssetBucket: asset.s3BucketName, + AssetKey: asset.s3ObjectKey, + }, + }); + + // THEN + const assembly = app.synth(); + const template = SynthUtils.toCloudFormation(parent); + + // two sets of asset parameters: one for the nested stack itself and one as a proxy for the asset within the stack + expect(template.Parameters).toEqual({ + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637: { Type: 'String', Description: 'S3 bucket for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2: { Type: 'String', Description: 'S3 key for asset version "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281ArtifactHash373B14D2: { Type: 'String', Description: 'Artifact hash for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3Bucket3C4265E9: { Type: 'String', Description: 'S3 bucket for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3VersionKey8E981535: { Type: 'String', Description: 'S3 key for asset version "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71ArtifactHash45A28583: { Type: 'String', Description: 'Artifact hash for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + }); + + // asset proxy parameters are passed to the nested stack + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3Bucket82C55B96Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637' }, + referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyA43C3CC6Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2' }, + }, + }); - // use the asset, so the parameters will be wired. - new sns.Topic(nested, 'MyTopic', { - displayName: `image location is ${location.imageUri}`, + // parent stack should have 2 assets + expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); }); - // THEN - const asm = app.synth(); - expect(asm.getStackArtifact(parent.artifactId).assets).toEqual([ - { - repositoryName: 'aws-cdk/assets', - imageTag: 'hash-of-source', - id: 'hash-of-source', - packaging: 'container-image', - path: 'my-image', + test('docker image assets are wired through the top-level stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'my-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + + // WHEN + const location = nested.synthesizer.addDockerImageAsset({ + directoryName: 'my-image', + dockerBuildArgs: { key: 'value', boom: 'bam' }, + dockerBuildTarget: 'buildTarget', sourceHash: 'hash-of-source', - buildArgs: { key: 'value', boom: 'bam' }, - target: 'buildTarget', - }, - { - path: 'mystacknestedstackFAE12FB5.nested.template.json', - id: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', - packaging: 'file', - sourceHash: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', - s3BucketParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3Bucket67A749F8', - s3KeyParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3VersionKeyE1E6A8D4', - artifactHashParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5ArtifactHash0AEDBE8A', - }, - ]); -}); + }); -test('metadata defined in nested stacks is reported at the parent stack level in the cloud assembly', () => { - // GIVEN - const app = new App({ stackTraces: false }); - const parent = new Stack(app, 'parent'); - const child = new Stack(parent, 'child'); - const nested = new NestedStack(child, 'nested'); - const resource = new CfnResource(nested, 'resource', { type: 'foo' }); - - // WHEN - resource.node.addMetadata('foo', 'bar'); - - // THEN: the first non-nested stack records the assembly metadata - const asm = app.synth(); - expect(asm.stacks.length).toEqual(2); // only one stack is defined as an artifact - expect(asm.getStackByName(parent.stackName).findMetadataByType('foo')).toEqual([]); - expect(asm.getStackByName(child.stackName).findMetadataByType('foo')).toEqual([ - { - path: '/parent/child/nested/resource', - type: 'foo', - data: 'bar', - }, - ]); -}); + // use the asset, so the parameters will be wired. + new sns.Topic(nested, 'MyTopic', { + displayName: `image location is ${location.imageUri}`, + }); -test('referencing attributes with period across stacks', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'nested'); - const consumed = new CfnResource(nested, 'resource-in-nested', { type: 'CONSUMED' }); - - // WHEN - new CfnResource(parent, 'resource-in-parent', { - type: 'CONSUMER', - properties: { - ConsumedAttribute: consumed.getAtt('Consumed.Attribute'), - }, - }); - - // THEN - expect(nested).toMatchTemplate({ - Resources: { - resourceinnested: { - Type: 'CONSUMED', + // THEN + const asm = app.synth(); + expect(asm.getStackArtifact(parent.artifactId).assets).toEqual([ + { + repositoryName: 'aws-cdk/assets', + imageTag: 'hash-of-source', + id: 'hash-of-source', + packaging: 'container-image', + path: 'my-image', + sourceHash: 'hash-of-source', + buildArgs: { key: 'value', boom: 'bam' }, + target: 'buildTarget', }, - }, - Outputs: { - nestedresourceinnested59B1F01CConsumedAttribute: { - Value: { - 'Fn::GetAtt': [ - 'resourceinnested', - 'Consumed.Attribute', - ], - }, + { + path: 'mystacknestedstackFAE12FB5.nested.template.json', + id: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + packaging: 'file', + sourceHash: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + s3BucketParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3Bucket67A749F8', + s3KeyParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3VersionKeyE1E6A8D4', + artifactHashParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5ArtifactHash0AEDBE8A', }, - }, + ]); }); - expect(parent).toHaveResource('CONSUMER', { - ConsumedAttribute: { - 'Fn::GetAtt': [ - 'nestedNestedStacknestedNestedStackResource3DD143BF', - 'Outputs.nestedresourceinnested59B1F01CConsumedAttribute', - ], - }, - }); -}); - -test('missing context in nested stack is reported if the context is not available', () => { - // GIVEN - const app = new App(); - const stack = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); - const nestedStack = new NestedStack(stack, 'nested'); - const provider = 'availability-zones'; - const expectedKey = ContextProvider.getKey(nestedStack, { - provider, - }).key; - - // WHEN - ContextProvider.getValue(nestedStack, { - provider, - dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], + test('metadata defined in nested stacks is reported at the parent stack level in the cloud assembly', () => { + // GIVEN + const app = new App({ stackTraces: false }); + const parent = new Stack(app, 'parent'); + const child = new Stack(parent, 'child'); + const nested = new NestedStack(child, 'nested'); + const resource = new CfnResource(nested, 'resource', { type: 'foo' }); + + // WHEN + resource.node.addMetadata('foo', 'bar'); + + // THEN: the first non-nested stack records the assembly metadata + const asm = app.synth(); + expect(asm.stacks.length).toEqual(2); // only one stack is defined as an artifact + expect(asm.getStackByName(parent.stackName).findMetadataByType('foo')).toEqual([]); + expect(asm.getStackByName(child.stackName).findMetadataByType('foo')).toEqual([ + { + path: '/parent/child/nested/resource', + type: 'foo', + data: 'bar', + }, + ]); }); - // THEN: missing context is reported in the cloud assembly - const asm = app.synth(); - const missing = asm.manifest.missing; - - expect(missing && missing.find(m => { - return (m.key === expectedKey); - })).toBeTruthy(); -}); - -test('3-level stacks: legacy synthesizer parameters are added to the middle-level stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'stack', { - synthesizer: new LegacyStackSynthesizer(), - }); - const middle = new NestedStack(top, 'nested1'); - const bottom = new NestedStack(middle, 'nested2'); + test('referencing attributes with period across stacks', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'nested'); + const consumed = new CfnResource(nested, 'resource-in-nested', { type: 'CONSUMED' }); + + // WHEN + new CfnResource(parent, 'resource-in-parent', { + type: 'CONSUMER', + properties: { + ConsumedAttribute: consumed.getAtt('Consumed.Attribute'), + }, + }); - // WHEN - new CfnResource(bottom, 'Something', { - type: 'BottomLevel', - }); + // THEN + expect(nested).toMatchTemplate({ + Resources: { + resourceinnested: { + Type: 'CONSUMED', + }, + }, + Outputs: { + nestedresourceinnested59B1F01CConsumedAttribute: { + Value: { + 'Fn::GetAtt': [ + 'resourceinnested', + 'Consumed.Attribute', + ], + }, + }, + }, + }); - // THEN - const asm = app.synth(); - const middleTemplate = JSON.parse(fs.readFileSync(path.join(asm.directory, middle.templateFile), { encoding: 'utf-8' })); - - const hash = 'bc3c51e4d3545ee0a0069401e5a32c37b66d044b983f12de416ba1576ecaf0a4'; - expect(middleTemplate.Parameters ?? {}).toEqual({ - [`referencetostackAssetParameters${hash}S3BucketD7C30435Ref`]: { - Type: 'String', - }, - [`referencetostackAssetParameters${hash}S3VersionKeyB667DBE1Ref`]: { - Type: 'String', - }, + expect(parent).toHaveResource('CONSUMER', { + ConsumedAttribute: { + 'Fn::GetAtt': [ + 'nestedNestedStacknestedNestedStackResource3DD143BF', + 'Outputs.nestedresourceinnested59B1F01CConsumedAttribute', + ], + }, + }); }); -}); -test('references to a resource from a deeply nested stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'stack'); - const topLevel = new CfnResource(top, 'toplevel', { type: 'TopLevel' }); - const nested1 = new NestedStack(top, 'nested1'); - const nested2 = new NestedStack(nested1, 'nested2'); - - // WHEN - new CfnResource(nested2, 'refToTopLevel', { - type: 'BottomLevel', - properties: { RefToTopLevel: topLevel.ref }, + test('missing context in nested stack is reported if the context is not available', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); + const nestedStack = new NestedStack(stack, 'nested'); + const provider = 'availability-zones'; + const expectedKey = ContextProvider.getKey(nestedStack, { + provider, + }).key; + + // WHEN + ContextProvider.getValue(nestedStack, { + provider, + dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // THEN: missing context is reported in the cloud assembly + const asm = app.synth(); + const missing = asm.manifest.missing; + + expect(missing && missing.find(m => { + return (m.key === expectedKey); + })).toBeTruthy(); }); - // THEN - expect(top).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3Bucket5DA5D2E7Ref: { - Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3BucketDD4D96B5', - }, - referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey8FBE5C12Ref: { - Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey83E381F3', + test('3-level stacks: legacy synthesizer parameters are added to the middle-level stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'stack', { + synthesizer: new LegacyStackSynthesizer(), + }); + const middle = new NestedStack(top, 'nested1'); + const bottom = new NestedStack(middle, 'nested2'); + + // WHEN + new CfnResource(bottom, 'Something', { + type: 'BottomLevel', + }); + + // THEN + const asm = app.synth(); + const middleTemplate = JSON.parse(fs.readFileSync(path.join(asm.directory, middle.templateFile), { encoding: 'utf-8' })); + + const hash = 'bc3c51e4d3545ee0a0069401e5a32c37b66d044b983f12de416ba1576ecaf0a4'; + expect(middleTemplate.Parameters ?? {}).toEqual({ + [`referencetostackAssetParameters${hash}S3BucketD7C30435Ref`]: { + Type: 'String', }, - referencetostacktoplevelBB16BF13Ref: { - Ref: 'toplevel', + [`referencetostackAssetParameters${hash}S3VersionKeyB667DBE1Ref`]: { + Type: 'String', }, - }, + }); }); - expect(nested1).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostacktoplevelBB16BF13Ref: { - Ref: 'referencetostacktoplevelBB16BF13Ref', + test('references to a resource from a deeply nested stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'stack'); + const topLevel = new CfnResource(top, 'toplevel', { type: 'TopLevel' }); + const nested1 = new NestedStack(top, 'nested1'); + const nested2 = new NestedStack(nested1, 'nested2'); + + // WHEN + new CfnResource(nested2, 'refToTopLevel', { + type: 'BottomLevel', + properties: { RefToTopLevel: topLevel.ref }, + }); + + // THEN + expect(top).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3Bucket5DA5D2E7Ref: { + Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3BucketDD4D96B5', + }, + referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey8FBE5C12Ref: { + Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey83E381F3', + }, + referencetostacktoplevelBB16BF13Ref: { + Ref: 'toplevel', + }, }, - }, - }); + }); - expect(nested2).toMatchTemplate({ - Resources: { - refToTopLevel: { - Type: 'BottomLevel', - Properties: { - RefToTopLevel: { - Ref: 'referencetostacktoplevelBB16BF13Ref', + expect(nested1).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostacktoplevelBB16BF13Ref: { + Ref: 'referencetostacktoplevelBB16BF13Ref', + }, + }, + }); + + expect(nested2).toMatchTemplate({ + Resources: { + refToTopLevel: { + Type: 'BottomLevel', + Properties: { + RefToTopLevel: { + Ref: 'referencetostacktoplevelBB16BF13Ref', + }, }, }, }, - }, - Parameters: { - referencetostacktoplevelBB16BF13Ref: { - Type: 'String', + Parameters: { + referencetostacktoplevelBB16BF13Ref: { + Type: 'String', + }, }, - }, + }); }); -}); -test('bottom nested stack consumes value from a top-level stack through a parameter in a middle nested stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'Grandparent'); - const middle = new NestedStack(top, 'Parent'); - const bottom = new NestedStack(middle, 'Child'); - const resourceInGrandparent = new CfnResource(top, 'ResourceInGrandparent', { type: 'ResourceInGrandparent' }); - - // WHEN - new CfnResource(bottom, 'ResourceInChild', { - type: 'ResourceInChild', - properties: { - RefToGrandparent: resourceInGrandparent.ref, - }, - }); + test('bottom nested stack consumes value from a top-level stack through a parameter in a middle nested stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'Grandparent'); + const middle = new NestedStack(top, 'Parent'); + const bottom = new NestedStack(middle, 'Child'); + const resourceInGrandparent = new CfnResource(top, 'ResourceInGrandparent', { type: 'ResourceInGrandparent' }); + + // WHEN + new CfnResource(bottom, 'ResourceInChild', { + type: 'ResourceInChild', + properties: { + RefToGrandparent: resourceInGrandparent.ref, + }, + }); - // THEN + // THEN - // this is the name allocated for the parameter that's propagated through - // the hierarchy. - const paramName = 'referencetoGrandparentResourceInGrandparent010E997ARef'; + // this is the name allocated for the parameter that's propagated through + // the hierarchy. + const paramName = 'referencetoGrandparentResourceInGrandparent010E997ARef'; - // child (bottom) references through a parameter. - expect(bottom).toMatchTemplate({ - Resources: { - ResourceInChild: { - Type: 'ResourceInChild', - Properties: { - RefToGrandparent: { Ref: paramName }, + // child (bottom) references through a parameter. + expect(bottom).toMatchTemplate({ + Resources: { + ResourceInChild: { + Type: 'ResourceInChild', + Properties: { + RefToGrandparent: { Ref: paramName }, + }, }, }, - }, - Parameters: { - [paramName]: { Type: 'String' }, - }, - }); + Parameters: { + [paramName]: { Type: 'String' }, + }, + }); - // the parent (middle) sets the value of this parameter to be a reference to another parameter - expect(middle).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - [paramName]: { Ref: paramName }, - }, - }); + // the parent (middle) sets the value of this parameter to be a reference to another parameter + expect(middle).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + [paramName]: { Ref: paramName }, + }, + }); - // grandparent (top) assigns the actual value to the parameter - expect(top).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - [paramName]: { Ref: 'ResourceInGrandparent' }, + // grandparent (top) assigns the actual value to the parameter + expect(top).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + [paramName]: { Ref: 'ResourceInGrandparent' }, - // these are for the asset of the bottom nested stack - referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket06EEE58DRef: { - Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket01877C2E', - }, - referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKeyD3B04909Ref: { - Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKey5765F084', + // these are for the asset of the bottom nested stack + referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket06EEE58DRef: { + Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket01877C2E', + }, + referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKeyD3B04909Ref: { + Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKey5765F084', + }, }, - }, + }); }); + }); diff --git a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts index a3bd1944625ae..e241171e43df5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts @@ -2,6 +2,7 @@ import { ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { CustomResource, CustomResourceProvider } from '../lib'; @@ -12,7 +13,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ /* eslint-disable quote-props */ -describe('custom resources honor removalPolicy', () => { +describeDeprecated('custom resources honor removalPolicy', () => { test('unspecified (aka .Destroy)', () => { // GIVEN const app = new cdk.App(); @@ -55,7 +56,7 @@ describe('custom resources honor removalPolicy', () => { }); }); -test('custom resource is added twice, lambda is added once', () => { +testDeprecated('custom resource is added twice, lambda is added once', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); @@ -141,7 +142,7 @@ test('custom resource is added twice, lambda is added once', () => { }); }); -test('custom resources can specify a resource type that starts with Custom::', () => { +testDeprecated('custom resources can specify a resource type that starts with Custom::', () => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); new CustomResource(stack, 'MyCustomResource', { @@ -151,7 +152,7 @@ test('custom resources can specify a resource type that starts with Custom::', ( expect(stack).toHaveResource('Custom::MyCustomResourceType'); }); -describe('fails if custom resource type is invalid', () => { +describeDeprecated('fails if custom resource type is invalid', () => { test('does not start with "Custom::"', () => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); @@ -189,7 +190,7 @@ describe('fails if custom resource type is invalid', () => { }); }); -test('.ref returns the intrinsic reference (physical name)', () => { +testDeprecated('.ref returns the intrinsic reference (physical name)', () => { // GIVEN const stack = new cdk.Stack(); const res = new TestCustomResource(stack, 'myResource'); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 9c4476de80fbb..c46b318a4d610 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -75,7 +75,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 10c42d68efcfd..e10f5c39d596b 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -39,10 +39,7 @@ An S3 bucket can be added as an origin. If the bucket is configured as a website documents. ```ts -import * as cloudfront from '@aws-cdk/aws-cloudfront'; -import * as origins from '@aws-cdk/aws-cloudfront-origins'; - -// Creates a distribution for a S3 bucket. +// Creates a distribution from an S3 bucket. const myBucket = new s3.Bucket(this, 'myBucket'); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, @@ -61,15 +58,13 @@ An Elastic Load Balancing (ELB) v2 load balancer may be used as an origin. In or accessible (`internetFacing` is true). Both Application and Network load balancers are supported. ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; - -const vpc = new ec2.Vpc(...); +// Creates a distribution from an ELBv2 load balancer +declare const vpc: ec2.Vpc; // Create an application load balancer in a VPC. 'internetFacing' must be 'true' // for CloudFront to access the load balancer and use it as an origin. const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, - internetFacing: true + internetFacing: true, }); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.LoadBalancerV2Origin(lb) }, @@ -81,6 +76,7 @@ new cloudfront.Distribution(this, 'myDist', { Origins can also be created from any other HTTP endpoint, given the domain name, and optionally, other origin properties. ```ts +// Creates a distribution from an HTTP endpoint new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, }); @@ -98,10 +94,17 @@ may either be created by ACM, or created elsewhere and imported into ACM. When a from SNI only and a minimum protocol version of TLSv1.2_2021 if the '@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021' feature flag is set, and TLSv1.2_2019 otherwise. ```ts +// To use your own domain name in a Distribution, you must associate a certificate +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as route53 from '@aws-cdk/aws-route53'; + +declare const hostedZone: route53.HostedZone; const myCertificate = new acm.DnsValidatedCertificate(this, 'mySiteCert', { domainName: 'www.example.com', hostedZone, }); + +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, domainNames: ['www.example.com'], @@ -112,10 +115,12 @@ new cloudfront.Distribution(this, 'myDist', { However, you can customize the minimum protocol version for the certificate while creating the distribution using `minimumProtocolVersion` property. ```ts +// Create a Distribution with a custom domain name and a minimum protocol version. +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, domainNames: ['www.example.com'], - minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2016 + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2016, }); ``` @@ -129,12 +134,14 @@ The properties of the default behavior can be adjusted as part of the distributi methods and viewer protocol policy of the cache. ```ts +// Create a Distribution with configured HTTP methods and viewer protocol policy of the cache. +declare const myBucket: s3.Bucket; const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket), - allowedMethods: AllowedMethods.ALLOW_ALL, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - } + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + }, }); ``` @@ -143,25 +150,30 @@ and enable customization for a specific set of resources based on a URL path pat override the default viewer protocol policy for all of the images. ```ts +// Add a behavior to a Distribution after initial creation. +declare const myBucket: s3.Bucket; +declare const myWebDistribution: cloudfront.Distribution; myWebDistribution.addBehavior('/images/*.jpg', new origins.S3Origin(myBucket), { - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }); ``` These behaviors can also be specified at distribution creation time. ```ts +// Create a Distribution with additional behaviors at creation time. +declare const myBucket: s3.Bucket; const bucketOrigin = new origins.S3Origin(myBucket); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: bucketOrigin, - allowedMethods: AllowedMethods.ALLOW_ALL, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }, additionalBehaviors: { '/images/*.jpg': { origin: bucketOrigin, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }, }, }); @@ -176,15 +188,19 @@ 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 +// Using an existing cache policy for a Distribution +declare const bucketOrigin: origins.S3Origin; new cloudfront.Distribution(this, 'myDistManagedPolicy', { defaultBehavior: { origin: bucketOrigin, cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, }, }); +``` -// Creating a custom cache policy -- all parameters optional +```ts +// Creating a custom cache policy for a Distribution -- all parameters optional +declare const bucketOrigin: origins.S3Origin; const myCachePolicy = new cloudfront.CachePolicy(this, 'myCachePolicy', { cachePolicyName: 'MyPolicy', comment: 'A default policy', @@ -215,25 +231,30 @@ 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 +// Using an existing origin request policy for a Distribution +declare const bucketOrigin: origins.S3Origin; 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', { +``` + +```ts +// Creating a custom origin request policy for a Distribution -- all parameters optional +declare const bucketOrigin: origins.S3Origin; +const myOriginRequestPolicy = new cloudfront.OriginRequestPolicy(this, '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, }, }); @@ -241,23 +262,26 @@ new cloudfront.Distribution(this, 'myDistCustomPolicy', { ### Validating signed URLs or signed cookies with Trusted Key Groups -CloudFront Distribution now supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. - -Example: +CloudFront Distribution supports validating signed URLs or signed cookies using key groups. +When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed +cookies for all requests that match the cache behavior. ```ts +// Validating signed URLs or signed cookies with Trusted Key Groups + // public key in PEM format -const pubKey = new PublicKey(stack, 'MyPubKey', { +declare const publicKey: string; +const pubKey = new cloudfront.PublicKey(this, 'MyPubKey', { encodedKey: publicKey, }); -const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { +const keyGroup = new cloudfront.KeyGroup(this, 'MyKeyGroup', { items: [ pubKey, ], }); -new cloudfront.Distribution(stack, 'Dist', { +new cloudfront.Distribution(this, 'Dist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com'), trustedKeyGroups: [ @@ -269,23 +293,27 @@ new cloudfront.Distribution(stack, 'Dist', { ### 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. -You can author Node.js or Python functions in the US East (N. Virginia) region, -and then execute them in AWS locations globally that are closer to the viewer, -without provisioning or managing servers. -Lambda@Edge functions are associated with a specific behavior and event type. -Lambda@Edge can be used to rewrite URLs, -alter responses based on headers or cookies, -or authorize requests based on headers or authorization tokens. +Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute +functions that customize the content that CloudFront delivers. You can author Node.js +or Python functions in the US East (N. Virginia) region, and then execute them in AWS +locations globally that are closer to the viewer, without provisioning or managing servers. +Lambda@Edge functions are associated with a specific behavior and event type. Lambda@Edge +can be used to rewrite URLs, alter responses based on headers or cookies, or authorize +requests based on headers or authorization tokens. -The following shows a Lambda@Edge function added to the default behavior and triggered on every request: +The following shows a Lambda@Edge function added to the default behavior and triggered +on every request: ```ts +// A Lambda@Edge function added to default behavior of a Distribution +// and triggered on every request const myFunc = new cloudfront.experimental.EdgeFunction(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); + +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket), @@ -309,6 +337,7 @@ new cloudfront.Distribution(this, 'myDist', { If the stack is in `us-east-1`, a "normal" `lambda.Function` can be used instead of an `EdgeFunction`. ```ts +// Using a lambda Function instead of an EdgeFunction for stacks in `us-east-`. const myFunc = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', @@ -320,18 +349,20 @@ If the stack is not in `us-east-1`, and you need references from different appli you can also set a specific stack ID for each Lambda@Edge. ```ts +// Setting stackIds for EdgeFunctions that can be referenced from different applications +// on the same account. const myFunc1 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction1', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler1')), - stackId: 'edge-lambda-stack-id-1' + stackId: 'edge-lambda-stack-id-1', }); const myFunc2 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction2', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler2')), - stackId: 'edge-lambda-stack-id-2' + stackId: 'edge-lambda-stack-id-2', }); ``` @@ -339,9 +370,13 @@ Lambda@Edge functions can also be associated with additional behaviors, either at or after Distribution creation time. ```ts +// Associating a Lambda@Edge function with additional behaviors. + +declare const myFunc: cloudfront.experimental.EdgeFunction; // assigning at Distribution creation +declare const myBucket: s3.Bucket; const myOrigin = new origins.S3Origin(myBucket); -new cloudfront.Distribution(this, 'myDist', { +const myDistribution = new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: myOrigin }, additionalBehaviors: { 'images/*': { @@ -371,16 +406,19 @@ myDistribution.addBehavior('images/*', myOrigin, { Adding an existing Lambda@Edge function created in a different stack to a CloudFront distribution. ```ts +// Adding an existing Lambda@Edge function created in a different stack +// to a CloudFront distribution. +declare const s3Bucket: s3.Bucket; const functionVersion = lambda.Version.fromVersionArn(this, 'Version', 'arn:aws:lambda:us-east-1:123456789012:function:functionName:1'); new cloudfront.Distribution(this, 'distro', { defaultBehavior: { origin: new origins.S3Origin(s3Bucket), edgeLambdas: [ - { - functionVersion, - eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST - }, + { + functionVersion, + eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST, + }, ], }, }); @@ -391,11 +429,13 @@ new cloudfront.Distribution(this, 'distro', { You can also deploy CloudFront functions and add them to a CloudFront distribution. ```ts -const cfFunction = new cloudfront.Function(stack, 'Function', { +// Add a cloudfront Function to a Distribution +const cfFunction = new cloudfront.Function(this, 'Function', { code: cloudfront.FunctionCode.fromInline('function handler(event) { return event.request }'), }); -new cloudfront.Distribution(stack, 'distro', { +declare const s3Bucket: s3.Bucket; +new cloudfront.Distribution(this, 'distro', { defaultBehavior: { origin: new origins.S3Origin(s3Bucket), functionAssociations: [{ @@ -416,6 +456,8 @@ You can configure CloudFront to create log files that contain detailed informati The logs can go to either an existing bucket, or a bucket will be created for you. ```ts +// Configure logging for Distributions + // Simplest form - creates a new bucket and logs to it. new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, @@ -438,7 +480,8 @@ Existing distributions can be imported as well; note that like most imported con However, it can be used as a reference for other higher-level constructs. ```ts -const distribution = cloudfront.Distribution.fromDistributionAttributes(scope, 'ImportedDist', { +// Using a reference to an imported Distribution +const distribution = cloudfront.Distribution.fromDistributionAttributes(this, 'ImportedDist', { domainName: 'd111111abcdef8.cloudfront.net', distributionId: '012345ABCDEF', }); @@ -452,23 +495,25 @@ const distribution = cloudfront.Distribution.fromDistributionAttributes(scope, ' Example usage: ```ts -const sourceBucket = new Bucket(this, 'Bucket'); +// Using a CloudFrontWebDistribution construct. -const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - originConfigs: [ - { - s3OriginSource: { - s3BucketSource: sourceBucket - }, - behaviors : [ {isDefaultBehavior: true}] - } - ] - }); +declare const sourceBucket: s3.Bucket; +const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors : [ {isDefaultBehavior: true}], + }, + ], +}); ``` ### Viewer certificate -By default, CloudFront Web Distributions will answer HTTPS requests with CloudFront's default certificate, only containing the distribution `domainName` (e.g. d111111abcdef8.cloudfront.net). +By default, CloudFront Web Distributions will answer HTTPS requests with CloudFront's default certificate, +only containing the distribution `domainName` (e.g. d111111abcdef8.cloudfront.net). You can customize the viewer certificate property to provide a custom certificate and/or list of domain name aliases to fit your needs. See [Using Alternate Domain Names and HTTPS](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https-alternate-domain-names.html) in the CloudFront User Guide. @@ -486,7 +531,10 @@ Example: You can change the default certificate by one stored AWS Certificate Manager, or ACM. Those certificate can either be generated by AWS, or purchased by another CA imported into ACM. -For more information, see [the aws-certificatemanager module documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-certificatemanager-readme.html) or [Importing Certificates into AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) in the AWS Certificate Manager User Guide. +For more information, see +[the aws-certificatemanager module documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-certificatemanager-readme.html) +or [Importing Certificates into AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) +in the AWS Certificate Manager User Guide. Example: @@ -504,22 +552,26 @@ Example: ### Trusted Key Groups -CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. +CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. +When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. Example: ```ts -const pubKey = new PublicKey(stack, 'MyPubKey', { +// Using trusted key groups for Cloudfront Web Distributions. +declare const sourceBucket: s3.Bucket; +declare const publicKey: string; +const pubKey = new cloudfront.PublicKey(this, 'MyPubKey', { encodedKey: publicKey, }); -const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { +const keyGroup = new cloudfront.KeyGroup(this, 'MyKeyGroup', { items: [ pubKey, ], }); -new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { +new cloudfront.CloudFrontWebDistribution(this, 'AnAmazingWebsiteProbably', { originConfigs: [ { s3OriginSource: { @@ -547,15 +599,25 @@ See [Restricting the Geographic Distribution of Your Content](https://docs.aws.a Example: ```ts -new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { - //... - geoRestriction: GeoRestriction.whitelist('US', 'UK') +// Adding restrictions to a Cloudfront Web Distribution. +declare const sourceBucket: s3.Bucket; +new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors : [ {isDefaultBehavior: true}], + }, + ], + geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'UK'), }); ``` ### Connection behaviors between CloudFront and your origin -CloudFront provides you even more control over the connection behaviors between CloudFront and your origin. You can now configure the number of connection attempts CloudFront will make to your origin and the origin connection timeout for each attempt. +CloudFront provides you even more control over the connection behaviors between CloudFront and your origin. +You can now configure the number of connection attempts CloudFront will make to your origin and the origin connection timeout for each attempt. See [Origin Connection Attempts](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#origin-connection-attempts) @@ -564,43 +626,49 @@ See [Origin Connection Timeout](https://docs.aws.amazon.com/AmazonCloudFront/lat Example usage: ```ts -const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - originConfigs: [ +// Configuring connection behaviors between Cloudfront and your origin +const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + connectionAttempts: 3, + connectionTimeout: Duration.seconds(10), + behaviors: [ { - ..., - connectionAttempts: 3, - connectionTimeout: cdk.Duration.seconds(10), - } - ] + isDefaultBehavior: true, + }, + ], + }, + ], }); ``` #### Origin Fallback In case the origin source is not available and answers with one of the -specified status code the failover origin source will be used. +specified status codes the failover origin source will be used. ```ts -new CloudFrontWebDistribution(stack, 'ADistribution', { +// Configuring origin fallback options for the CloudFrontWebDistribution +new cloudfront.CloudFrontWebDistribution(this, 'ADistribution', { originConfigs: [ { s3OriginSource: { - s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucket', 'myoriginbucket'), + s3BucketSource: s3.Bucket.fromBucketName(this, 'aBucket', 'myoriginbucket'), originPath: '/', originHeaders: { 'myHeader': '42', }, - originShieldRegion: 'us-west-2' + originShieldRegion: 'us-west-2', }, failoverS3OriginSource: { - s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucketFallback', 'myoriginbucketfallback'), + s3BucketSource: s3.Bucket.fromBucketName(this, 'aBucketFallback', 'myoriginbucketfallback'), originPath: '/somewhere', originHeaders: { 'myHeader2': '21', }, - originShieldRegion: 'us-east-1' + originShieldRegion: 'us-east-1', }, - failoverCriteriaStatusCodes: [FailoverStatusCode.INTERNAL_SERVER_ERROR], + failoverCriteriaStatusCodes: [cloudfront.FailoverStatusCode.INTERNAL_SERVER_ERROR], behaviors: [ { isDefaultBehavior: true, @@ -613,7 +681,8 @@ new CloudFrontWebDistribution(stack, 'ADistribution', { ## KeyGroup & PublicKey API -Now you can create a key group to use with CloudFront signed URLs and signed cookies. You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption. +You can create a key group to use with CloudFront signed URLs and signed cookies +You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption. The following example command uses OpenSSL to generate an RSA key pair with a length of 2048 bits and save to the file named `private_key.pem`. @@ -632,15 +701,16 @@ Note: Don't forget to copy/paste the contents of `public_key.pem` file including Example: ```ts - new cloudfront.KeyGroup(stack, 'MyKeyGroup', { - items: [ - new cloudfront.PublicKey(stack, 'MyPublicKey', { - encodedKey: '...', // contents of public_key.pem file - // comment: 'Key is expiring on ...', - }), - ], - // comment: 'Key group containing public keys ...', - }); +// Create a key group to use with CloudFront signed URLs and signed cookies. +new cloudfront.KeyGroup(this, 'MyKeyGroup', { + items: [ + new cloudfront.PublicKey(this, 'MyPublicKey', { + encodedKey: '...', // contents of public_key.pem file + // comment: 'Key is expiring on ...', + }), + ], + // comment: 'Key group containing public keys ...', +}); ``` See: diff --git a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts index d0dad353a8727..533ccbd5d78b9 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts @@ -85,9 +85,13 @@ export interface CachePolicyProps { * A Cache Policy configuration. * * @resource AWS::CloudFront::CachePolicy + * @link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html */ export class CachePolicy extends Resource implements ICachePolicy { - + /** + * This policy is designed for use with an origin that is an AWS Amplify web app. + */ + public static readonly AMPLIFY = CachePolicy.fromManagedCachePolicy('2e54312d-136d-493c-8eb9-b001f22f67d2'); /** * 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. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index b8926bad4c88f..517a73bcc8917 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,7 +1,7 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { IResource, Lazy, Resource, Stack, Token, Duration, Names, FeatureFlags } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Resource, Stack, Token, Duration, Names, FeatureFlags } from '@aws-cdk/core'; import { CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021 } from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { ICachePolicy } from './cache-policy'; @@ -260,7 +260,7 @@ export class Distribution extends Resource implements IDistribution { super(scope, id); if (props.certificate) { - const certificateRegion = Stack.of(this).parseArn(props.certificate.certificateArn).region; + const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region; if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { throw new Error(`Distribution certificates must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`); } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts index 4a3974c2af632..1fe9e745e8079 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -144,13 +144,13 @@ export class EdgeFunction extends Resource implements lambda.IVersion { /** Create a support stack and function in us-east-1, and a SSM reader in-region */ private createCrossRegionFunction(id: string, props: EdgeFunctionProps): FunctionConfig { - const parameterNamePrefix = '/cdk/EdgeFunctionArn'; + const parameterNamePrefix = 'cdk/EdgeFunctionArn'; if (Token.isUnresolved(this.env.region)) { throw new Error('stacks which use EdgeFunctions must have an explicitly set region'); } // SSM parameter names must only contain letters, numbers, ., _, -, or /. const sanitizedPath = this.node.path.replace(/[^\/\w.-]/g, '_'); - const parameterName = `${parameterNamePrefix}/${this.env.region}/${sanitizedPath}`; + const parameterName = `/${parameterNamePrefix}/${this.env.region}/${sanitizedPath}`; const functionStack = this.edgeStack(props.stackId); const edgeFunction = new lambda.Function(functionStack, id, props); @@ -177,7 +177,6 @@ export class EdgeFunction extends Resource implements lambda.IVersion { region: EdgeFunction.EDGE_REGION, resource: 'parameter', resourceName: parameterNamePrefix + '/*', - sep: '', }); const resourceType = 'Custom::CrossRegionStringParameterReader'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index ab2fbbd44b03c..c0a332a2e1b89 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -721,19 +721,17 @@ export interface CloudFrontWebDistributionAttributes { * Here's how you can use this construct: * * ```ts - * import { CloudFrontWebDistribution } from '@aws-cdk/aws-cloudfront' + * const sourceBucket = new s3.Bucket(this, 'Bucket'); * - * const sourceBucket = new Bucket(this, 'Bucket'); - * - * const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - * originConfigs: [ - * { - * s3OriginSource: { - * s3BucketSource: sourceBucket - * }, - * behaviors : [ {isDefaultBehavior: true}] - * } - * ] + * const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + * originConfigs: [ + * { + * s3OriginSource: { + * s3BucketSource: sourceBucket, + * }, + * behaviors : [ {isDefaultBehavior: true}], + * }, + * ], * }); * ``` * diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index e2b1305049227..b0fdf92f48da2 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,9 +84,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..1ac8c7fec5da3 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as origins from '@aws-cdk/aws-cloudfront-origins'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as path from 'path'; + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts index c06a78d16e714..7f90fff25740e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts @@ -19,7 +19,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { behaviors: [{ isDefaultBehavior: true }], }, ], - geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'UK'), + geoRestriction: cloudfront.GeoRestriction.allowlist('US', 'UK'), }); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts index e44ffa5ba138c..4a8d60efd5219 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts @@ -1,4 +1,3 @@ - import * as cdk from '@aws-cdk/core'; import * as cloudfront from '../lib'; diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json index f02507fb5fa05..bc5f2284b77ca 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json @@ -12,7 +12,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda", - "RefreshToken": "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6" + "RefreshToken": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -138,7 +138,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda2", - "RefreshToken": "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9" + "RefreshToken": "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -257,13 +257,13 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "LambdaServiceRoleA8ED4D3B" ] }, - "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6": { + "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -276,7 +276,7 @@ "Properties": { "Type": "String", "Value": { - "Ref": "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6" + "Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" }, "Name": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda" } @@ -289,7 +289,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6", + "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d", "Version" ] }, @@ -351,13 +351,13 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "Lambda2ServiceRole31A072E1" ] }, - "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9": { + "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -370,7 +370,7 @@ "Properties": { "Type": "String", "Value": { - "Ref": "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9" + "Ref": "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491" }, "Name": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda2" } @@ -383,7 +383,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9", + "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491", "Version" ] }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts index b1aed3d79da21..64c27f6535594 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts @@ -12,13 +12,13 @@ const stack = new cdk.Stack(app, 'integ-distribution-lambda-cross-region', { env const lambdaFunction = new cloudfront.experimental.EdgeFunction(stack, 'Lambda', { code: lambda.Code.fromInline('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, }); const lambdaFunction2 = new cloudfront.experimental.EdgeFunction(stack, 'Lambda2', { code: lambda.Code.fromInline('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, stackId: `edge-lambda-stack-${region}-2`, }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts index 600750bc4deca..d7612633cf93c 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -3,6 +3,7 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { CfnDistribution, @@ -1008,7 +1009,7 @@ added the ellipsis so a user would know there was more to ...`, }); - test('allows multiple aliasConfiguration CloudFrontWebDistribution per stack', () => { + testDeprecated('allows multiple aliasConfiguration CloudFrontWebDistribution per stack', () => { const stack = new cdk.Stack(); const s3BucketSource = new s3.Bucket(stack, 'Bucket'); @@ -1248,7 +1249,7 @@ added the ellipsis so a user would know there was more to ...`, }); }); describe('errors', () => { - test('throws if both deprecated aliasConfiguration and viewerCertificate', () => { + testDeprecated('throws if both deprecated aliasConfiguration and viewerCertificate', () => { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 245c60600c08e..12cf079c5279e 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -334,6 +334,7 @@ export class Trail extends Resource { values: dataResourceValues, }], includeManagementEvents: options.includeManagementEvents, + excludeManagementEventSources: options.excludeManagementEventSources, readWriteType: options.readWriteType, }); } @@ -424,6 +425,28 @@ export interface AddEventSelectorOptions { * @default true */ readonly includeManagementEvents?: boolean; + + /** + * An optional list of service event sources from which you do not want management events to be logged on your trail. + * + * @default [] + */ + readonly excludeManagementEventSources?: ManagementEventSources[]; +} + +/** + * Types of management event sources that can be excluded + */ +export enum ManagementEventSources { + /** + * AWS Key Management Service (AWS KMS) events + */ + KMS = 'kms.amazonaws.com', + + /** + * Data API events + */ + RDS_DATA_API = 'rdsdata.amazonaws.com', } /** @@ -457,6 +480,7 @@ export enum DataResourceType { interface EventSelector { readonly includeManagementEvents?: boolean; + readonly excludeManagementEventSources?: string[]; readonly readWriteType?: ReadWriteType; readonly dataResources?: EventSelectorData[]; } diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index c7d55ac99bf69..cae3b5dcf27cc 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -77,10 +77,10 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", "colors": "^1.4.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index 9e17345368785..a53a45b2c97ce 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -6,8 +6,9 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { LogGroup, RetentionDays } from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; -import { ReadWriteType, Trail } from '../lib'; +import { ManagementEventSources, ReadWriteType, Trail } from '../lib'; const ExpectedBucketPolicyProperties = { PolicyDocument: { @@ -131,13 +132,13 @@ describe('cloudtrail', () => { test('with imported s3 bucket', () => { // GIVEN const stack = getTestStack(); - const bucket = s3.Bucket.fromBucketName(stack, 'S3', 'SomeBucket'); + const bucket = s3.Bucket.fromBucketName(stack, 'S3', 'somebucket'); // WHEN new Trail(stack, 'Trail', { bucket }); expect(stack).toHaveResource('AWS::CloudTrail::Trail', { - S3BucketName: 'SomeBucket', + S3BucketName: 'somebucket', }); }); @@ -192,16 +193,11 @@ describe('cloudtrail', () => { }); new Trail(stack, 'KmsKeyTrail', { trailName: 'KmsKeyTrail', - kmsKey: key, + encryptionKey: key, }); new Trail(stack, 'UnencryptedTrail', { trailName: 'UnencryptedTrail', }); - expect(() => new Trail(stack, 'ErrorTrail', { - trailName: 'ErrorTrail', - encryptionKey: key, - kmsKey: key, - })).toThrow(/Both kmsKey and encryptionKey must not be specified/); expect(stack).toHaveResource('AWS::CloudTrail::Trail', { TrailName: 'EncryptionKeyTrail', @@ -221,6 +217,17 @@ describe('cloudtrail', () => { }); }); + testDeprecated('Both kmsKey and encryptionKey must not be specified', () => { + const stack = new Stack(); + const key = new kms.Key(stack, 'key'); + + expect(() => new Trail(stack, 'ErrorTrail', { + trailName: 'ErrorTrail', + encryptionKey: key, + kmsKey: key, + })).toThrow(/Both kmsKey and encryptionKey must not be specified/); + }); + describe('with cloud watch logs', () => { test('enabled', () => { const stack = getTestStack(); @@ -446,6 +453,59 @@ describe('cloudtrail', () => { }); }); + test('exclude management events', () => { + const stack = getTestStack(); + const bucket = new s3.Bucket(stack, 'testBucket', { bucketName: 'test-bucket' }); + const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); + cloudTrail.addS3EventSelector([{ bucket }], { + excludeManagementEventSources: [ + ManagementEventSources.KMS, + ManagementEventSources.RDS_DATA_API, + ], + }); + cloudTrail.addS3EventSelector([{ bucket }], { + excludeManagementEventSources: [], + }); + + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [{ + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': ['testBucketDF4D7D1A', 'Arn'] }, + '/', + ], + ], + }], + }], + ExcludeManagementEventSources: [ + 'kms.amazonaws.com', + 'rdsdata.amazonaws.com', + ], + }, + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [{ + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': ['testBucketDF4D7D1A', 'Arn'] }, + '/', + ], + ], + }], + }], + ExcludeManagementEventSources: [], + }, + ], + }); + }); + test('for Lambda function data event', () => { const stack = getTestStack(); const lambdaFunction = new lambda.Function(stack, 'LambdaFunction', { diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/package.json b/packages/@aws-cdk/aws-cloudwatch-actions/package.json index 0b687b88aee14..62a23e4c89e14 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/package.json +++ b/packages/@aws-cdk/aws-cloudwatch-actions/package.json @@ -70,8 +70,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts index d4dd9781f8370..1d6dd47793796 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts @@ -10,7 +10,7 @@ test('can use instance reboot as alarm action', () => { metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed', - dimensions: { + dimensionsMap: { InstanceId: 'i-03cb889aaaafffeee', }, }), diff --git a/packages/@aws-cdk/aws-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index 450fc8ca9821a..37a31f636b3ad 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -25,6 +25,8 @@ For example, `lambda.Function` objects have the `fn.metricErrors()` method, whic represents the amount of errors reported by that Lambda function: ```ts +declare const fn: lambda.Function; + const errors = fn.metricErrors(); ``` @@ -37,7 +39,7 @@ For example: ```ts const hostedZone = new route53.HostedZone(this, 'MyHostedZone', { zoneName: "example.org" }); -const metric = new Metric({ +const metric = new cloudwatch.Metric({ namespace: 'AWS/Route53', metricName: 'DNSQueries', dimensionsMap: { @@ -52,7 +54,7 @@ If you want to reference a metric that is not yet exposed by an existing constru you can instantiate a `Metric` object to represent it. For example: ```ts -const metric = new Metric({ +const metric = new cloudwatch.Metric({ namespace: 'MyNamespace', metricName: 'MyMetric', dimensionsMap: { @@ -67,11 +69,13 @@ Math expressions are supported by instantiating the `MathExpression` class. For example, a math expression that sums two other metrics looks like this: ```ts -const allProblems = new MathExpression({ - expression: "errors + faults", +declare const fn: lambda.Function; + +const allProblems = new cloudwatch.MathExpression({ + expression: "errors + throttles", usingMetrics: { - errors: myConstruct.metricErrors(), - faults: myConstruct.metricFaults(), + errors: fn.metricErrors(), + faults: fn.metricThrottles(), } }); ``` @@ -80,11 +84,14 @@ You can use `MathExpression` objects like any other metric, including using them in other math expressions: ```ts -const problemPercentage = new MathExpression({ +declare const fn: lambda.Function; +declare const allProblems: cloudwatch.MathExpression; + +const problemPercentage = new cloudwatch.MathExpression({ expression: "(problems / invocations) * 100", usingMetrics: { problems: allProblems, - invocations: myConstruct.metricInvocations() + invocations: fn.metricInvocations() } }); ``` @@ -96,7 +103,7 @@ search expression returns all CPUUtilization metrics that it finds, with the graph showing the Average statistic with an aggregation period of 5 minutes: ```ts -const cpuUtilization = new MathExpression({ +const cpuUtilization = new cloudwatch.MathExpression({ expression: "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)" }); ``` @@ -120,6 +127,8 @@ the function or the period), you can do so by passing additional parameters to the metric function call: ```ts +declare const fn: lambda.Function; + const minuteErrorRate = fn.metricErrors({ statistic: 'avg', period: Duration.minutes(1), @@ -153,9 +162,10 @@ useful when embedding them in graphs, see below). Alarms can be created on metrics in one of two ways. Either create an `Alarm` object, passing the `Metric` object to set the alarm on: - ```ts -new Alarm(this, 'Alarm', { +declare const fn: lambda.Function; + +new cloudwatch.Alarm(this, 'Alarm', { metric: fn.metricErrors(), threshold: 100, evaluationPeriods: 2, @@ -165,6 +175,8 @@ new Alarm(this, 'Alarm', { Alternatively, you can call `metric.createAlarm()`: ```ts +declare const fn: lambda.Function; + fn.metricErrors().createAlarm(this, 'Alarm', { threshold: 100, evaluationPeriods: 2, @@ -188,33 +200,57 @@ an SNS topic when an alarm breaches, do the following: ```ts import * as cw_actions from '@aws-cdk/aws-cloudwatch-actions'; +declare const alarm: cloudwatch.Alarm; -// ... -const topic = new sns.Topic(stack, 'Topic'); -const alarm = new cloudwatch.Alarm(stack, 'Alarm', { /* ... */ }); - +const topic = new sns.Topic(this, 'Topic'); alarm.addAlarmAction(new cw_actions.SnsAction(topic)); ``` +#### Notification formats + +Alarms can be created in one of two "formats": + +- With "top-level parameters" (these are the classic style of CloudWatch Alarms). +- With a list of metrics specifications (these are the modern style of CloudWatch Alarms). + +For backwards compatibility, CDK will try to create classic, top-level CloudWatch alarms +as much as possible, unless you are using features that cannot be expressed in that format. +Features that require the new-style alarm format are: + +- Metric math +- Cross-account metrics +- Labels + +The difference between these two does not impact the functionality of the alarm +in any way, *except* that the format of the notifications the Alarm generates is +different between them. This affects both the notifications sent out over SNS, +as well as the EventBridge events generated by this Alarm. If you are writing +code to consume these notifications, be sure to handle both formats. + ### Composite Alarms -[Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/) +[Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/) can be created from existing Alarm resources. ```ts -const alarmRule = AlarmRule.anyOf( - AlarmRule.allOf( - AlarmRule.anyOf( +declare const alarm1: cloudwatch.Alarm; +declare const alarm2: cloudwatch.Alarm; +declare const alarm3: cloudwatch.Alarm; +declare const alarm4: cloudwatch.Alarm; + +const alarmRule = cloudwatch.AlarmRule.anyOf( + cloudwatch.AlarmRule.allOf( + cloudwatch.AlarmRule.anyOf( alarm1, - AlarmRule.fromAlarm(alarm2, AlarmState.OK), + cloudwatch.AlarmRule.fromAlarm(alarm2, cloudwatch.AlarmState.OK), alarm3, ), - AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)), + cloudwatch.AlarmRule.not(cloudwatch.AlarmRule.fromAlarm(alarm4, cloudwatch.AlarmState.INSUFFICIENT_DATA)), ), - AlarmRule.fromBoolean(false), + cloudwatch.AlarmRule.fromBoolean(false), ); -new CompositeAlarm(this, 'MyAwesomeCompositeAlarm', { +new cloudwatch.CompositeAlarm(this, 'MyAwesomeCompositeAlarm', { alarmRule, }); ``` @@ -260,7 +296,11 @@ A graph widget can display any number of metrics on either the `left` or `right` vertical axis: ```ts -dashboard.addWidgets(new GraphWidget({ +declare const dashboard: cloudwatch.Dashboard; +declare const executionCountMetric: cloudwatch.Metric; +declare const errorCountMetric: cloudwatch.Metric; + +dashboard.addWidgets(new cloudwatch.GraphWidget({ title: "Executions vs error rate", left: [executionCountMetric], @@ -268,7 +308,7 @@ dashboard.addWidgets(new GraphWidget({ right: [errorCountMetric.with({ statistic: "average", label: "Error rate", - color: Color.GREEN + color: cloudwatch.Color.GREEN })] })); ``` @@ -278,12 +318,13 @@ Using the methods `addLeftMetric()` and `addRightMetric()` you can add metrics t Graph widgets can also display annotations attached to the left or the right y-axis. ```ts -dashboard.addWidgets(new GraphWidget({ - // ... +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.GraphWidget({ // ... leftAnnotations: [ - { value: 1800, label: Duration.minutes(30).toHumanString(), color: Color.RED, }, + { value: 1800, label: Duration.minutes(30).toHumanString(), color: cloudwatch.Color.RED, }, { value: 3600, label: '1 hour', color: '#2ca02c', } ], })); @@ -292,19 +333,21 @@ dashboard.addWidgets(new GraphWidget({ The graph legend can be adjusted from the default position at bottom of the widget. ```ts -dashboard.addWidgets(new GraphWidget({ - // ... +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.GraphWidget({ // ... - legendPosition: LegendPosition.RIGHT, + legendPosition: cloudwatch.LegendPosition.RIGHT, })); ``` The graph can publish live data within the last minute that has not been fully aggregated. ```ts -dashboard.addWidgets(new GraphWidget({ - // ... +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.GraphWidget({ // ... liveData: true, @@ -314,11 +357,12 @@ dashboard.addWidgets(new GraphWidget({ The graph view can be changed from default 'timeSeries' to 'bar' or 'pie'. ```ts -dashboard.addWidgets(new GraphWidget({ - // ... +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.GraphWidget({ // ... - view: GraphWidgetView.BAR, + view: cloudwatch.GraphWidgetView.BAR, })); ``` @@ -327,7 +371,10 @@ dashboard.addWidgets(new GraphWidget({ An alarm widget shows the graph and the alarm line of a single alarm: ```ts -dashboard.addWidgets(new AlarmWidget({ +declare const dashboard: cloudwatch.Dashboard; +declare const errorAlarm: cloudwatch.Alarm; + +dashboard.addWidgets(new cloudwatch.AlarmWidget({ title: "Errors", alarm: errorAlarm, })); @@ -339,7 +386,11 @@ A single-value widget shows the latest value of a set of metrics (as opposed to a graph of the value over time): ```ts -dashboard.addWidgets(new SingleValueWidget({ +declare const dashboard: cloudwatch.Dashboard; +declare const visitorCount: cloudwatch.Metric; +declare const purchaseCount: cloudwatch.Metric; + +dashboard.addWidgets(new cloudwatch.SingleValueWidget({ metrics: [visitorCount, purchaseCount], })); ``` @@ -347,9 +398,10 @@ dashboard.addWidgets(new SingleValueWidget({ Show as many digits as can fit, before rounding. ```ts -dashboard.addWidgets(new SingleValueWidget({ - // .. - // .. +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.SingleValueWidget({ + metrics: [ /* ... */ ], fullPrecision: true, })); @@ -361,7 +413,9 @@ A text widget shows an arbitrary piece of MarkDown. Use this to add explanations to your dashboard: ```ts -dashboard.addWidgets(new TextWidget({ +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.TextWidget({ markdown: '# Key Performance Indicators' })); ``` @@ -372,8 +426,11 @@ An alarm status widget displays instantly the status of any type of alarms and g ability to aggregate one or more alarms together in a small surface. ```ts +declare const dashboard: cloudwatch.Dashboard; +declare const errorAlarm: cloudwatch.Alarm; + dashboard.addWidgets( - new AlarmStatusWidget({ + new cloudwatch.AlarmStatusWidget({ alarms: [errorAlarm], }) ); @@ -384,9 +441,11 @@ dashboard.addWidgets( A `LogQueryWidget` shows the results of a query from Logs Insights: ```ts -dashboard.addWidgets(new LogQueryWidget({ +declare const dashboard: cloudwatch.Dashboard; + +dashboard.addWidgets(new cloudwatch.LogQueryWidget({ logGroupNames: ['my-log-group'], - view: LogQueryVisualizationType.TABLE, + view: cloudwatch.LogQueryVisualizationType.TABLE, // The lines will be automatically combined using '\n|'. queryLines: [ 'fields @message', diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index e09b5d714af69..62d717f33dc54 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,4 +1,4 @@ -import { Lazy, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAlarmAction } from './alarm-action'; import { AlarmBase, IAlarm } from './alarm-base'; @@ -114,7 +114,7 @@ export class Alarm extends AlarmBase { public static fromAlarmArn(scope: Construct, id: string, alarmArn: string): IAlarm { class Import extends AlarmBase implements IAlarm { public readonly alarmArn = alarmArn; - public readonly alarmName = Stack.of(scope).parseArn(alarmArn, ':').resourceName!; + public readonly alarmName = Stack.of(scope).splitArn(alarmArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!; } return new Import(scope, id); } @@ -192,7 +192,7 @@ export class Alarm extends AlarmBase { service: 'cloudwatch', resource: 'alarm', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.alarmName = this.getResourceNameAttribute(alarm.ref); @@ -257,8 +257,7 @@ export class Alarm extends AlarmBase { return dispatchMetric(metric, { withStat(stat, conf) { self.validateMetricStat(stat, metric); - const canRenderAsLegacyMetric = conf.renderingProperties?.label == undefined && - (stat.account == undefined || Stack.of(self).account == stat.account); + const canRenderAsLegacyMetric = conf.renderingProperties?.label == undefined && !self.requiresAccountId(stat); // Do this to disturb existing templates as little as possible if (canRenderAsLegacyMetric) { return dropUndefined({ @@ -286,7 +285,7 @@ export class Alarm extends AlarmBase { unit: stat.unitFilter, }, id: 'm1', - accountId: stat.account, + accountId: self.requiresAccountId(stat) ? stat.account : undefined, label: conf.renderingProperties?.label, returnData: true, } as CfnAlarm.MetricDataQueryProperty, @@ -321,7 +320,7 @@ export class Alarm extends AlarmBase { unit: stat.unitFilter, }, id: entry.id || uniqueMetricId(), - accountId: stat.account, + accountId: self.requiresAccountId(stat) ? stat.account : undefined, label: conf.renderingProperties?.label, returnData: entry.tag ? undefined : false, // entry.tag evaluates to true if the metric is the math expression the alarm is based on. }; @@ -370,6 +369,23 @@ export class Alarm extends AlarmBase { throw new Error('Cannot create an Alarm based on a MathExpression which specifies a searchAccount or searchRegion'); } } + + /** + * Determine if the accountId property should be included in the metric. + */ + private requiresAccountId(stat: MetricStatConfig): boolean { + const stackAccount = Stack.of(this).account; + + // if stat.account is undefined, it's by definition in the same account + if (stat.account === undefined) { + return false; + } + + // Return true if they're different. The ACCOUNT_ID token is interned + // so will always have the same string value (and even if we guess wrong + // it will still work). + return stackAccount !== stat.account; + } } function definitelyDifferent(x: string | undefined, y: string) { diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts index 8b9d61c99ea82..08f0db1b0880c 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts @@ -1,4 +1,4 @@ -import { Lazy, Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AlarmBase, IAlarm, IAlarmRule } from './alarm-base'; import { CfnCompositeAlarm } from './cloudwatch.generated'; @@ -68,7 +68,7 @@ export class CompositeAlarm extends AlarmBase { public static fromCompositeAlarmArn(scope: Construct, id: string, compositeAlarmArn: string): IAlarm { class Import extends AlarmBase implements IAlarm { public readonly alarmArn = compositeAlarmArn; - public readonly alarmName = Stack.of(scope).parseArn(compositeAlarmArn).resourceName!; + public readonly alarmName = Stack.of(scope).splitArn(compositeAlarmArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; } return new Import(scope, id); } @@ -115,7 +115,7 @@ export class CompositeAlarm extends AlarmBase { service: 'cloudwatch', resource: 'alarm', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index d306978c93733..03d9e4fe5ca7d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -288,8 +288,7 @@ export class Metric implements IMetric { } return new Metric({ - dimensions: ifUndefined(props.dimensions, this.dimensions), - dimensionsMap: props.dimensionsMap, + dimensionsMap: props.dimensionsMap ?? props.dimensions ?? this.dimensions, namespace: this.namespace, metricName: this.metricName, period: ifUndefined(props.period, this.period), diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index 9e5d8892953a1..b02f9e882b51a 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,8 +84,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudwatch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-cloudwatch/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..85cc6b579f761 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudwatch/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack, Duration } from '@aws-cdk/core'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as sns from '@aws-cdk/aws-sns'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + + diff --git a/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts index afb2224eb2f50..85e6460bb0e08 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts @@ -9,9 +9,7 @@ const testMetric = new Metric({ }); describe('Alarm', () => { - test('alarm does not accept a math expression with more than 10 metrics', () => { - const stack = new Stack(); const usingMetrics: Record = {}; @@ -30,19 +28,15 @@ describe('Alarm', () => { }); expect(() => { - new Alarm(stack, 'Alarm', { metric: math, threshold: 1000, evaluationPeriods: 3, }); - }).toThrow(/Alarms on math expressions cannot contain more than 10 individual metrics/); - - }); - test('non ec2 instance related alarm does not accept EC2 action', () => { + test('non ec2 instance related alarm does not accept EC2 action', () => { const stack = new Stack(); const alarm = new Alarm(stack, 'Alarm', { metric: testMetric, @@ -53,8 +47,8 @@ describe('Alarm', () => { expect(() => { alarm.addAlarmAction(new Ec2TestAlarmAction('arn:aws:automate:us-east-1:ec2:reboot')); }).toThrow(/EC2 alarm actions requires an EC2 Per-Instance Metric. \(.+ does not have an 'InstanceId' dimension\)/); - }); + test('can make simple alarm', () => { // GIVEN const stack = new Stack(); @@ -76,8 +70,6 @@ describe('Alarm', () => { Statistic: 'Average', Threshold: 1000, }); - - }); test('override metric period in Alarm', () => { @@ -86,8 +78,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - period: Duration.minutes(10), + metric: testMetric.with({ period: Duration.minutes(10) }), threshold: 1000, evaluationPeriods: 3, }); @@ -102,8 +93,6 @@ describe('Alarm', () => { Statistic: 'Average', Threshold: 1000, }); - - }); test('override statistic Alarm', () => { @@ -112,8 +101,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - statistic: 'max', + metric: testMetric.with({ statistic: 'max' }), threshold: 1000, evaluationPeriods: 3, }); @@ -129,8 +117,6 @@ describe('Alarm', () => { ExtendedStatistic: Match.absent(), Threshold: 1000, }); - - }); test('can use percentile in Alarm', () => { @@ -139,8 +125,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - statistic: 'P99', + metric: testMetric.with({ statistic: 'P99' }), threshold: 1000, evaluationPeriods: 3, }); @@ -156,8 +141,6 @@ describe('Alarm', () => { ExtendedStatistic: 'p99', Threshold: 1000, }); - - }); test('can set DatapointsToAlarm', () => { @@ -183,8 +166,6 @@ describe('Alarm', () => { Statistic: 'Average', Threshold: 1000, }); - - }); test('can add actions to alarms', () => { @@ -208,8 +189,6 @@ describe('Alarm', () => { InsufficientDataActions: ['B'], OKActions: ['C'], }); - - }); test('can make alarm directly from metric', () => { @@ -217,11 +196,12 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { - threshold: 1000, - evaluationPeriods: 2, + testMetric.with({ statistic: 'min', period: Duration.seconds(10), + }).createAlarm(stack, 'Alarm', { + threshold: 1000, + evaluationPeriods: 2, }); // THEN @@ -234,8 +214,6 @@ describe('Alarm', () => { Statistic: 'Minimum', Threshold: 1000, }); - - }); test('can use percentile string to make alarm', () => { @@ -243,18 +221,17 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { + testMetric.with({ + statistic: 'p99.9', + }).createAlarm(stack, 'Alarm', { threshold: 1000, evaluationPeriods: 2, - statistic: 'p99.9', }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ExtendedStatistic: 'p99.9', }); - - }); test('can use a generic string for extended statistic to make alarm', () => { @@ -262,10 +239,11 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { + testMetric.with({ + statistic: 'tm99.9999999999', + }).createAlarm(stack, 'Alarm', { threshold: 1000, evaluationPeriods: 2, - statistic: 'tm99.9999999999', }); // THEN @@ -273,9 +251,7 @@ describe('Alarm', () => { Statistic: Match.absent(), ExtendedStatistic: 'tm99.9999999999', }); - }); - }); class TestAlarmAction implements IAlarmAction { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts index 668807c89bfef..00b9ef07b8aa8 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts @@ -1,5 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; -import { Duration, Stack } from '@aws-cdk/core'; +import { Match, Template } from '@aws-cdk/assertions'; +import { Duration, Stack, Token, Aws } from '@aws-cdk/core'; import { Alarm, GraphWidget, IWidget, MathExpression, Metric } from '../lib'; const a = new Metric({ namespace: 'Test', metricName: 'ACount' }); @@ -7,11 +7,13 @@ const a = new Metric({ namespace: 'Test', metricName: 'ACount' }); let stack1: Stack; let stack2: Stack; let stack3: Stack; +let stack4: Stack; describe('cross environment', () => { beforeEach(() => { stack1 = new Stack(undefined, undefined, { env: { region: 'pluto', account: '1234' } }); stack2 = new Stack(undefined, undefined, { env: { region: 'mars', account: '5678' } }); stack3 = new Stack(undefined, undefined, { env: { region: 'pluto', account: '0000' } }); + stack4 = new Stack(undefined, undefined); }); describe('in graphs', () => { @@ -124,12 +126,10 @@ describe('cross environment', () => { Namespace: 'Test', Period: 300, }); - - }); test('metric attached to stack1 will throw in stack2', () => { - // Cross-region/cross-account metrics are supported in Dashboards but not in Alarms + // Cross-region metrics are supported in Dashboards but not in Alarms // GIVEN expect(() => { @@ -139,8 +139,6 @@ describe('cross environment', () => { metric: a.attachTo(stack1), }); }).toThrow(/Cannot create an Alarm in region 'mars' based on metric 'ACount' in 'pluto'/); - - }); test('metric attached to stack3 will render in stack1', () => { @@ -207,12 +205,49 @@ describe('cross environment', () => { }); }); + test('metric from same account as stack will not have accountId', () => { + // GIVEN + + // including label property will force Alarm configuration to "modern" config. + const b = new Metric({ + namespace: 'Test', + metricName: 'ACount', + label: 'my-label', + }); + + new Alarm(stack1, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric: b, + }); + + // THEN + Template.fromStack(stack1).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: [ + { + AccountId: Match.absent(), + Id: 'm1', + Label: 'my-label', + MetricStat: { + Metric: { + MetricName: 'ACount', + Namespace: 'Test', + }, + Period: 300, + Stat: 'Average', + }, + ReturnData: true, + }, + ], + }); + }); + test('math expression can render in a different account', () => { // GIVEN const b = new Metric({ namespace: 'Test', metricName: 'ACount', - account: '1234', + account: '5678', }); const c = new MathExpression({ @@ -248,7 +283,64 @@ describe('cross environment', () => { ReturnData: false, }, { - AccountId: '1234', + AccountId: '5678', + Id: 'b', + MetricStat: { + Metric: { + MetricName: 'ACount', + Namespace: 'Test', + }, + Period: 60, + Stat: 'Average', + }, + ReturnData: false, + }, + ], + }); + }); + + test('math expression from same account as stack will not have accountId', () => { + // GIVEN + const b = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: '1234', + }); + + const c = new MathExpression({ + expression: 'a + b', + usingMetrics: { a: a.attachTo(stack1), b }, + period: Duration.minutes(1), + }); + + new Alarm(stack1, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric: c, + }); + + // THEN + Template.fromStack(stack1).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: [ + { + Expression: 'a + b', + Id: 'expr_1', + }, + { + AccountId: Match.absent(), + Id: 'a', + MetricStat: { + Metric: { + MetricName: 'ACount', + Namespace: 'Test', + }, + Period: 60, + Stat: 'Average', + }, + ReturnData: false, + }, + { + AccountId: Match.absent(), Id: 'b', MetricStat: { Metric: { @@ -289,7 +381,7 @@ describe('cross environment', () => { }).toThrow(/Cannot create an Alarm based on a MathExpression which specifies a searchAccount or searchRegion/); }); - test('match expression with different searchRegion will throw', () => { + test('math expression with different searchRegion will throw', () => { // GIVEN const b = new Metric({ namespace: 'Test', @@ -313,6 +405,126 @@ describe('cross environment', () => { }); }).toThrow(/Cannot create an Alarm based on a MathExpression which specifies a searchAccount or searchRegion/); }); + + describe('accountId requirements', () => { + test('metric account is not defined', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as legacy alarm. + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + Threshold: 1, + EvaluationPeriods: 1, + MetricName: 'ACount', + Namespace: 'Test', + }); + }); + + test('metric account is defined and stack account is token', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: '123456789', + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as modern alarm. + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: Match.anyValue(), + }); + }); + + test('metric account is attached to stack account', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric: metric.attachTo(stack4), + }); + + // Alarm will be defined as legacy alarm. + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + Threshold: 1, + EvaluationPeriods: 1, + MetricName: 'ACount', + Namespace: 'Test', + }); + }); + + test('metric account === stack account, both are the AccountID token', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: Aws.ACCOUNT_ID, + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as legacy alarm + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + MetricName: 'ACount', + }); + }); + + test('metric account and stack account are different tokens', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: Token.asString('asdf'), + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as modern alarm, since there is no way of knowing that the two tokens are equal. + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: Match.anyValue(), + }); + }); + + test('metric account !== stack account', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: '123456789', + }); + + new Alarm(stack1, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as modern alarm. + Template.fromStack(stack1).hasResourceProperties('AWS::CloudWatch::Alarm', { + Metrics: Match.anyValue(), + }); + }); + }); }); }); diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts index 1ca941d8519b4..799dad893e227 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts @@ -16,13 +16,13 @@ const queue = new cdk.CfnResource(stack, 'queue', { type: 'AWS::SQS::Queue' }); const numberOfMessagesVisibleMetric = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, }); const sentMessageSizeMetric = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'SentMessageSize', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, }); const alarm = numberOfMessagesVisibleMetric.createAlarm(stack, 'Alarm', { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts index 5a3285d873fe8..9f966feb03b61 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts @@ -16,7 +16,7 @@ const queue = new cdk.CfnResource(stack, 'queue', { type: 'AWS::SQS::Queue' }); const metricA = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, period: cdk.Duration.seconds(10), label: 'Visible Messages', }); @@ -24,7 +24,7 @@ const metricA = new cloudwatch.Metric({ const metricB = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesNotVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, period: cdk.Duration.seconds(30), label: 'NotVisible Messages', }); diff --git a/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts index d13d3a8c4e0c6..49b9384df870d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { Alarm, Metric } from '../lib'; @@ -51,7 +52,7 @@ describe('Metrics', () => { }); - test('cannot use null dimension value', () => { + testDeprecated('cannot use null dimension value', () => { expect(() => { new Metric({ namespace: 'Test', @@ -66,7 +67,7 @@ describe('Metrics', () => { }); - test('cannot use undefined dimension value', () => { + testDeprecated('cannot use undefined dimension value', () => { expect(() => { new Metric({ namespace: 'Test', @@ -81,7 +82,7 @@ describe('Metrics', () => { }); - test('cannot use long dimension values', () => { + testDeprecated('cannot use long dimension values', () => { const arr = new Array(256); const invalidDimensionValue = arr.fill('A', 0).join(''); @@ -117,7 +118,7 @@ describe('Metrics', () => { }); - test('throws error when there are more than 10 dimensions', () => { + testDeprecated('throws error when there are more than 10 dimensions', () => { expect(() => { new Metric({ namespace: 'Test', diff --git a/packages/@aws-cdk/aws-codeartifact/package.json b/packages/@aws-cdk/aws-codeartifact/package.json index 3d2410610103b..6ad2e9c887a85 100644 --- a/packages/@aws-cdk/aws-codeartifact/package.json +++ b/packages/@aws-cdk/aws-codeartifact/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index d4b31c1bf91db..4bccbbc68cfa1 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -30,7 +30,7 @@ $ npm i @aws-cdk/aws-codebuild Import it into your code: -```ts +```ts nofixture import * as codebuild from '@aws-cdk/aws-codebuild'; ``` @@ -56,7 +56,6 @@ CodeBuild!`: Use an AWS CodeCommit repository as the source of this build: ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codecommit from '@aws-cdk/aws-codecommit'; const repository = new codecommit.Repository(this, 'MyRepo', { repositoryName: 'foo' }); @@ -70,10 +69,8 @@ new codebuild.Project(this, 'MyFirstCodeCommitProject', { Create a CodeBuild project with an S3 bucket as the source: ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; -import * as s3 from '@aws-cdk/aws-s3'; - const bucket = new s3.Bucket(this, 'MyBucket'); + new codebuild.Project(this, 'MyProject', { source: codebuild.Source.s3({ bucket: bucket, @@ -140,7 +137,9 @@ const gitHubSource = codebuild.Source.gitHub({ CodeBuild Projects can produce Artifacts and upload them to S3. For example: ```ts -const project = codebuild.Project(stack, 'MyProject', { +declare const bucket: s3.Bucket; + +const project = new codebuild.Project(this, 'MyProject', { buildSpec: codebuild.BuildSpec.fromObject({ version: '0.2', }), @@ -185,23 +184,53 @@ You can save time when your project builds by using a cache. A cache can store r ### S3 Caching -With S3 caching, the cache is stored in an S3 bucket which is available from multiple hosts. +With S3 caching, the cache is stored in an S3 bucket which is available +regardless from what CodeBuild instance gets selected to run your CodeBuild job +on. When using S3 caching, you must also add in a `cache` section to your +buildspec which indicates the files to be cached: ```ts +declare const myCachingBucket: s3.Bucket; + new codebuild.Project(this, 'Project', { source: codebuild.Source.bitBucket({ owner: 'awslabs', repo: 'aws-cdk', }), - cache: codebuild.Cache.bucket(new Bucket(this, 'Bucket')) + + cache: codebuild.Cache.bucket(myCachingBucket), + + // BuildSpec with a 'cache' section necessary for S3 caching. This can + // also come from 'buildspec.yml' in your source. + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: ['...'], + }, + }, + cache: { + paths: [ + // The '**/*' is required to indicate all files in this directory + '/root/cachedir/**/*', + ], + }, + }), }); ``` +Note that two different CodeBuild Projects using the same S3 bucket will *not* +share their cache: each Project will get a unique file in the S3 bucket to store +the cache in. + ### Local Caching -With local caching, the cache is stored on the codebuild instance itself. This is simple, -cheap and fast, but CodeBuild cannot guarantee a reuse of instance and hence cannot -guarantee cache hits. For example, when a build starts and caches files locally, if two subsequent builds start at the same time afterwards only one of those builds would get the cache. Three different cache modes are supported, which can be turned on individually. +With local caching, the cache is stored on the codebuild instance itself. This +is simple, cheap and fast, but CodeBuild cannot guarantee a reuse of instance +and hence cannot guarantee cache hits. For example, when a build starts and +caches files locally, if two subsequent builds start at the same time afterwards +only one of those builds would get the cache. Three different cache modes are +supported, which can be turned on individually. * `LocalCacheMode.SOURCE` caches Git metadata for primary and secondary sources. * `LocalCacheMode.DOCKER_LAYER` caches existing Docker layers. @@ -214,7 +243,24 @@ new codebuild.Project(this, 'Project', { }), // Enable Docker AND custom caching - cache: codebuild.Cache.local(LocalCacheMode.DOCKER_LAYER, LocalCacheMode.CUSTOM) + cache: codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER, codebuild.LocalCacheMode.CUSTOM), + + // BuildSpec with a 'cache' section necessary for 'CUSTOM' caching. This can + // also come from 'buildspec.yml' in your source. + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: ['...'], + }, + }, + cache: { + paths: [ + // The '**/*' is required to indicate all files in this directory + '/root/cachedir/**/*', + ], + }, + }), }); ``` @@ -260,6 +306,8 @@ Note that the `WindowsBuildImage` version of the static methods accepts an optio which can be either `WindowsImageType.STANDARD`, the default, or `WindowsImageType.SERVER_2019`: ```ts +declare const ecrRepository: ecr.Repository; + new codebuild.Project(this, 'Project', { environment: { buildImage: codebuild.WindowsBuildImage.fromEcrRepository(ecrRepository, 'v1.0', codebuild.WindowsImageType.SERVER_2019), @@ -269,7 +317,7 @@ new codebuild.Project(this, 'Project', { objectKey: 'path/to/cert.pem', }, }, - ... + // ... }) ``` @@ -296,7 +344,7 @@ new codebuild.Project(this, 'Project', { environment: { buildImage: codebuild.LinuxGpuBuildImage.DLC_TENSORFLOW_2_1_0_INFERENCE, }, - ... + // ... }) ``` @@ -315,10 +363,12 @@ new codebuild.Project(this, 'Project', { buildImage: codebuild.LinuxGpuBuildImage.awsDeepLearningContainersImage( 'tensorflow-inference', '2.1.0-gpu-py36-cu101-ubuntu18.04', '123456789012'), }, - ... + // ... }) ``` +Alternatively, you can reference an image available in an ECR repository using the `LinuxGpuBuildImage.fromEcrRepository(repo[, tag])` method. + ## Logs CodeBuild lets you specify an S3 Bucket, CloudWatch Log Group or both to receive logs from your projects. @@ -331,10 +381,9 @@ By default, logs will go to cloudwatch. new codebuild.Project(this, 'Project', { logging: { cloudWatch: { - logGroup: new cloudwatch.LogGroup(this, `MyLogGroup`), + logGroup: new logs.LogGroup(this, `MyLogGroup`), } }, - ... }) ``` @@ -347,7 +396,6 @@ new codebuild.Project(this, 'Project', { bucket: new s3.Bucket(this, `LogBucket`) } }, - ... }) ``` @@ -358,7 +406,7 @@ like GitHub: ```ts new codebuild.GitHubSourceCredentials(this, 'CodeBuildGitHubCreds', { - accessToken: cdk.SecretValue.secretsManager('my-token'), + accessToken: SecretValue.secretsManager('my-token'), }); // GitHub Enterprise is almost the same, // except the class is called GitHubEnterpriseSourceCredentials @@ -368,8 +416,8 @@ and BitBucket: ```ts new codebuild.BitBucketSourceCredentials(this, 'CodeBuildBitBucketCreds', { - username: cdk.SecretValue.secretsManager('my-bitbucket-creds', { jsonField: 'username' }), - password: cdk.SecretValue.secretsManager('my-bitbucket-creds', { jsonField: 'password' }), + username: SecretValue.secretsManager('my-bitbucket-creds', { jsonField: 'username' }), + password: SecretValue.secretsManager('my-bitbucket-creds', { jsonField: 'password' }), }); ``` @@ -409,8 +457,10 @@ if you'd rather not have those permissions added, you can opt out of it when creating the project: ```ts +declare const source: codebuild.Source; + const project = new codebuild.Project(this, 'Project', { - // ... + source, grantReportGroupPermissions: false, }); ``` @@ -419,10 +469,13 @@ Alternatively, you can specify an ARN of an existing resource group, instead of a simple name, in your buildspec: ```ts +declare const source: codebuild.Source; + // create a new ReportGroup const reportGroup = new codebuild.ReportGroup(this, 'ReportGroup'); const project = new codebuild.Project(this, 'Project', { + source, buildSpec: codebuild.BuildSpec.fromObject({ // ... reports: { @@ -438,6 +491,9 @@ const project = new codebuild.Project(this, 'Project', { If you do that, you need to grant the project's role permissions to write reports to that report group: ```ts +declare const project: codebuild.Project; +declare const reportGroup: codebuild.ReportGroup; + reportGroup.grantWrite(project); ``` @@ -456,8 +512,12 @@ project as a AWS CloudWatch event rule target: ```ts // start build when a commit is pushed +import * as codecommit from '@aws-cdk/aws-codecommit'; import * as targets from '@aws-cdk/aws-events-targets'; +declare const codeCommitRepository: codecommit.Repository; +declare const project: codebuild.Project; + codeCommitRepository.onCommit('OnCommit', { target: new targets.CodeBuildProject(project), }); @@ -469,6 +529,10 @@ To define Amazon CloudWatch event rules for build projects, use one of the `onXx methods: ```ts +import * as targets from '@aws-cdk/aws-events-targets'; +declare const fn: lambda.Function; +declare const project: codebuild.Project; + const rule = project.onStateChange('BuildStateChange', { target: new targets.LambdaFunction(fn) }); @@ -480,7 +544,11 @@ To define CodeStar Notification rules for Projects, use one of the `notifyOnXxx( They are very similar to `onXxx()` methods for CloudWatch events: ```ts -const target = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { +import * as chatbot from '@aws-cdk/aws-chatbot'; + +declare const project: codebuild.Project; + +const target = new chatbot.SlackChannelConfiguration(this, 'MySlackChannel', { slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', slackChannelId: 'YOUR_SLACK_CHANNEL_ID', @@ -495,6 +563,10 @@ CodeBuild Projects can get their sources from multiple places, and produce multiple outputs. For example: ```ts +import * as codecommit from '@aws-cdk/aws-codecommit'; +declare const repo: codecommit.Repository; +declare const bucket: s3.Bucket; + const project = new codebuild.Project(this, 'MyProject', { secondarySources: [ codebuild.Source.codeCommit({ @@ -586,6 +658,8 @@ to access the resources that it needs by using the For example: ```ts +declare const loadBalancer: elbv2.ApplicationLoadBalancer; + const vpc = new ec2.Vpc(this, 'MyVPC'); const project = new codebuild.Project(this, 'MyProject', { vpc: vpc, @@ -608,7 +682,7 @@ The only supported file system type is `EFS`. For example: ```ts -new codebuild.Project(stack, 'MyProject', { +new codebuild.Project(this, 'MyProject', { buildSpec: codebuild.BuildSpec.fromObject({ version: '0.2', }), @@ -635,9 +709,9 @@ It returns an object containing the batch service role that was created, or `undefined` if batch builds could not be enabled, for example if the project was imported. ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; +declare const source: codebuild.Source; -const project = new codebuild.Project(this, 'MyProject', { ... }); +const project = new codebuild.Project(this, 'MyProject', { source, }); if (project.enableBatchBuilds()) { console.log('Batch builds were enabled'); @@ -652,9 +726,7 @@ The default is 60 minutes. An example of overriding the default follows. ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; - -new codebuild.Project(stack, 'MyProject', { +new codebuild.Project(this, 'MyProject', { timeout: Duration.minutes(90) }); ``` @@ -665,9 +737,7 @@ As an example, to allow your Project to queue for up to thirty (30) minutes befo use the following code. ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; - -new codebuild.Project(stack, 'MyProject', { +new codebuild.Project(this, 'MyProject', { queuedTimeout: Duration.minutes(30) }); ``` @@ -679,9 +749,7 @@ It is possible to limit the maximum concurrent builds to value between 1 and the By default there is no explicit limit. ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; - -new codebuild.Project(stack, 'MyProject', { +new codebuild.Project(this, 'MyProject', { concurrentBuildLimit: 1 }); ``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/file-location.ts b/packages/@aws-cdk/aws-codebuild/lib/file-location.ts index eabfd95570b06..b836fc92bc22a 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/file-location.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/file-location.ts @@ -71,7 +71,8 @@ export interface EfsFileSystemLocationProps { /** * A string that specifies the location of the file system, like Amazon EFS. - * @example 'fs-abcd1234.efs.us-west-2.amazonaws.com:/my-efs-mount-directory'. + * + * This value looks like `fs-abcd1234.efs.us-west-2.amazonaws.com:/my-efs-mount-directory`. */ readonly location: string; diff --git a/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts index 6cbf5bcfc2f7e..b26611745afbb 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts @@ -91,6 +91,22 @@ export class LinuxGpuBuildImage implements IBindableBuildImage { return new LinuxGpuBuildImage(repositoryName, tag, account); } + + /** + * Returns a GPU image running Linux from an ECR repository. + * + * NOTE: if the repository is external (i.e. imported), then we won't be able to add + * a resource policy statement for it so CodeBuild can pull the image. + * + * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html + * + * @param repository The ECR repository + * @param tag Image tag (default "latest") + */ + public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + return new LinuxGpuBuildImage(repository.repositoryName, tag, repository.env.account); + } + public readonly type = 'LINUX_GPU_CONTAINER'; public readonly defaultComputeType = ComputeType.LARGE; public readonly imageId: string; diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index fdd2e1295f6bb..f7a503d335cbc 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -8,7 +8,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '@aws-cdk/core'; +import { ArnFormat, Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -412,7 +412,7 @@ abstract class ProjectBase extends Resource implements IProject { return new cloudwatch.Metric({ namespace: 'AWS/CodeBuild', metricName, - dimensions: { ProjectName: this.projectName }, + dimensionsMap: { ProjectName: this.projectName }, ...props, }).attachTo(this); } @@ -751,7 +751,7 @@ export interface BindToCodePipelineOptions { export class Project extends ProjectBase { public static fromProjectArn(scope: Construct, id: string, projectArn: string): IProject { - const parsedArn = Stack.of(scope).parseArn(projectArn); + const parsedArn = Stack.of(scope).splitArn(projectArn, ArnFormat.SLASH_RESOURCE_NAME); class Import extends ProjectBase { public readonly grantPrincipal: iam.IPrincipal; @@ -874,7 +874,7 @@ export class Project extends ProjectBase { // 2. A Token. // 3. A simple value, like 'secret-id'. if (envVariableValue.startsWith('arn:')) { - const parsedArn = stack.parseArn(envVariableValue, ':'); + const parsedArn = stack.splitArn(envVariableValue, ArnFormat.COLON_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error('SecretManager ARN is missing the name of the secret: ' + envVariableValue); } @@ -889,7 +889,7 @@ export class Project extends ProjectBase { // (CodeBuild supports both), // stick a "*" at the end, which makes it work for both resourceName: `${secretName}*`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, partition: parsedArn.partition, account: parsedArn.account, region: parsedArn.region, @@ -903,7 +903,7 @@ export class Project extends ProjectBase { // We do not know the ID of the key, but since this is a cross-account access, // the key policies have to allow this access, so a wildcard is safe here resourceName: '*', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, partition: parsedArn.partition, account: parsedArn.account, region: parsedArn.region, @@ -931,7 +931,7 @@ export class Project extends ProjectBase { // We do not know the ID of the key, but since this is a cross-account access, // the key policies have to allow this access, so a wildcard is safe here resourceName: '*', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, partition: resourceStack.partition, account: resourceStack.account, region: resourceStack.region, @@ -957,7 +957,7 @@ export class Project extends ProjectBase { service: 'secretsmanager', resource: 'secret', resourceName: `${secretName}-??????`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); } } @@ -1257,7 +1257,7 @@ export class Project extends ProjectBase { const logGroupArn = Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: `/aws/codebuild/${this.projectName}`, }); @@ -1656,8 +1656,9 @@ class ArmBuildImage implements IBuildImage { public validate(buildEnvironment: BuildEnvironment): string[] { const ret = []; if (buildEnvironment.computeType && + buildEnvironment.computeType !== ComputeType.SMALL && buildEnvironment.computeType !== ComputeType.LARGE) { - ret.push(`ARM images only support ComputeType '${ComputeType.LARGE}' - ` + + ret.push(`ARM images only support ComputeTypes '${ComputeType.SMALL}' and '${ComputeType.LARGE}' - ` + `'${buildEnvironment.computeType}' was given`); } return ret; @@ -1724,6 +1725,8 @@ export class LinuxBuildImage implements IBuildImage { public static readonly AMAZON_LINUX_2_3 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:3.0'); public static readonly AMAZON_LINUX_2_ARM: IBuildImage = new ArmBuildImage('aws/codebuild/amazonlinux2-aarch64-standard:1.0'); + /** Image "aws/codebuild/amazonlinux2-aarch64-standard:2.0". */ + public static readonly AMAZON_LINUX_2_ARM_2: IBuildImage = new ArmBuildImage('aws/codebuild/amazonlinux2-aarch64-standard:2.0'); /** @deprecated Use {@link STANDARD_2_0} and specify runtime in buildspec runtime-versions section */ public static readonly UBUNTU_14_04_BASE = LinuxBuildImage.codeBuildImage('aws/codebuild/ubuntu-base:14.04'); diff --git a/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts b/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts index 229cb547e7c1f..1a94c521fc08a 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts @@ -39,7 +39,8 @@ export interface UntrustedCodeBoundaryPolicyProps { * * @example * - * iam.PermissionsBoundary.of(project).apply(new UntrustedCodeBoundaryPolicy(this, 'Boundary')); + * declare const project: codebuild.Project; + * iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(this, 'Boundary')); */ export class UntrustedCodeBoundaryPolicy extends iam.ManagedPolicy { constructor(scope: Construct, id: string, props: UntrustedCodeBoundaryPolicyProps = {}) { diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 9b54a075fce61..3d1609d9665b4 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -83,11 +90,12 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { + "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codestarnotifications": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-codebuild/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..12926b4e95060 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/rosetta/default.ts-fixture @@ -0,0 +1,19 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack, Duration, SecretValue } from '@aws-cdk/core'; +import codebuild = require('@aws-cdk/aws-codebuild'); +import iam = require('@aws-cdk/aws-iam'); +import ec2 = require('@aws-cdk/aws-ec2'); +import lambda = require('@aws-cdk/aws-lambda'); +import * as s3 from '@aws-cdk/aws-s3'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ecr from '@aws-cdk/aws-ecr'; +import * as logs from '@aws-cdk/aws-logs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts b/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts index cc8f9309bcb44..f5bb3eacda8d1 100644 --- a/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts @@ -1590,17 +1590,21 @@ describe('ARM image', () => { }); }); - test('cannot be used in conjunction with ComputeType SMALL', () => { + test('can be used with ComputeType SMALL', () => { const stack = new cdk.Stack(); + new codebuild.PipelineProject(stack, 'Project', { + environment: { + computeType: codebuild.ComputeType.SMALL, + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_ARM, + }, + }); - expect(() => { - new codebuild.PipelineProject(stack, 'Project', { - environment: { - buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_ARM, - computeType: codebuild.ComputeType.SMALL, - }, - }); - }).toThrow(/ARM images only support ComputeType 'BUILD_GENERAL1_LARGE' - 'BUILD_GENERAL1_SMALL' was given/); + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'Type': 'ARM_CONTAINER', + 'ComputeType': 'BUILD_GENERAL1_SMALL', + }, + }); }); test('cannot be used in conjunction with ComputeType MEDIUM', () => { @@ -1613,7 +1617,24 @@ describe('ARM image', () => { computeType: codebuild.ComputeType.MEDIUM, }, }); - }).toThrow(/ARM images only support ComputeType 'BUILD_GENERAL1_LARGE' - 'BUILD_GENERAL1_MEDIUM' was given/); + }).toThrow(/ARM images only support ComputeTypes 'BUILD_GENERAL1_SMALL' and 'BUILD_GENERAL1_LARGE' - 'BUILD_GENERAL1_MEDIUM' was given/); + }); + + test('can be used with ComputeType LARGE', () => { + const stack = new cdk.Stack(); + new codebuild.PipelineProject(stack, 'Project', { + environment: { + computeType: codebuild.ComputeType.LARGE, + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_ARM, + }, + }); + + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'Type': 'ARM_CONTAINER', + 'ComputeType': 'BUILD_GENERAL1_LARGE', + }, + }); }); test('cannot be used in conjunction with ComputeType X2_LARGE', () => { @@ -1626,7 +1647,7 @@ describe('ARM image', () => { computeType: codebuild.ComputeType.X2_LARGE, }, }); - }).toThrow(/ARM images only support ComputeType 'BUILD_GENERAL1_LARGE' - 'BUILD_GENERAL1_2XLARGE' was given/); + }).toThrow(/ARM images only support ComputeTypes 'BUILD_GENERAL1_SMALL' and 'BUILD_GENERAL1_LARGE' - 'BUILD_GENERAL1_2XLARGE' was given/); }); }); }); diff --git a/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts b/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts index 7e32ab033c68c..d3b0d44dde984 100644 --- a/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts @@ -1,5 +1,6 @@ import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import * as codebuild from '../lib'; @@ -58,4 +59,177 @@ describe('Linux GPU build image', () => { }); }); }); + + describe('ECR Repository', () => { + test('allows creating a build image from a new ECR repository', () => { + const stack = new cdk.Stack(); + + const repository = new ecr.Repository(stack, 'my-repo'); + + new codebuild.Project(stack, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { commands: ['ls'] }, + }, + }), + environment: { + buildImage: codebuild.LinuxGpuBuildImage.fromEcrRepository(repository, 'v1'), + }, + }); + + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + ComputeType: 'BUILD_GENERAL1_LARGE', + Image: { + 'Fn::Join': ['', [ + { Ref: 'AWS::AccountId' }, + '.dkr.ecr.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/', + { Ref: 'myrepo5DFA62E5' }, + ':v1', + ]], + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':ecr:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':repository/', + { Ref: 'myrepo5DFA62E5' }, + ]], + }, + })), + }, + }); + }); + + test('allows creating a build image from an existing ECR repository', () => { + const stack = new cdk.Stack(); + + const repository = ecr.Repository.fromRepositoryName(stack, 'my-imported-repo', 'test-repo'); + + new codebuild.Project(stack, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { commands: ['ls'] }, + }, + }), + environment: { + buildImage: codebuild.LinuxGpuBuildImage.fromEcrRepository(repository), + }, + }); + + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + ComputeType: 'BUILD_GENERAL1_LARGE', + Image: { + 'Fn::Join': ['', [ + { Ref: 'AWS::AccountId' }, + '.dkr.ecr.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/test-repo:latest', + ]], + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':ecr:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':repository/test-repo', + ]], + }, + })), + }, + }); + }); + + test('allows creating a build image from an existing cross-account ECR repository', () => { + const stack = new cdk.Stack(); + + const repository = ecr.Repository.fromRepositoryArn(stack, 'my-cross-acount-repo', 'arn:aws:ecr:us-east-1:585695036304:repository/foo/bar/foo/fooo'); + + new codebuild.Project(stack, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { commands: ['ls'] }, + }, + }), + environment: { + buildImage: codebuild.LinuxGpuBuildImage.fromEcrRepository(repository), + }, + }); + + expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + ComputeType: 'BUILD_GENERAL1_LARGE', + Image: { + 'Fn::Join': ['', [ + '585695036304.dkr.ecr.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/foo/bar/foo/fooo:latest', + ]], + }, + }, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':ecr:', + { Ref: 'AWS::Region' }, + ':585695036304:repository/foo/bar/foo/fooo', + ]], + }, + })), + }, + }); + }); + }); }); diff --git a/packages/@aws-cdk/aws-codebuild/test/project.test.ts b/packages/@aws-cdk/aws-codebuild/test/project.test.ts index 08fbe3e8f8768..0041d1651ec9d 100644 --- a/packages/@aws-cdk/aws-codebuild/test/project.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/project.test.ts @@ -673,7 +673,7 @@ describe('Environment', () => { test('logs config - s3', () => { // GIVEN const stack = new cdk.Stack(); - const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket', 'MyBucketName'); + const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket', 'mybucketname'); // WHEN new codebuild.Project(stack, 'Project', { @@ -693,7 +693,7 @@ describe('Environment', () => { expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { LogsConfig: objectLike({ S3Logs: { - Location: 'MyBucketName/my-logs', + Location: 'mybucketname/my-logs', Status: 'ENABLED', }, }), @@ -703,7 +703,7 @@ describe('Environment', () => { test('logs config - cloudWatch and s3', () => { // GIVEN const stack = new cdk.Stack(); - const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket2', 'MyBucketName'); + const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket2', 'mybucketname'); const logGroup = logs.LogGroup.fromLogGroupName(stack, 'LogGroup2', 'MyLogGroupName'); // WHEN @@ -730,7 +730,7 @@ describe('Environment', () => { Status: 'ENABLED', }, S3Logs: { - Location: 'MyBucketName', + Location: 'mybucketname', Status: 'ENABLED', }, }), diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index e55053c0249be..69781b04f793c 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,7 +1,7 @@ import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnRepository } from './codecommit.generated'; @@ -177,6 +177,7 @@ export interface IRepository extends IResource, notifications.INotificationRuleS /** * Defines a CodeStar Notification rule which triggers when a pull request is merged. + * @deprecated this method has a typo in its name, use notifyOnPullRequestMerged instead */ notifiyOnPullRequestMerged( id: string, @@ -184,6 +185,15 @@ export interface IRepository extends IResource, notifications.INotificationRuleS options?: notifications.NotificationRuleOptions, ): notifications.INotificationRule; + /** + * Defines a CodeStar Notification rule which triggers when a pull request is merged. + */ + notifyOnPullRequestMerged( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule; + /** * Defines a CodeStar Notification rule which triggers when a new branch or tag is created. */ @@ -419,6 +429,14 @@ abstract class RepositoryBase extends Resource implements IRepository { id: string, target: notifications.INotificationRuleTarget, options?: notifications.NotificationRuleOptions, + ): notifications.INotificationRule { + return this.notifyOnPullRequestMerged(id, target, options); + } + + public notifyOnPullRequestMerged( + id: string, + target: notifications.INotificationRuleTarget, + options?: notifications.NotificationRuleOptions, ): notifications.INotificationRule { return this.notifyOn(id, target, { ...options, @@ -483,7 +501,7 @@ export class Repository extends RepositoryBase { */ public static fromRepositoryArn(scope: Construct, id: string, repositoryArn: string): IRepository { const stack = Stack.of(scope); - const arn = stack.parseArn(repositoryArn); + const arn = stack.splitArn(repositoryArn, ArnFormat.NO_RESOURCE_NAME); const repositoryName = arn.resource; const region = arn.region; diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index d5041d7e5711b..9f40eabeaf1e9 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -83,9 +83,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-codestarnotifications": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.repository-notification.ts b/packages/@aws-cdk/aws-codecommit/test/integ.repository-notification.ts index 4f7a5926a47ac..9e61f82141178 100644 --- a/packages/@aws-cdk/aws-codecommit/test/integ.repository-notification.ts +++ b/packages/@aws-cdk/aws-codecommit/test/integ.repository-notification.ts @@ -14,6 +14,6 @@ const repository = new codecommit.Repository(stack, 'MyCodecommitRepository', { const target = new sns.Topic(stack, 'MyTopic'); repository.notifyOnPullRequestCreated('NotifyOnPullRequestCreated', target); -repository.notifiyOnPullRequestMerged('NotifyOnPullRequestMerged', target); +repository.notifyOnPullRequestMerged('NotifyOnPullRequestMerged', target); app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts index 32540aefc1111..721ce01d4c490 100644 --- a/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts +++ b/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts @@ -14,7 +14,7 @@ describe('notification rule', () => { repository.notifyOnPullRequestCreated('NotifyOnPullRequestCreated', target); - repository.notifiyOnPullRequestMerged('NotifyOnPullRequestMerged', target); + repository.notifyOnPullRequestMerged('NotifyOnPullRequestMerged', target); expect(stack).toHaveResource('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyCodecommitRepositoryNotifyOnPullRequestCreatedBB14EA32', diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index e8f878973ee6a..9b6aa5cda5d8d 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -22,10 +22,8 @@ The CDK currently supports Amazon EC2, on-premise and AWS Lambda applications. To create a new CodeDeploy Application that deploys to EC2/on-premise instances: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; - const application = new codedeploy.ServerApplication(this, 'CodeDeployApplication', { - applicationName: 'MyApplication', // optional property + applicationName: 'MyApplication', // optional property }); ``` @@ -33,7 +31,9 @@ To import an already existing Application: ```ts const application = codedeploy.ServerApplication.fromServerApplicationName( - this, 'ExistingCodeDeployApplication', 'MyExistingApplication' + this, + 'ExistingCodeDeployApplication', + 'MyExistingApplication', ); ``` @@ -42,47 +42,51 @@ const application = codedeploy.ServerApplication.fromServerApplicationName( To create a new CodeDeploy Deployment Group that deploys to EC2/on-premise instances: ```ts +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +declare const application: codedeploy.ServerApplication; +declare const asg: autoscaling.AutoScalingGroup; +declare const alarm: cloudwatch.Alarm; const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDeploymentGroup', { - application, - deploymentGroupName: 'MyDeploymentGroup', - autoScalingGroups: [asg1, asg2], - // adds User Data that installs the CodeDeploy agent on your auto-scaling groups hosts - // default: true - installAgent: true, - // adds EC2 instances matching tags - ec2InstanceTags: new codedeploy.InstanceTagSet( - { - // any instance with tags satisfying - // key1=v1 or key1=v2 or key2 (any value) or value v3 (any key) - // will match this group - 'key1': ['v1', 'v2'], - 'key2': [], - '': ['v3'], - }, - ), - // adds on-premise instances matching tags - onPremiseInstanceTags: new codedeploy.InstanceTagSet( - // only instances with tags (key1=v1 or key1=v2) AND key2=v3 will match this set - { - 'key1': ['v1', 'v2'], - }, - { - 'key2': ['v3'], - }, - ), - // CloudWatch alarms - alarms: [ - new cloudwatch.Alarm(/* ... */), - ], - // whether to ignore failure to fetch the status of alarms from CloudWatch - // default: false - ignorePollAlarmsFailure: false, - // auto-rollback configuration - autoRollback: { - failedDeployment: true, // default: true - stoppedDeployment: true, // default: false - deploymentInAlarm: true, // default: true if you provided any alarms, false otherwise + application, + deploymentGroupName: 'MyDeploymentGroup', + autoScalingGroups: [asg], + // adds User Data that installs the CodeDeploy agent on your auto-scaling groups hosts + // default: true + installAgent: true, + // adds EC2 instances matching tags + ec2InstanceTags: new codedeploy.InstanceTagSet( + { + // any instance with tags satisfying + // key1=v1 or key1=v2 or key2 (any value) or value v3 (any key) + // will match this group + 'key1': ['v1', 'v2'], + 'key2': [], + '': ['v3'], + }, + ), + // adds on-premise instances matching tags + onPremiseInstanceTags: new codedeploy.InstanceTagSet( + // only instances with tags (key1=v1 or key1=v2) AND key2=v3 will match this set + { + 'key1': ['v1', 'v2'], + }, + { + 'key2': ['v3'], }, + ), + // CloudWatch alarms + alarms: [alarm], + // whether to ignore failure to fetch the status of alarms from CloudWatch + // default: false + ignorePollAlarmsFailure: false, + // auto-rollback configuration + autoRollback: { + failedDeployment: true, // default: true + stoppedDeployment: true, // default: false + deploymentInAlarm: true, // default: true if you provided any alarms, false otherwise + }, }); ``` @@ -92,10 +96,14 @@ one will be automatically created. To import an already existing Deployment Group: ```ts -const deploymentGroup = codedeploy.ServerDeploymentGroup.fromLambdaDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', { +declare const application: codedeploy.ServerApplication; +const deploymentGroup = codedeploy.ServerDeploymentGroup.fromServerDeploymentGroupAttributes( + this, + 'ExistingCodeDeployDeploymentGroup', { application, deploymentGroupName: 'MyExistingDeploymentGroup', -}); + }, +); ``` ### Load balancers @@ -108,18 +116,15 @@ with the `loadBalancer` property when creating a Deployment Group. With Classic Elastic Load Balancer, you provide it directly: ```ts -import * as lb from '@aws-cdk/aws-elasticloadbalancing'; +import * as elb from '@aws-cdk/aws-elasticloadbalancing'; -const elb = new lb.LoadBalancer(this, 'ELB', { - // ... -}); -elb.addTarget(/* ... */); -elb.addListener({ - // ... +declare const lb: elb.LoadBalancer; +lb.addListener({ + externalPort: 80, }); const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'DeploymentGroup', { - loadBalancer: codedeploy.LoadBalancer.classic(elb), + loadBalancer: codedeploy.LoadBalancer.classic(lb), }); ``` @@ -127,17 +132,11 @@ With Application Load Balancer or Network Load Balancer, you provide a Target Group as the load balancer: ```ts -import * as lbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -const alb = new lbv2.ApplicationLoadBalancer(this, 'ALB', { - // ... -}); -const listener = alb.addListener('Listener', { - // ... -}); -const targetGroup = listener.addTargets('Fleet', { - // ... -}); +declare const alb: elbv2.ApplicationLoadBalancer; +const listener = alb.addListener('Listener', { port: 80 }); +const targetGroup = listener.addTargets('Fleet', { port: 80 }); const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'DeploymentGroup', { loadBalancer: codedeploy.LoadBalancer.application(targetGroup), @@ -150,7 +149,7 @@ You can also pass a Deployment Configuration when creating the Deployment Group: ```ts const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDeploymentGroup', { - deploymentConfig: codedeploy.ServerDeploymentConfig.ALL_AT_ONCE, + deploymentConfig: codedeploy.ServerDeploymentConfig.ALL_AT_ONCE, }); ``` @@ -160,10 +159,10 @@ You can also create a custom Deployment Configuration: ```ts const deploymentConfig = new codedeploy.ServerDeploymentConfig(this, 'DeploymentConfiguration', { - deploymentConfigName: 'MyDeploymentConfiguration', // optional property - // one of these is required, but both cannot be specified at the same time - minHealthyHostCount: 2, - minHealthyHostPercentage: 75, + deploymentConfigName: 'MyDeploymentConfiguration', // optional property + // one of these is required, but both cannot be specified at the same time + minimumHealthyHosts: codedeploy.MinimumHealthyHosts.count(2), + // minimumHealthyHosts: codedeploy.MinimumHealthyHosts.percentage(75), }); ``` @@ -171,7 +170,9 @@ Or import an existing one: ```ts const deploymentConfig = codedeploy.ServerDeploymentConfig.fromServerDeploymentConfigName( - this, 'ExistingDeploymentConfiguration', 'MyExistingDeploymentConfiguration' + this, + 'ExistingDeploymentConfiguration', + 'MyExistingDeploymentConfiguration', ); ``` @@ -180,10 +181,8 @@ const deploymentConfig = codedeploy.ServerDeploymentConfig.fromServerDeploymentC To create a new CodeDeploy Application that deploys to a Lambda function: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; - const application = new codedeploy.LambdaApplication(this, 'CodeDeployApplication', { - applicationName: 'MyApplication', // optional property + applicationName: 'MyApplication', // optional property }); ``` @@ -191,7 +190,9 @@ To import an already existing Application: ```ts const application = codedeploy.LambdaApplication.fromLambdaApplicationName( - this, 'ExistingCodeDeployApplication', 'MyExistingApplication' + this, + 'ExistingCodeDeployApplication', + 'MyExistingApplication', ); ``` @@ -204,18 +205,15 @@ When you publish a new version of the function to your stack, CodeDeploy will se To create a new CodeDeploy Deployment Group that deploys to a Lambda function: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; -import * as lambda from '@aws-cdk/aws-lambda'; - -const myApplication = new codedeploy.LambdaApplication(..); -const func = new lambda.Function(..); +declare const myApplication: codedeploy.LambdaApplication; +declare const func: lambda.Function; const version = func.addVersion('1'); const version1Alias = new lambda.Alias(this, 'alias', { aliasName: 'prod', - version + version, }); -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { application: myApplication, // optional property: one will be created for you if not provided alias: version1Alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, @@ -237,12 +235,15 @@ 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', { +const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', { type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, interval: Duration.minutes(1), percentage: 5, }); -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { + +declare const application: codedeploy.LambdaApplication; +declare const alias: lambda.Alias; +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { application, alias, deploymentConfig: config, @@ -252,7 +253,7 @@ const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDe 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', { +const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', { type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, interval: Duration.minutes(1), percentage: 5, @@ -265,26 +266,31 @@ const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig' CodeDeploy will roll back if the deployment fails. You can optionally trigger a rollback when one or more alarms are in a failed state: ```ts -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +declare const alias: lambda.Alias; +const alarm = new cloudwatch.Alarm(this, 'Errors', { + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + metric: alias.metricErrors(), +}); +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, alarms: [ // pass some alarms when constructing the deployment group - new cloudwatch.Alarm(stack, 'Errors', { - comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, - threshold: 1, - evaluationPeriods: 1, - metric: alias.metricErrors() - }) - ] + alarm, + ], }); // or add alarms to an existing group -deploymentGroup.addAlarm(new cloudwatch.Alarm(stack, 'BlueGreenErrors', { +declare const blueGreenAlias: lambda.Alias; +deploymentGroup.addAlarm(new cloudwatch.Alarm(this, 'BlueGreenErrors', { comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1, - metric: blueGreenAlias.metricErrors() + metric: blueGreenAlias.metricErrors(), })); ``` @@ -295,18 +301,19 @@ With either hook, you have the opportunity to run logic that determines whether For example, with PreTraffic hook you could run integration tests against the newly created Lambda version (but not serving traffic). With PostTraffic hook, you could run end-to-end validation checks. ```ts -const warmUpUserCache = new lambda.Function(..); -const endToEndValidation = new lambda.Function(..); +declare const warmUpUserCache: lambda.Function; +declare const endToEndValidation: lambda.Function; +declare const alias: lambda.Alias; // pass a hook whe creating the deployment group -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { alias: alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, preHook: warmUpUserCache, }); // or configure one on an existing deployment group -deploymentGroup.onPostHook(endToEndValidation); +deploymentGroup.addPostHook(endToEndValidation); ``` ### Import an existing Deployment Group @@ -314,8 +321,9 @@ deploymentGroup.onPostHook(endToEndValidation); To import an already existing Deployment Group: ```ts -const deploymentGroup = codedeploy.LambdaDeploymentGroup.import(this, 'ExistingCodeDeployDeploymentGroup', { - application, - deploymentGroupName: 'MyExistingDeploymentGroup', +declare const application: codedeploy.LambdaApplication; +const deploymentGroup = codedeploy.LambdaDeploymentGroup.fromLambdaDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', { + application, + deploymentGroupName: 'MyExistingDeploymentGroup', }); ``` diff --git a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts index e8a06374523b3..dc136abb87ee4 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -74,7 +74,7 @@ export class EcsApplication extends Resource implements IEcsApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts index 309bd02ba3647..03449cf00b229 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -74,7 +74,7 @@ export class LambdaApplication extends Resource implements ILambdaApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } 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 0d6c6a347886a..2449ff87f31fd 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -179,7 +179,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy service: 'codedeploy', resource: 'deploymentgroup', resourceName: `${this.application.applicationName}/${this.physicalName}`, - sep: ':', + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, }); if (props.preHook) { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts index 6de47a5fff381..b6f7324ef5985 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -75,7 +75,7 @@ export class ServerApplication extends Resource implements IServerApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 4e1a436ef9b77..f4f3cad0774cc 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -4,6 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; @@ -311,7 +312,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { service: 'codedeploy', resource: 'deploymentgroup', resourceName: `${this.application.applicationName}/${this.physicalName}`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index f8279b5c21e57..52636610a2453 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -80,8 +87,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-autoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..4baee63065a45 --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as codedeploy from '@aws-cdk/aws-codedeploy'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index 0537b469c17d5..0c878278b31bf 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -1,5 +1,5 @@ import { Grant, IGrantable } from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnProfilingGroup } from './codeguruprofiler.generated'; @@ -154,7 +154,7 @@ export class ProfilingGroup extends ProfilingGroupBase { */ public static fromProfilingGroupArn(scope: Construct, id: string, profilingGroupArn: string): IProfilingGroup { class Import extends ProfilingGroupBase { - public readonly profilingGroupName = Stack.of(scope).parseArn(profilingGroupArn).resource; + public readonly profilingGroupName = Stack.of(scope).splitArn(profilingGroupArn, ArnFormat.SLASH_RESOURCE_NAME).resource; public readonly profilingGroupArn = profilingGroupArn; } diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index 247e4696080fe..d5da8cc3123b0 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-codegurureviewer/package.json b/packages/@aws-cdk/aws-codegurureviewer/package.json index 1ea8076d375ee..70318450a5a94 100644 --- a/packages/@aws-cdk/aws-codegurureviewer/package.json +++ b/packages/@aws-cdk/aws-codegurureviewer/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index e57381c517c1f..7625ed08bfb8e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -11,7 +11,7 @@ This package contains Actions that can be used in a CodePipeline. -```ts +```ts nofixture import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; ``` @@ -23,10 +23,8 @@ import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; To use a CodeCommit Repository in a CodePipeline: ```ts -import * as codecommit from '@aws-cdk/aws-codecommit'; - const repo = new codecommit.Repository(this, 'Repo', { - // ... + repositoryName: 'MyRepo', }); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { @@ -49,6 +47,7 @@ You can specify the role object in eventRole property. ```ts const eventRole = iam.Role.fromRoleArn(this, 'Event-role', 'roleArn'); +declare const repo: codecommit.Repository; const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ actionName: 'CodeCommit', repository: repo, @@ -61,6 +60,8 @@ If you want to clone the entire CodeCommit repository (only available for CodeBu you can set the `codeBuildCloneOutput` property to `true`: ```ts +declare const project: codebuild.PipelineProject; +declare const repo: codecommit.Repository; const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ actionName: 'CodeCommit', @@ -80,15 +81,22 @@ const buildAction = new codepipeline_actions.CodeBuildAction({ The CodeCommit source action emits variables: ```ts +declare const project: codebuild.PipelineProject; +declare const repo: codecommit.Repository; +const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ - // ... + actionName: 'CodeCommit', + repository: repo, + output: sourceOutput, variablesNamespace: 'MyNamespace', // optional - by default, a name will be generated for you }); // later: new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { COMMIT_ID: { value: sourceAction.variables.commitId, @@ -107,20 +115,21 @@ If you want to use a GitHub repository as the source, you must create: with the value of the **GitHub Access Token**. Pick whatever name you want (for example `my-github-token`). This token can be stored either as Plaintext or as a Secret key/value. If you stored the token as Plaintext, - set `cdk.SecretValue.secretsManager('my-github-token')` as the value of `oauthToken`. + set `SecretValue.secretsManager('my-github-token')` as the value of `oauthToken`. If you stored it as a Secret key/value, - you must set `cdk.SecretValue.secretsManager('my-github-token', { jsonField : 'my-github-token' })` as the value of `oauthToken`. + you must set `SecretValue.secretsManager('my-github-token', { jsonField : 'my-github-token' })` as the value of `oauthToken`. To use GitHub as the source of a CodePipeline: ```ts // Read the secret from Secrets Manager +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.GitHubSourceAction({ actionName: 'GitHub_Source', owner: 'awslabs', repo: 'aws-cdk', - oauthToken: cdk.SecretValue.secretsManager('my-github-token'), + oauthToken: SecretValue.secretsManager('my-github-token'), output: sourceOutput, branch: 'develop', // default: 'master' }); @@ -133,15 +142,24 @@ pipeline.addStage({ The GitHub source action emits variables: ```ts +declare const sourceOutput: codepipeline.Artifact; +declare const project: codebuild.PipelineProject; + const sourceAction = new codepipeline_actions.GitHubSourceAction({ - // ... + actionName: 'Github_Source', + output: sourceOutput, + owner: 'my-owner', + repo: 'my-repo', + oauthToken: SecretValue.secretsManager('my-github-token'), variablesNamespace: 'MyNamespace', // optional - by default, a name will be generated for you }); // later: new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { COMMIT_URL: { value: sourceAction.variables.commitUrl, @@ -185,8 +203,6 @@ You can also use the `CodeStarConnectionsSourceAction` to connect to GitHub, in To use an S3 Bucket as a source in CodePipeline: ```ts -import * as s3 from '@aws-cdk/aws-s3'; - const sourceBucket = new s3.Bucket(this, 'MyBucket', { versioned: true, // a Bucket used as a source in CodePipeline must be versioned }); @@ -229,6 +245,8 @@ You can do it through the CDK: ```ts import * as cloudtrail from '@aws-cdk/aws-cloudtrail'; +declare const sourceBucket: s3.Bucket; +const sourceOutput = new codepipeline.Artifact(); const key = 'some/key.zip'; const trail = new cloudtrail.Trail(this, 'CloudTrail'); trail.addS3EventSelector([{ @@ -249,15 +267,23 @@ const sourceAction = new codepipeline_actions.S3SourceAction({ The S3 source action emits variables: ```ts +const key = 'some/key.zip'; +declare const sourceBucket: s3.Bucket; +const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.S3SourceAction({ - // ... + actionName: 'S3Source', + bucketKey: key, + bucket: sourceBucket, + output: sourceOutput, variablesNamespace: 'MyNamespace', // optional - by default, a name will be generated for you }); // later: - +declare const project: codebuild.PipelineProject; new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { VERSION_ID: { value: sourceAction.variables.versionId, @@ -273,6 +299,7 @@ To use an ECR Repository as a source in a Pipeline: ```ts import * as ecr from '@aws-cdk/aws-ecr'; +declare const ecrRepository: ecr.Repository; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.EcrSourceAction({ @@ -290,15 +317,23 @@ pipeline.addStage({ The ECR source action emits variables: ```ts +import * as ecr from '@aws-cdk/aws-ecr'; + +const sourceOutput = new codepipeline.Artifact(); +declare const ecrRepository: ecr.Repository; const sourceAction = new codepipeline_actions.EcrSourceAction({ - // ... + actionName: 'Source', + output: sourceOutput, + repository: ecrRepository, variablesNamespace: 'MyNamespace', // optional - by default, a name will be generated for you }); // later: - +declare const project: codebuild.PipelineProject; new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { IMAGE_URI: { value: sourceAction.variables.imageUri, @@ -314,9 +349,7 @@ new codepipeline_actions.CodeBuildAction({ Example of a CodeBuild Project used in a Pipeline, alongside CodeCommit: ```ts -import * as codebuild from '@aws-cdk/aws-codebuild'; -import * as codecommit from '@aws-cdk/aws-codecommit'; - +declare const project: codebuild.PipelineProject; const repository = new codecommit.Repository(this, 'MyRepository', { repositoryName: 'MyRepository', }); @@ -356,6 +389,8 @@ if you want a `Test` Action instead, override the `type` property: ```ts +declare const project: codebuild.PipelineProject; +const sourceOutput = new codepipeline.Artifact(); const testAction = new codepipeline_actions.CodeBuildAction({ actionName: 'IntegrationTest', project, @@ -373,12 +408,14 @@ properties of the `Project` class, you need to use the `extraInputs` and Actions. Example: ```ts +declare const repository1: codecommit.Repository; const sourceOutput1 = new codepipeline.Artifact(); const sourceAction1 = new codepipeline_actions.CodeCommitSourceAction({ actionName: 'Source1', repository: repository1, output: sourceOutput1, }); +declare const repository2: codecommit.Repository; const sourceOutput2 = new codepipeline.Artifact('source2'); const sourceAction2 = new codepipeline_actions.CodeCommitSourceAction({ actionName: 'Source2', @@ -386,6 +423,7 @@ const sourceAction2 = new codepipeline_actions.CodeCommitSourceAction({ output: sourceOutput2, }); +declare const project: codebuild.PipelineProject; const buildAction = new codepipeline_actions.CodeBuildAction({ actionName: 'Build', project, @@ -447,6 +485,7 @@ in the 'exported-variables' subsection of the 'env' section. Example: ```ts +const sourceOutput = new codepipeline.Artifact(); const buildAction = new codepipeline_actions.CodeBuildAction({ actionName: 'Build1', input: sourceOutput, @@ -469,9 +508,11 @@ const buildAction = new codepipeline_actions.CodeBuildAction({ }); // later: - +declare const project: codebuild.PipelineProject; new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { MyVar: { value: buildAction.variable('MY_VAR'), @@ -498,7 +539,7 @@ or outside the CDK (in the CodePipeline AWS Console, for example), you can import it: ```ts -const jenkinsProvider = codepipeline_actions.JenkinsProvider.import(this, 'JenkinsProvider', { +const jenkinsProvider = codepipeline_actions.JenkinsProvider.fromJenkinsProviderAttributes(this, 'JenkinsProvider', { providerName: 'MyJenkinsProvider', serverUrl: 'http://my-jenkins.com:8080', version: '2', // optional, default: '1' @@ -514,6 +555,7 @@ With a `JenkinsProvider`, we can create a Jenkins Action: ```ts +declare const jenkinsProvider: codepipeline_actions.JenkinsProvider; const buildAction = new codepipeline_actions.JenkinsAction({ actionName: 'JenkinsBuild', jenkinsProvider: jenkinsProvider, @@ -566,8 +608,12 @@ If you want to update stacks in a different account, pass the `account` property when creating the action: ```ts +const sourceOutput = new codepipeline.Artifact(); new codepipeline_actions.CloudFormationCreateUpdateStackAction({ - // ... + actionName: 'CloudFormationCreateUpdate', + stackName: 'MyStackName', + adminPermissions: true, + templatePath: sourceOutput.atPath('template.yaml'), account: '123456789012', }); ``` @@ -584,15 +630,20 @@ and the action will operate in the same account the role belongs to: import { PhysicalName } from '@aws-cdk/core'; // in stack for account 123456789012... +declare const otherAccountStack: Stack; const actionRole = new iam.Role(otherAccountStack, 'ActionRole', { - assumedBy: new iam.AccountPrincipal(pipelineAccount), + assumedBy: new iam.AccountPrincipal('123456789012'), // the role has to have a physical name set roleName: PhysicalName.GENERATE_IF_NEEDED, }); // in the pipeline stack... +const sourceOutput = new codepipeline.Artifact(); new codepipeline_actions.CloudFormationCreateUpdateStackAction({ - // ... + actionName: 'CloudFormationCreateUpdate', + stackName: 'MyStackName', + adminPermissions: true, + templatePath: sourceOutput.atPath('template.yaml'), role: actionRole, // this action will be cross-account as well }); ``` @@ -604,14 +655,13 @@ new codepipeline_actions.CloudFormationCreateUpdateStackAction({ To use CodeDeploy for EC2/on-premise deployments in a Pipeline: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; - const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { pipelineName: 'MyPipeline', }); // add the source and build Stages to the Pipeline... - +const buildOutput = new codepipeline.Artifact(); +declare const deploymentGroup: codedeploy.ServerDeploymentGroup; const deployAction = new codepipeline_actions.CodeDeployServerDeployAction({ actionName: 'CodeDeploy', input: buildOutput, @@ -629,19 +679,19 @@ To use CodeDeploy for blue-green Lambda deployments in a Pipeline: ```ts const lambdaCode = lambda.Code.fromCfnParameters(); -const func = new lambda.Function(lambdaStack, 'Lambda', { +const func = new lambda.Function(this, 'Lambda', { code: lambdaCode, handler: 'index.handler', runtime: lambda.Runtime.NODEJS_12_X, }); // used to make sure each CDK synthesis produces a different Version const version = func.addVersion('NewVersion'); -const alias = new lambda.Alias(lambdaStack, 'LambdaAlias', { +const alias = new lambda.Alias(this, 'LambdaAlias', { aliasName: 'Prod', version, }); -new codedeploy.LambdaDeploymentGroup(lambdaStack, 'DeploymentGroup', { +new codedeploy.LambdaDeploymentGroup(this, 'DeploymentGroup', { alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, }); @@ -658,6 +708,11 @@ CodePipeline can deploy an ECS service. The deploy Action receives one input Artifact which contains the [image definition file]: ```ts +import * as ecs from '@aws-cdk/aws-ecs'; + +declare const service: ecs.FargateService; +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const buildOutput = new codepipeline.Artifact(); const deployStage = pipeline.addStage({ stageName: 'Deploy', actions: [ @@ -672,7 +727,7 @@ const deployStage = pipeline.addStage({ // use the `imageFile` property, // and leave out the `input` property imageFile: buildOutput.atPath('imageDef.json'), - deploymentTimeout: cdk.Duration.minutes(60), // optional, default is 60 minutes + deploymentTimeout: Duration.minutes(60), // optional, default is 60 minutes }), ], }); @@ -697,12 +752,12 @@ Here's an example: To use an S3 Bucket as a deployment target in CodePipeline: ```ts -const targetBucket = new s3.Bucket(this, 'MyBucket', {}); +const sourceOutput = new codepipeline.Artifact(); +const targetBucket = new s3.Bucket(this, 'MyBucket'); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const deployAction = new codepipeline_actions.S3DeployAction({ actionName: 'S3Deploy', - stage: deployStage, bucket: targetBucket, input: sourceOutput, }); @@ -720,9 +775,8 @@ and use the AWS CLI to invalidate the cache: ```ts // Create a Cloudfront Web Distribution -const distribution = new cloudfront.Distribution(this, `Distribution`, { - // ... -}); +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +declare const distribution: cloudfront.Distribution; // Create the build project that will invalidate the cache const invalidateBuildProject = new codebuild.PipelineProject(this, `InvalidateProject`, { @@ -752,19 +806,21 @@ invalidateBuildProject.addToRolePolicy(new iam.PolicyStatement({ })); // Create the pipeline (here only the S3 deploy and Invalidate cache build) +const deployBucket = new s3.Bucket(this, 'DeployBucket'); +const deployInput = new codepipeline.Artifact(); new codepipeline.Pipeline(this, 'Pipeline', { stages: [ // ... { stageName: 'Deploy', actions: [ - new codepipelineActions.S3DeployAction({ + new codepipeline_actions.S3DeployAction({ actionName: 'S3Deploy', bucket: deployBucket, input: deployInput, runOrder: 1, }), - new codepipelineActions.CodeBuildAction({ + new codepipeline_actions.CodeBuildAction({ actionName: 'InvalidateCache', project: invalidateBuildProject, input: deployInput, @@ -782,11 +838,12 @@ You can deploy to Alexa using CodePipeline with the following Action: ```ts // Read the secrets from ParameterStore -const clientId = cdk.SecretValue.secretsManager('AlexaClientId'); -const clientSecret = cdk.SecretValue.secretsManager('AlexaClientSecret'); -const refreshToken = cdk.SecretValue.secretsManager('AlexaRefreshToken'); +const clientId = SecretValue.secretsManager('AlexaClientId'); +const clientSecret = SecretValue.secretsManager('AlexaClientSecret'); +const refreshToken = SecretValue.secretsManager('AlexaRefreshToken'); // Add deploy action +const sourceOutput = new codepipeline.Artifact(); new codepipeline_actions.AlexaSkillDeployAction({ actionName: 'DeploySkill', runOrder: 1, @@ -801,20 +858,22 @@ new codepipeline_actions.AlexaSkillDeployAction({ If you need manifest overrides you can specify them as `parameterOverridesArtifact` in the action: ```ts -import * as cloudformation from '@aws-cdk/aws-cloudformation'; - // Deploy some CFN change set and store output const executeOutput = new codepipeline.Artifact('CloudFormation'); const executeChangeSetAction = new codepipeline_actions.CloudFormationExecuteChangeSetAction({ actionName: 'ExecuteChangesTest', runOrder: 2, - stackName, - changeSetName, + stackName: 'MyStack', + changeSetName: 'MyChangeSet', outputFileName: 'overrides.json', output: executeOutput, }); // Provide CFN output as manifest overrides +const clientId = SecretValue.secretsManager('AlexaClientId'); +const clientSecret = SecretValue.secretsManager('AlexaClientSecret'); +const refreshToken = SecretValue.secretsManager('AlexaRefreshToken'); +const sourceOutput = new codepipeline.Artifact(); new codepipeline_actions.AlexaSkillDeployAction({ actionName: 'DeploySkill', runOrder: 1, @@ -832,11 +891,11 @@ new codepipeline_actions.AlexaSkillDeployAction({ You can deploy a CloudFormation template to an existing Service Catalog product with the following Action: ```ts +const cdkBuildOutput = new codepipeline.Artifact(); const serviceCatalogDeployAction = new codepipeline_actions.ServiceCatalogDeployActionBeta1({ actionName: 'ServiceCatalogDeploy', templatePath: cdkBuildOutput.atPath("Sample.template.json"), productVersionName: "Version - " + Date.now.toString, - productType: "CLOUD_FORMATION_TEMPLATE", productVersionDescription: "This is a version from the pipeline with a new description.", productId: "prod-XXXXXXXX", }); @@ -849,6 +908,10 @@ const serviceCatalogDeployAction = new codepipeline_actions.ServiceCatalogDeploy This package contains an Action that stops the Pipeline until someone manually clicks the approve button: ```ts +import * as sns from '@aws-cdk/aws-sns'; + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const approveStage = pipeline.addStage({ stageName: 'Approve' }); const manualApprovalAction = new codepipeline_actions.ManualApprovalAction({ actionName: 'Approve', notificationTopic: new sns.Topic(this, 'Topic'), // optional @@ -871,12 +934,14 @@ If you want to grant a principal permissions to approve the changes, you can invoke the method `grantManualApproval` passing it a `IGrantable`: ```ts +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const approveStage = pipeline.addStage({ stageName: 'Approve' }); const manualApprovalAction = new codepipeline_actions.ManualApprovalAction({ actionName: 'Approve', }); approveStage.addAction(manualApprovalAction); -const role = iam.Role.fromRoleArn(this, 'Admin', Arn.format({ service: 'iam', resource: 'role', resourceName: 'Admin' }, stack)); +const role = iam.Role.fromRoleArn(this, 'Admin', Arn.format({ service: 'iam', resource: 'role', resourceName: 'Admin' }, this)); manualApprovalAction.grantManualApproval(role); ``` @@ -885,8 +950,7 @@ manualApprovalAction.grantManualApproval(role); This module contains an Action that allows you to invoke a Lambda function in a Pipeline: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; - +declare const fn: lambda.Function; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ actionName: 'Lambda', @@ -902,7 +966,9 @@ The Lambda Action can have up to 5 inputs, and up to 5 outputs: ```ts - +declare const fn: lambda.Function; +const sourceOutput = new codepipeline.Artifact(); +const buildOutput = new codepipeline.Artifact(); const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ actionName: 'Lambda', inputs: [ @@ -913,7 +979,26 @@ const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ new codepipeline.Artifact('Out1'), new codepipeline.Artifact('Out2'), ], - lambda: fn + lambda: fn, +}); +``` + +The Lambda Action supports custom user parameters that pipeline +will pass to the Lambda function: + +```ts +declare const fn: lambda.Function; + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ + actionName: 'Lambda', + lambda: fn, + userParameters: { + foo: 'bar', + baz: 'qux', + }, + // OR + userParametersString: 'my-parameter-string', }); ``` @@ -924,8 +1009,6 @@ API with the `outputVariables` property filled with the map of variables Example: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; - const lambdaInvokeAction = new codepipeline_actions.LambdaInvokeAction({ actionName: 'Lambda', lambda: new lambda.Function(this, 'Func', { @@ -949,9 +1032,12 @@ const lambdaInvokeAction = new codepipeline_actions.LambdaInvokeAction({ }); // later: - +declare const project: codebuild.PipelineProject; +const sourceOutput = new codepipeline.Artifact(); new codepipeline_actions.CodeBuildAction({ - // ... + actionName: 'CodeBuild', + project, + input: sourceOutput, environmentVariables: { MyVar: { value: lambdaInvokeAction.variable('MY_VAR'), @@ -968,14 +1054,13 @@ on how to write a Lambda function invoked from CodePipeline. This module contains an Action that allows you to invoke a Step Function in a Pipeline: ```ts -import * as stepfunction from '@aws-cdk/aws-stepfunctions'; - +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const startState = new stepfunction.Pass(stack, 'StartState'); -const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { +const startState = new stepfunctions.Pass(this, 'StartState'); +const simpleStateMachine = new stepfunctions.StateMachine(this, 'SimpleStateMachine', { definition: startState, }); -const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ +const stepFunctionAction = new codepipeline_actions.StepFunctionInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, stateMachineInput: codepipeline_actions.StateMachineInput.literal({ IsHelloWorldExample: true }), @@ -990,15 +1075,15 @@ The `StateMachineInput` can be created with one of 2 static factory methods: `literal`, which takes an arbitrary map as its only argument, or `filePath`: ```ts -import * as stepfunction from '@aws-cdk/aws-stepfunctions'; +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const inputArtifact = new codepipeline.Artifact(); -const startState = new stepfunction.Pass(stack, 'StartState'); -const simpleStateMachine = new stepfunction.StateMachine(stack, 'SimpleStateMachine', { +const startState = new stepfunctions.Pass(this, 'StartState'); +const simpleStateMachine = new stepfunctions.StateMachine(this, 'SimpleStateMachine', { definition: startState, }); -const stepFunctionAction = new codepipeline_actions.StepFunctionsInvokeAction({ +const stepFunctionAction = new codepipeline_actions.StepFunctionInvokeAction({ actionName: 'Invoke', stateMachine: simpleStateMachine, stateMachineInput: codepipeline_actions.StateMachineInput.filePath(inputArtifact.atPath('assets/input.json')), diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts index 254b2764f2684..e861161ab39c1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts @@ -18,3 +18,4 @@ export * from './s3/source-action'; export * from './stepfunctions/invoke-action'; export * from './servicecatalog/deploy-action-beta1'; export * from './action'; + diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts index 923b1a1e39e7c..6e0d179859916 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts @@ -1,7 +1,6 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { CustomActionRegistration } from '../custom-action-registration'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -190,7 +189,7 @@ export class JenkinsProvider extends BaseJenkinsProvider { } private registerJenkinsCustomAction(id: string, category: codepipeline.ActionCategory) { - new CustomActionRegistration(this, id, { + new codepipeline.CustomActionRegistration(this, id, { category, artifactBounds: jenkinsArtifactsBounds, provider: this.providerName, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index f8a49d977f6b4..8740d8fafb9ff 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -38,10 +38,24 @@ export interface LambdaInvokeActionProps extends codepipeline.CommonAwsActionPro * A set of key-value pairs that will be accessible to the invoked Lambda * inside the event that the Pipeline will call it with. * + * Only one of `userParameters` or `userParametersString` can be specified. + * * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example + * @default - no user parameters will be passed */ readonly userParameters?: { [key: string]: any }; + /** + * The string representation of the user parameters that will be + * accessible to the invoked Lambda inside the event + * that the Pipeline will call it with. + * + * Only one of `userParametersString` or `userParameters` can be specified. + * + * @default - no user parameters will be passed + */ + readonly userParametersString?: string; + /** * The lambda function to invoke. */ @@ -71,6 +85,10 @@ export class LambdaInvokeAction extends Action { }); this.props = props; + + if (props.userParameters && props.userParametersString) { + throw new Error('Only one of userParameters or userParametersString can be specified'); + } } /** @@ -121,7 +139,7 @@ export class LambdaInvokeAction extends Action { return { configuration: { FunctionName: this.props.lambda.functionName, - UserParameters: Stack.of(scope).toJsonString(this.props.userParameters), + UserParameters: this.props.userParametersString ?? Stack.of(scope).toJsonString(this.props.userParameters), }, }; } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts index 34b055c14735b..302b82b2a8725 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts @@ -35,12 +35,8 @@ export interface ServiceCatalogDeployActionBeta1Props extends codepipeline.Commo /** * CodePipeline action to connect to an existing ServiceCatalog product. -<<<<<<< HEAD:packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts * * **Note**: this class is still experimental, and may have breaking changes in the future! - * -======= ->>>>>>> master:packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts */ export class ServiceCatalogDeployActionBeta1 extends Action { private readonly templatePath: string; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index bf6c34ab505a8..2181f198ca012 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -128,19 +128,19 @@ export class StepFunctionInvokeAction extends Action { protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { // allow pipeline to invoke this step function - options.role.addToPolicy(new iam.PolicyStatement({ + options.role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['states:StartExecution', 'states:DescribeStateMachine'], resources: [this.props.stateMachine.stateMachineArn], })); // allow state machine executions to be inspected - options.role.addToPolicy(new iam.PolicyStatement({ + options.role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['states:DescribeExecution'], resources: [cdk.Stack.of(this.props.stateMachine).formatArn({ service: 'states', resource: 'execution', - resourceName: `${cdk.Stack.of(this.props.stateMachine).parseArn(this.props.stateMachine.stateMachineArn, ':').resourceName}:${this.props.executionNamePrefix ?? ''}*`, - sep: ':', + resourceName: `${cdk.Stack.of(this.props.stateMachine).splitArn(this.props.stateMachine.stateMachineArn, cdk.ArnFormat.COLON_RESOURCE_NAME).resourceName}:${this.props.executionNamePrefix ?? ''}*`, + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, })], })); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index 6f08ecc470c28..aa3c6a125a362 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -75,9 +82,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "@types/lodash": "^4.14.175", - "jest": "^26.6.3", + "@types/jest": "^27.0.2", + "@types/lodash": "^4.14.177", + "jest": "^27.3.1", "lodash": "^4.17.21" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-codepipeline-actions/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..aba6086a1683e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import { Arn, Construct, Duration, SecretValue, Stack } from '@aws-cdk/core'; +import codebuild = require('@aws-cdk/aws-codebuild'); +import codedeploy = require('@aws-cdk/aws-codedeploy'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import codepipeline_actions = require('@aws-cdk/aws-codepipeline-actions'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); +import s3 = require('@aws-cdk/aws-s3'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts index a4767fa1a61bc..4e3fd6045a251 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts @@ -2,12 +2,13 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as cpactions from '../../lib'; /* eslint-disable quote-props */ -describe('BitBucket source Action', () => { +describeDeprecated('BitBucket source Action', () => { describe('BitBucket source Action', () => { test('produces the correct configuration when added to a pipeline', () => { const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts index e631dd6b10c1b..3f9a337333915 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts @@ -124,9 +124,7 @@ pipeline.addStage({ templatePath: cdkBuildOutput.atPath('LambdaStack.template.yaml'), stackName: 'LambdaStackDeployedName', adminPermissions: true, - parameterOverrides: { - ...lambdaCode.assign(lambdaBuildOutput.s3Location), - }, + parameterOverrides: lambdaCode.assign(lambdaBuildOutput.s3Location), extraInputs: [ lambdaBuildOutput, ], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/lambda-invoke-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/lambda-invoke-action.test.ts index c9c694b6cedaf..41d1b13563235 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/lambda-invoke-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/lambda-invoke-action.test.ts @@ -3,9 +3,9 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { App, Aws, Lazy, SecretValue, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as cpactions from '../../lib'; /* eslint-disable quote-props */ @@ -100,6 +100,36 @@ describe('', () => { }); + test('properly assings userParametersString to UserParameters', () => { + const stack = stackIncludingLambdaInvokeCodePipeline({ + userParamsString: '**/*.template.json', + }); + + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + {}, + { + 'Actions': [ + { + 'Configuration': { + 'UserParameters': '**/*.template.json', + }, + }, + ], + }, + ], + }); + }); + + test('throws if both userParameters and userParametersString are supplied', () => { + expect(() => stackIncludingLambdaInvokeCodePipeline({ + userParams: { + key: Token.asString(null), + }, + userParamsString: '**/*.template.json', + })).toThrow(/Only one of userParameters or userParametersString can be specified/); + }); + test("assigns the Action's Role with read permissions to the Bucket if it has only inputs", () => { const stack = stackIncludingLambdaInvokeCodePipeline({ lambdaInput: new codepipeline.Artifact(), @@ -302,6 +332,7 @@ describe('', () => { interface HelperProps { readonly userParams?: { [key: string]: any }; + readonly userParamsString?: string; readonly lambdaInput?: codepipeline.Artifact; readonly lambdaOutput?: codepipeline.Artifact; } @@ -334,6 +365,7 @@ function stackIncludingLambdaInvokeCodePipeline(props: HelperProps, app?: App) { runtime: lambda.Runtime.NODEJS_10_X, }), userParameters: props.userParams, + userParametersString: props.userParamsString, inputs: props.lambdaInput ? [props.lambdaInput] : undefined, outputs: props.lambdaOutput ? [props.lambdaOutput] : undefined, }), diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 23f86b9293e92..b9e5e3954980e 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -16,14 +16,14 @@ To construct an empty Pipeline: ```ts -import * as codepipeline from '@aws-cdk/aws-codepipeline'; - +// Construct an empty Pipeline const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline'); ``` To give the Pipeline a nice, human-readable name: ```ts +// Give the Pipeline a nice, human-readable name const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { pipelineName: 'MyPipeline', }); @@ -40,6 +40,7 @@ the creation of the Customer Master Keys by passing `crossAccountKeys: false` when defining the Pipeline: ```ts +// Don't create Customer Master Keys const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { crossAccountKeys: false, }); @@ -50,6 +51,7 @@ you can configure it by passing `enableKeyRotation: true` when creating the pipe Note that key rotation will incur an additional cost of **$1/month**. ```ts +// Enable key rotation for the generated KMS key const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { // ... enableKeyRotation: true, @@ -61,6 +63,7 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { You can provide Stages when creating the Pipeline: ```ts +// Provide a Stage when creating a pipeline const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { stages: [ { @@ -76,6 +79,8 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { Or append a Stage to an existing Pipeline: ```ts +// Append a Stage to an existing Pipeline +declare const pipeline: codepipeline.Pipeline; const sourceStage = pipeline.addStage({ stageName: 'Source', actions: [ // optional property @@ -87,12 +92,17 @@ const sourceStage = pipeline.addStage({ You can insert the new Stage at an arbitrary point in the Pipeline: ```ts +// Insert a new Stage at an arbitrary point +declare const pipeline: codepipeline.Pipeline; +declare const anotherStage: codepipeline.IStage; +declare const yetAnotherStage: codepipeline.IStage; + const someStage = pipeline.addStage({ stageName: 'SomeStage', placement: { // note: you can only specify one of the below properties rightBefore: anotherStage, - justAfter: anotherStage + justAfter: yetAnotherStage, } }); ``` @@ -106,9 +116,48 @@ in the `actions` property, or you can use the `IStage.addAction()` method to mutate an existing Stage: ```ts +// Use the `IStage.addAction()` method to mutate an existing Stage. +declare const sourceStage: codepipeline.IStage; +declare const someAction: codepipeline.Action; sourceStage.addAction(someAction); ``` +## Custom Action Registration + +To make your own custom CodePipeline Action requires registering the action provider. Look to the `JenkinsProvider` in `@aws-cdk/aws-codepipeline-actions` for an implementation example. + +```ts +// Make a custom CodePipeline Action +new codepipeline.CustomActionRegistration(this, 'GenericGitSourceProviderResource', { + category: codepipeline.ActionCategory.SOURCE, + artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 1 }, + provider: 'GenericGitSource', + version: '1', + entityUrl: 'https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-create-custom-action.html', + executionUrl: 'https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-create-custom-action.html', + actionProperties: [ + { + name: 'Branch', + required: true, + key: false, + secret: false, + queryable: false, + description: 'Git branch to pull', + type: 'String', + }, + { + name: 'GitUrl', + required: true, + key: false, + secret: false, + queryable: false, + description: 'SSH git clone URL', + type: 'String', + }, + ], +}); +``` + ## Cross-account CodePipelines > Cross-account Pipeline actions require that the Pipeline has *not* been @@ -129,11 +178,16 @@ example, the following action deploys to an imported S3 bucket from a different account: ```ts +// Deploy an imported S3 bucket from a different account +declare const stage: codepipeline.IStage; +declare const input: codepipeline.Artifact; stage.addAction(new codepipeline_actions.S3DeployAction({ bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { account: '123456789012', // ... }), + input: input, + actionName: 's3-deploy-action', // ... })); ``` @@ -141,8 +195,15 @@ stage.addAction(new codepipeline_actions.S3DeployAction({ Actions that don't accept a resource object accept an explicit `account` parameter: ```ts +// Actions that don't accept a resource objet accept an explicit `account` parameter +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ account: '123456789012', + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... })); ``` @@ -158,7 +219,14 @@ If you do not want to use the generated role, you can also explicitly pass a account the role belongs to: ```ts +// Explicitly pass in a `role` when creating an action. +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... role: iam.Role.fromRoleArn(this, 'ActionRole', '...'), })); @@ -171,11 +239,16 @@ pass to actions can also be in different *Regions*. For example, the following Action deploys to an imported S3 bucket from a different Region: ```ts +// Deploy to an imported S3 bucket from a different Region. +declare const stage: codepipeline.IStage; +declare const input: codepipeline.Artifact; stage.addAction(new codepipeline_actions.S3DeployAction({ bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { region: 'us-west-1', // ... }), + input: input, + actionName: 's3-deploy-action', // ... })); ``` @@ -184,7 +257,14 @@ Actions that don't take an AWS resource will accept an explicit `region` parameter: ```ts +// Actions that don't take an AWS resource will accept an explicit `region` parameter. +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... region: 'us-west-1', })); @@ -201,6 +281,7 @@ place to serve as replication buckets, you can supply these at Pipeline definiti time using the `crossRegionReplicationBuckets` parameter. Example: ```ts +// Supply replication buckets for the Pipeline instead of using the generated support stack const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { // ... @@ -226,6 +307,8 @@ If you're passing a replication bucket created in a different stack, like this: ```ts +// Passing a replication bucket created in a different stack. +const app = new App(); const replicationStack = new Stack(app, 'ReplicationStack', { env: { region: 'us-west-1', @@ -239,7 +322,7 @@ const replicationBucket = new s3.Bucket(replicationStack, 'ReplicationBucket', { }); // later... -new codepipeline.Pipeline(pipelineStack, 'Pipeline', { +new codepipeline.Pipeline(replicationStack, 'Pipeline', { crossRegionReplicationBuckets: { 'us-west-1': replicationBucket, }, @@ -255,6 +338,13 @@ and so you can't reference them across environments. In this case, you need to use an alias in place of the key when creating the bucket: ```ts +// Passing an encrypted replication bucket created in a different stack. +const app = new App(); +const replicationStack = new Stack(app, 'ReplicationStack', { + env: { + region: 'us-west-1', + }, +}); const key = new kms.Key(replicationStack, 'ReplicationKey'); const alias = new kms.Alias(replicationStack, 'ReplicationAlias', { // aliasName is required @@ -278,14 +368,16 @@ you access the appropriate property of the interface returned from `variables`, which represents a single variable. Example: -```ts -// MyAction is some action type that produces variables +```ts fixture=action +// MyAction is some action type that produces variables, like EcrSourceAction const myAction = new MyAction({ // ... + actionName: 'myAction', }); new OtherAction({ // ... config: myAction.variables.myVariable, + actionName: 'otherAction', }); ``` @@ -293,10 +385,12 @@ The namespace name that will be used will be automatically generated by the pipe based on the stage and action name; you can pass a custom name when creating the action instance: -```ts +```ts fixture=action +// MyAction is some action type that produces variables, like EcrSourceAction const myAction = new MyAction({ // ... variablesNamespace: 'MyNamespace', + actionName: 'myAction', }); ``` @@ -304,10 +398,12 @@ There are also global variables available, not tied to any action; these are accessed through static properties of the `GlobalVariables` class: -```ts +```ts fixture=action +// OtherAction is some action type that produces variables, like EcrSourceAction new OtherAction({ // ... config: codepipeline.GlobalVariables.executionId, + actionName: 'otherAction', }); ``` @@ -324,6 +420,7 @@ for more details on how to use the variables feature. A pipeline can be used as a target for a CloudWatch event rule: ```ts +// A pipeline being used as a target for a CloudWatch event rule. import * as targets from '@aws-cdk/aws-events-targets'; import * as events from '@aws-cdk/aws-events'; @@ -332,6 +429,7 @@ const rule = new events.Rule(this, 'Daily', { schedule: events.Schedule.rate(Duration.days(1)), }); +declare const pipeline: codepipeline.Pipeline; rule.addTarget(new targets.CodePipeline(pipeline)); ``` @@ -346,7 +444,14 @@ the pipeline, stages or action, use the `onXxx` methods on the respective construct: ```ts -myPipeline.onStateChange('MyPipelineStateChange', target); +// Define event rules for events emitted by the pipeline +import * as events from '@aws-cdk/aws-events'; + +declare const myPipeline: codepipeline.Pipeline; +declare const myStage: codepipeline.IStage; +declare const myAction: codepipeline.Action; +declare const target: events.IRuleTarget; +myPipeline.onStateChange('MyPipelineStateChange', { target: target } ); myStage.onStateChange('MyStageStateChange', target); myAction.onStateChange('MyActionStateChange', target); ``` @@ -357,11 +462,14 @@ To define CodeStar Notification rules for Pipelines, use one of the `notifyOnXxx They are very similar to `onXxx()` methods for CloudWatch events: ```ts -const target = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { +// Define CodeStar Notification rules for Pipelines +import * as chatbot from '@aws-cdk/aws-chatbot'; +const target = new chatbot.SlackChannelConfiguration(this, 'MySlackChannel', { slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', slackChannelId: 'YOUR_SLACK_CHANNEL_ID', }); +declare const pipeline: codepipeline.Pipeline; const rule = pipeline.notifyOnExecutionStateChange('NotifyOnExecutionStateChange', target); ``` diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts similarity index 76% rename from packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts rename to packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts index dad9e84a47094..207d69d02e268 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts @@ -1,8 +1,10 @@ -import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { Construct } from 'constructs'; +import { ActionCategory, ActionArtifactBounds } from './action'; +import { CfnCustomActionType } from './codepipeline.generated'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; +import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * The creation attributes used for defining a configuration property @@ -13,14 +15,14 @@ export interface CustomActionProperty { * The name of the property. * You use this name in the `configuration` attribute when defining your custom Action class. */ - name: string; + readonly name: string; /** * The description of the property. * * @default the description will be empty */ - description?: string; + readonly description?: string; /** * Whether this property is a key. @@ -28,7 +30,7 @@ export interface CustomActionProperty { * @default false * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-key */ - key?: boolean; + readonly key?: boolean; /** * Whether this property is queryable. @@ -37,12 +39,12 @@ export interface CustomActionProperty { * @default false * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-queryable */ - queryable?: boolean; + readonly queryable?: boolean; /** * Whether this property is required. */ - required: boolean; + readonly required: boolean; /** * Whether this property is secret, @@ -50,7 +52,7 @@ export interface CustomActionProperty { * * @default false */ - secret?: boolean; + readonly secret?: boolean; /** * The type of the property, @@ -58,7 +60,7 @@ export interface CustomActionProperty { * * @default 'String' */ - type?: string; + readonly type?: string; } /** @@ -68,41 +70,44 @@ export interface CustomActionRegistrationProps { /** * The category of the Action. */ - category: codepipeline.ActionCategory; + readonly category: ActionCategory; /** * The artifact bounds of the Action. */ - artifactBounds: codepipeline.ActionArtifactBounds; + readonly artifactBounds: ActionArtifactBounds; /** * The provider of the Action. + * For example, `'MyCustomActionProvider'` */ - provider: string; + readonly provider: string; /** * The version of your Action. * * @default '1' */ - version?: string; + readonly version?: string; /** * The URL shown for the entire Action in the Pipeline UI. + * @default none */ - entityUrl?: string; + readonly entityUrl?: string; /** * The URL shown for a particular execution of an Action in the Pipeline UI. + * @default none */ - executionUrl?: string; + readonly executionUrl?: string; /** * The properties used for customizing the instance of your Action. * * @default [] */ - actionProperties?: CustomActionProperty[]; + readonly actionProperties?: CustomActionProperty[]; } /** @@ -112,11 +117,11 @@ export interface CustomActionRegistrationProps { * representing your custom Action, extending the Action class, * and taking the `actionProperties` as properly typed, construction properties. */ -export class CustomActionRegistration extends Construct { - constructor(parent: Construct, id: string, props: CustomActionRegistrationProps) { - super(parent, id); +export class CustomActionRegistration extends CoreConstruct { + constructor(scope: Construct, id: string, props: CustomActionRegistrationProps) { + super(scope, id); - new codepipeline.CfnCustomActionType(this, 'Resource', { + new CfnCustomActionType(this, 'Resource', { category: props.category, inputArtifactDetails: { minimumCount: props.artifactBounds.minInputs, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-codepipeline/lib/index.ts index c62d8dda6b33f..81b855f904184 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/index.ts @@ -1,6 +1,7 @@ export * from './action'; export * from './artifact'; export * from './pipeline'; +export * from './custom-action-registration'; // AWS::CodePipeline CloudFormation Resources: export * from './codepipeline.generated'; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index f453b0c5748ae..1dc98ccca51d4 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -4,7 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { - App, BootstraplessSynthesizer, DefaultStackSynthesizer, + App, ArnFormat, BootstraplessSynthesizer, DefaultStackSynthesizer, IStackSynthesizer, Lazy, Names, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -263,15 +263,19 @@ abstract class PipelineBase extends Resource implements IPipeline { * * @example * // create a pipeline - * const pipeline = new Pipeline(this, 'Pipeline'); + * import * as codecommit from '@aws-cdk/aws-codecommit'; + * + * const pipeline = new codepipeline.Pipeline(this, 'Pipeline'); * * // add a stage * const sourceStage = pipeline.addStage({ stageName: 'Source' }); * * // add a source action to the stage + * declare const repo: codecommit.Repository; + * declare const sourceArtifact: codepipeline.Artifact; * sourceStage.addAction(new codepipeline_actions.CodeCommitSourceAction({ * actionName: 'Source', - * outputArtifactName: 'SourceArtifact', + * output: sourceArtifact, * repository: repo, * })); * @@ -287,7 +291,7 @@ export class Pipeline extends PipelineBase { */ public static fromPipelineArn(scope: Construct, id: string, pipelineArn: string): IPipeline { class Import extends PipelineBase { - public readonly pipelineName = Stack.of(scope).parseArn(pipelineArn).resource; + public readonly pipelineName = Stack.of(scope).splitArn(pipelineArn, ArnFormat.SLASH_RESOURCE_NAME).resource; public readonly pipelineArn = pipelineArn; } diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 5f242010eae8b..84f28a8804685 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -83,8 +90,8 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-codestarnotifications": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture b/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture new file mode 100644 index 0000000000000..43b0b75b3788e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture @@ -0,0 +1,61 @@ +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; + +interface MyActionProps { + variablesNamespace?: string; + actionName: string; +} + +class MyAction extends codepipeline.Action { + public variables: { [key: string]: string }; + protected readonly providedActionProperties: codepipeline.ActionProperties; + + constructor(props: MyActionProps) { + super(); + this.providedActionProperties = { + ...props, + category: codepipeline.ActionCategory.SOURCE, + provider: 'Fake', + artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 4 }, + }; + this.variables = { 'myVariable': 'var' }; + } + + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return {}; + } +} + +interface OtherActionProps { + config: string; + actionName: string; +} + +class OtherAction extends codepipeline.Action { + protected readonly providedActionProperties: codepipeline.ActionProperties; + + constructor(props: OtherActionProps) { + super(); + this.providedActionProperties = { + ...props, + category: codepipeline.ActionCategory.SOURCE, + provider: 'Fake', + artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 4 }, + }; + } + + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return {}; + } +} + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..b46720fa572c4 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +import { Construct } from 'constructs'; +import { App, Duration, PhysicalName, Stack } from '@aws-cdk/core'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts index cf4f733d811be..4570ea850df3a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts @@ -17,7 +17,7 @@ describe('artifacts', () => { test('without a name, when used as an input without being used as an output first - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -44,8 +44,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Action 'Build' is using an unnamed input Artifact, which is not being produced in this pipeline"); + expect(error).toMatch(/Action 'Build' is using an unnamed input Artifact, which is not being produced in this pipeline/); }); @@ -53,7 +52,7 @@ describe('artifacts', () => { test('with a name, when used as an input without being used as an output first - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -80,8 +79,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Action 'Build' is using input Artifact 'named', which is not being produced in this pipeline"); + expect(error).toMatch(/Action 'Build' is using input Artifact 'named', which is not being produced in this pipeline/); }); @@ -89,7 +87,7 @@ describe('artifacts', () => { test('without a name, when used as an output multiple times - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -117,8 +115,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Both Actions 'Source' and 'Build' are producting Artifact 'Artifact_Source_Source'. Every artifact can only be produced once."); + expect(error).toMatch(/Both Actions 'Source' and 'Build' are producting Artifact 'Artifact_Source_Source'. Every artifact can only be produced once./); }); @@ -177,7 +174,7 @@ describe('artifacts', () => { const buildOutput1 = new codepipeline.Artifact('buildOutput1'); const sourceOutput2 = new codepipeline.Artifact('sourceOutput2'); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -217,8 +214,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Stage 2 Action 2 ('Build'/'build2') is consuming input Artifact 'buildOutput1' before it is being produced at Stage 2 Action 3 ('Build'/'build1')"); + expect(error).toMatch(/Stage 2 Action 2 \('Build'\/'build2'\) is consuming input Artifact 'buildOutput1' before it is being produced at Stage 2 Action 3 \('Build'\/'build1'\)/); }); @@ -283,8 +279,16 @@ describe('artifacts', () => { }); /* eslint-disable @aws-cdk/no-core-construct */ -function validate(construct: cdk.IConstruct): cdk.ValidationError[] { - cdk.ConstructNode.prepare(construct.node); - return cdk.ConstructNode.validate(construct.node); +function validate(construct: cdk.IConstruct): string[] { + try { + (construct.node.root as cdk.App).synth(); + return []; + } catch (e) { + const err = e as any; // coerce unknown to any + if (!('message' in err) || !err.message.startsWith('Validation failed')) { + throw e; + } + return err.message.split('\n').slice(1); + } } /* eslint-enable @aws-cdk/no-core-construct */ diff --git a/packages/@aws-cdk/aws-codestar/package.json b/packages/@aws-cdk/aws-codestar/package.json index 40edf4f9fe7a6..c07b029163b37 100644 --- a/packages/@aws-cdk/aws-codestar/package.json +++ b/packages/@aws-cdk/aws-codestar/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-s3": "0.0.0", diff --git a/packages/@aws-cdk/aws-codestarconnections/package.json b/packages/@aws-cdk/aws-codestarconnections/package.json index 4c60595599eb8..93ad273d92f34 100644 --- a/packages/@aws-cdk/aws-codestarconnections/package.json +++ b/packages/@aws-cdk/aws-codestarconnections/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index ec43414e43e45..08a772cb69dbb 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index b015607589652..906480586b769 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -108,7 +108,7 @@ new cognito.UserPool(this, 'myuserpool', { userInvitation: { emailSubject: 'Invite to join our awesome app!', emailBody: 'Hello {username}, you have been invited to join our awesome app! Your temporary password is {####}', - smsMessage: 'Your temporary password for our awesome app is {####}' + smsMessage: 'Hello {username}, your temporary password for our awesome app is {####}' } }); ``` @@ -314,29 +314,55 @@ new cognito.UserPool(this, 'UserPool', { The default for account recovery is by phone if available and by email otherwise. A user will not be allowed to reset their password via phone if they are also using it for MFA. + ### Emails Cognito sends emails to users in the user pool, when particular actions take place, such as welcome emails, invitation emails, password resets, etc. The address from which these emails are sent can be configured on the user pool. -Read more about [email settings here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html). +Read more at [Email settings for User Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html). + +By default, user pools are configured to use Cognito's built in email capability, which will send emails +from `no-reply@verificationemail.com`. If you want to use a custom email address you can configure +Cognito to send emails through Amazon SES, which is detailed below. ```ts new cognito.UserPool(this, 'myuserpool', { - // ... - emailSettings: { - from: 'noreply@myawesomeapp.com', + email: UserPoolEmail.withCognito('support@myawesomeapp.com'), +}); +``` + +For typical production environments, the default email limit is below the required delivery volume. +To enable a higher delivery volume, you can configure the UserPool to send emails through Amazon SES. To do +so, follow the steps in the [Cognito Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-developer) +to verify an email address, move the account out of the SES sandbox, and grant Cognito email permissions via an +authorization policy. + +Once the SES setup is complete, the UserPool can be configured to use the SES email. + +```ts +new cognito.UserPool(this, 'myuserpool', { + email: UserPoolEmail.withSES({ + fromEmail: 'noreply@myawesomeapp.com', + fromName: 'Awesome App', replyTo: 'support@myawesomeapp.com', - }, + }), }); ``` -By default, user pools are configured to use Cognito's built-in email capability, but it can also be configured to use -Amazon SES, however, support for Amazon SES is not available in the CDK yet. If you would like this to be implemented, -give [this issue](https://github.com/aws/aws-cdk/issues/6768) a +1. Until then, you can use the [cfn -layer](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html) to configure this. +Sending emails through SES requires that SES be configured (as described above) in one of the regions - `us-east-1`, `us-west-1`, or `eu-west-1`. +If the UserPool is being created in a different region, `sesRegion` must be used to specify the correct SES region. + +```ts +new cognito.UserPool(this, 'myuserpool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'noreply@myawesomeapp.com', + fromName: 'Awesome App', + replyTo: 'support@myawesomeapp.com', + }), +}); -If an email address contains non-ASCII characters, it will be encoded using the [punycode -encoding](https://en.wikipedia.org/wiki/Punycode) when generating the template for Cloudformation. +``` ### Device Tracking diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index cab56671c2b9e..7d5ce97fc2c76 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -4,6 +4,7 @@ export * from './user-pool'; export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; +export * from './user-pool-email'; export * from './user-pool-idp'; export * from './user-pool-idps'; export * from './user-pool-resource-server'; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts new file mode 100644 index 0000000000000..2d5b8af06447f --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts @@ -0,0 +1,203 @@ +import { Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { toASCII as punycodeEncode } from 'punycode/'; + +/** + * The valid Amazon SES configuration regions + */ +const REGIONS = ['us-east-1', 'us-west-2', 'eu-west-1']; + +/** + * Configuration for Cognito sending emails via Amazon SES + */ +export interface UserPoolSESOptions { + /** + * The verified Amazon SES email address that Cognito should + * use to send emails. + * + * The email address used must be a verified email address + * in Amazon SES and must be configured to allow Cognito to + * send emails. + * + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html + */ + readonly fromEmail: string; + + /** + * An optional name that should be used as the sender's name + * along with the email. + * + * @default - no name + */ + readonly fromName?: string; + + /** + * The destination to which the receiver of the email should reploy to. + * + * @default - same as the fromEmail + */ + readonly replyTo?: string; + + /** + * The name of a configuration set in Amazon SES that should + * be applied to emails sent via Cognito. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-userpool-emailconfiguration.html#cfn-cognito-userpool-emailconfiguration-configurationset + * + * @default - no configuration set + */ + readonly configurationSetName?: string; + + /** + * Required if the UserPool region is different than the SES region. + * + * If sending emails with a Amazon SES verified email address, + * and the region that SES is configured is different than the + * region in which the UserPool is deployed, you must specify that + * region here. + * + * Must be 'us-east-1', 'us-west-2', or 'eu-west-1' + * + * @default - The same region as the Cognito UserPool + */ + readonly sesRegion?: string; +} + +/** + * Result of binding email settings with a user pool + */ +interface UserPoolEmailConfig { + /** + * The name of the configuration set in SES. + * + * @default - none + */ + readonly configurationSet?: string; + + /** + * Specifies whether to use Cognito's built in email functionality + * or SES. + * + * @default - Cognito built in email functionality + */ + readonly emailSendingAccount?: string; + + /** + * Identifies either the sender's email address or the sender's + * name with their email address. + * + * If emailSendingAccount is DEVELOPER then this cannot be specified. + * + * @default 'no-reply@verificationemail.com' + */ + readonly from?: string; + + /** + * The destination to which the receiver of the email should reply to. + * + * @default - same as `from` + */ + readonly replyToEmailAddress?: string; + + /** + * The ARN of a verified email address in Amazon SES. + * + * required if emailSendingAccount is DEVELOPER or if + * 'from' is provided. + * + * @default - none + */ + readonly sourceArn?: string; +} + +/** + * Configure how Cognito sends emails + */ +export abstract class UserPoolEmail { + /** + * Send email using Cognito + */ + public static withCognito(replyTo?: string): UserPoolEmail { + return new CognitoEmail(replyTo); + } + + /** + * Send email using SES + */ + public static withSES(options: UserPoolSESOptions): UserPoolEmail { + return new SESEmail(options); + } + + + /** + * Returns the email configuration for a Cognito UserPool + * that controls how Cognito will send emails + * @internal + */ + public abstract _bind(scope: Construct): UserPoolEmailConfig; + +} + +class CognitoEmail extends UserPoolEmail { + constructor(private readonly replyTo?: string) { + super(); + } + + public _bind(_scope: Construct): UserPoolEmailConfig { + return { + replyToEmailAddress: encodeAndTest(this.replyTo), + emailSendingAccount: 'COGNITO_DEFAULT', + }; + + } +} + +class SESEmail extends UserPoolEmail { + constructor(private readonly options: UserPoolSESOptions) { + super(); + } + + public _bind(scope: Construct): UserPoolEmailConfig { + const region = Stack.of(scope).region; + + if (Token.isUnresolved(region) && !this.options.sesRegion) { + throw new Error('Your stack region cannot be determined so "sesRegion" is required in SESOptions'); + } + + if (this.options.sesRegion && !REGIONS.includes(this.options.sesRegion)) { + throw new Error(`sesRegion must be one of 'us-east-1', 'us-west-2', 'eu-west-1'. received ${this.options.sesRegion}`); + } else if (!this.options.sesRegion && !REGIONS.includes(region)) { + throw new Error(`Your stack is in ${region}, which is not a SES Region. Please provide a valid value for 'sesRegion'`); + } + + let from = this.options.fromEmail; + if (this.options.fromName) { + from = `${this.options.fromName} <${this.options.fromEmail}>`; + } + + return { + from: encodeAndTest(from), + replyToEmailAddress: encodeAndTest(this.options.replyTo), + configurationSet: this.options.configurationSetName, + emailSendingAccount: 'DEVELOPER', + sourceArn: Stack.of(scope).formatArn({ + service: 'ses', + resource: 'identity', + resourceName: encodeAndTest(this.options.fromEmail), + region: this.options.sesRegion ?? region, + }), + }; + } +} + +function encodeAndTest(input: string | undefined): string | undefined { + if (input) { + const local = input.split('@')[0]; + if (!/[\p{ASCII}]+/u.test(local)) { + throw new Error('the local part of the email address must use ASCII characters only'); + } + return punycodeEncode(input); + } else { + return undefined; + } +} diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 6277ee0f467ee..731b382e20f76 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -1,6 +1,6 @@ import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { toASCII as punycodeEncode } from 'punycode/'; import { CfnUserPool } from './cognito.generated'; @@ -8,6 +8,7 @@ import { StandardAttributeNames } from './private/attr-names'; import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user-pool-attr'; import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; +import { UserPoolEmail } from './user-pool-email'; import { IUserPoolIdentityProvider } from './user-pool-idp'; import { UserPoolResourceServer, UserPoolResourceServerOptions } from './user-pool-resource-server'; @@ -570,10 +571,18 @@ export interface UserPoolProps { /** * Email settings for a user pool. + * * @default - see defaults on each property of EmailSettings. + * @deprecated Use 'email' instead. */ readonly emailSettings?: EmailSettings; + /** + * Email settings for a user pool. + * @default - cognito will use the default email configuration + */ + readonly email?: UserPoolEmail; + /** * Lambda functions to use for supported Cognito triggers. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html @@ -706,7 +715,7 @@ export class UserPool extends UserPoolBase { * Import an existing user pool based on its ARN. */ public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool { - const arnParts = Stack.of(scope).parseArn(userPoolArn); + const arnParts = Stack.of(scope).splitArn(userPoolArn, ArnFormat.SLASH_RESOURCE_NAME); if (!arnParts.resourceName) { throw new Error('invalid user pool ARN'); @@ -788,6 +797,14 @@ export class UserPool extends UserPoolBase { const passwordPolicy = this.configurePasswordPolicy(props); + if (props.email && props.emailSettings) { + throw new Error('you must either provide "email" or "emailSettings", but not both'); + } + const emailConfiguration = props.email ? props.email._bind(this) : undefinedIfNoKeys({ + from: encodePuny(props.emailSettings?.from), + replyToEmailAddress: encodePuny(props.emailSettings?.replyTo), + }); + const userPool = new CfnUserPool(this, 'Resource', { userPoolName: props.userPoolName, usernameAttributes: signIn.usernameAttrs, @@ -805,10 +822,7 @@ export class UserPool extends UserPoolBase { mfaConfiguration: props.mfa, enabledMfas: this.mfaConfiguration(props), policies: passwordPolicy !== undefined ? { passwordPolicy } : undefined, - emailConfiguration: undefinedIfNoKeys({ - from: encodePuny(props.emailSettings?.from), - replyToEmailAddress: encodePuny(props.emailSettings?.replyTo), - }), + emailConfiguration, usernameConfiguration: undefinedIfNoKeys({ caseSensitive: props.signInCaseSensitive, }), @@ -830,7 +844,7 @@ export class UserPool extends UserPoolBase { */ public addTrigger(operation: UserPoolOperation, fn: lambda.IFunction): void { if (operation.operationName in this.triggers) { - throw new Error(`A trigger for the operation ${operation} already exists.`); + throw new Error(`A trigger for the operation ${operation.operationName} already exists.`); } this.addLambdaPermission(fn, operation.operationName); diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 8b280b238a8d4..89e8c16900564 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -77,9 +77,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/punycode": "^2.1.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 7b132803da2d6..9d89be13d6fef 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -1,9 +1,10 @@ import { Match, Template } from '@aws-cdk/assertions'; import { Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnParameter, Duration, Stack, Tags } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib'; +import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle, UserPoolEmail } from '../lib'; describe('User Pool', () => { test('default setup', () => { @@ -403,7 +404,7 @@ describe('User Pool', () => { // WHEN userpool.addTrigger(UserPoolOperation.CREATE_AUTH_CHALLENGE, fn1); - expect(() => userpool.addTrigger(UserPoolOperation.CREATE_AUTH_CHALLENGE, fn2)).toThrow(/already exists/); + expect(() => userpool.addTrigger(UserPoolOperation.CREATE_AUTH_CHALLENGE, fn2)).toThrow(/createAuthChallenge already exists/); }); test('no username aliases specified', () => { @@ -903,7 +904,7 @@ describe('User Pool', () => { })).toThrow(/minLength for password must be between/); }); - test('email transmission settings are recognized correctly', () => { + testDeprecated('email transmission settings are recognized correctly', () => { // GIVEN const stack = new Stack(); @@ -1368,7 +1369,7 @@ describe('User Pool', () => { }); }); - test('email transmission with cyrillic characters are encoded', () => { + testDeprecated('email transmission with cyrillic characters are encoded', () => { // GIVEN const stack = new Stack(); @@ -1388,6 +1389,285 @@ describe('User Pool', () => { }, }); }); + + test('email transmission with cyrillic characters in the domain are encoded', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'user@домен.рф', + replyTo: 'user@домен.рф', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + From: 'user@xn--d1acufc.xn--p1ai', + ReplyToEmailAddress: 'user@xn--d1acufc.xn--p1ai', + }, + }); + }); + + test('email transmission with cyrillic characters in the local part throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'от@домен.рф', + replyTo: 'user@домен.рф', + }), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email transmission with cyrillic characters in the local part of replyTo throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'user@домен.рф', + replyTo: 'от@домен.рф', + }), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email withCognito transmission with cyrillic characters in the local part of replyTo throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito('от@домен.рф'), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email withCognito', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito(), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'COGNITO_DEFAULT', + }, + }); + }); + + test('email withCognito and replyTo', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito('reply@example.com'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'COGNITO_DEFAULT', + ReplyToEmailAddress: 'reply@example.com', + }, + }); + }); + + test('email withSES with custom email and no region', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + replyTo: 'reply@example.com', + }), + })).toThrow(/Your stack region cannot be determined/); + + }); + + test('email withSES with no name', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-1', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'mycustomemail@example.com', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + + test('email withSES', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-1', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'My Custom Email ', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + + test('email withSES with valid region', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-2', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + sesRegion: 'us-east-1', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'My Custom Email ', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + test('email withSES invalid region throws error', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-2', + account: '11111111111', + }, + }); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + })).toThrow(/Please provide a valid value/); + + }); + + test('email withSES invalid sesRegion throws error', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + account: '11111111111', + }, + }); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-2', + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + })).toThrow(/sesRegion must be one of/); + + }); }); test('device tracking is configured correctly', () => { diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 1f6458201763a..d52985a5561bf 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -78,8 +78,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-connect/package.json b/packages/@aws-cdk/aws-connect/package.json index d1ec993751e42..dfc68b783399d 100644 --- a/packages/@aws-cdk/aws-connect/package.json +++ b/packages/@aws-cdk/aws-connect/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-cur/package.json b/packages/@aws-cdk/aws-cur/package.json index 73ce922bae440..a6b6436c1e289 100644 --- a/packages/@aws-cdk/aws-cur/package.json +++ b/packages/@aws-cdk/aws-cur/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-customerprofiles/package.json b/packages/@aws-cdk/aws-customerprofiles/package.json index b05870683b8bf..25c30a3e9b3bc 100644 --- a/packages/@aws-cdk/aws-customerprofiles/package.json +++ b/packages/@aws-cdk/aws-customerprofiles/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-databrew/package.json b/packages/@aws-cdk/aws-databrew/package.json index 9c9ae300c4044..16afbe12cff84 100644 --- a/packages/@aws-cdk/aws-databrew/package.json +++ b/packages/@aws-cdk/aws-databrew/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 9c4e850b6f90c..594bd86ef375f 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-datasync/package.json b/packages/@aws-cdk/aws-datasync/package.json index 80db2e21342e6..a8534ef967112 100644 --- a/packages/@aws-cdk/aws-datasync/package.json +++ b/packages/@aws-cdk/aws-datasync/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index e7cc80850e2a6..99246561f2d53 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-detective/package.json b/packages/@aws-cdk/aws-detective/package.json index 4e7eb64080103..0dc643180b84b 100644 --- a/packages/@aws-cdk/aws-detective/package.json +++ b/packages/@aws-cdk/aws-detective/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-devopsguru/package.json b/packages/@aws-cdk/aws-devopsguru/package.json index 0434c473a8862..13d92fc5f5779 100644 --- a/packages/@aws-cdk/aws-devopsguru/package.json +++ b/packages/@aws-cdk/aws-devopsguru/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index 0f5d849fd4b1f..f2160c4bac892 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index d8baae91ee532..0d4b4c73ca54f 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index 366911bc7129c..f4f20b0130875 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-docdb/README.md b/packages/@aws-cdk/aws-docdb/README.md index 6f7ed89e28e11..12f8e08a08387 100644 --- a/packages/@aws-cdk/aws-docdb/README.md +++ b/packages/@aws-cdk/aws-docdb/README.md @@ -21,6 +21,7 @@ your instances will be launched privately or publicly: const cluster = new DatabaseCluster(this, 'Database', { masterUser: { username: 'myuser' // NOTE: 'admin' is reserved by DocumentDB + excludeCharacters: '\"@/:', // optional, defaults to the set "\"@/" }, instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), vpcSubnets: { diff --git a/packages/@aws-cdk/aws-docdb/lib/cluster.ts b/packages/@aws-cdk/aws-docdb/lib/cluster.ts index 56b5a724dd439..c2b4c7c9737a2 100644 --- a/packages/@aws-cdk/aws-docdb/lib/cluster.ts +++ b/packages/@aws-cdk/aws-docdb/lib/cluster.ts @@ -352,6 +352,7 @@ export class DatabaseCluster extends DatabaseClusterBase { secret = new DatabaseSecret(this, 'Secret', { username: props.masterUser.username, encryptionKey: props.masterUser.kmsKey, + excludeCharacters: props.masterUser.excludeCharacters, }); } diff --git a/packages/@aws-cdk/aws-docdb/lib/database-secret.ts b/packages/@aws-cdk/aws-docdb/lib/database-secret.ts index 605609b4b6ab2..8f1bca671da6d 100644 --- a/packages/@aws-cdk/aws-docdb/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-docdb/lib/database-secret.ts @@ -32,6 +32,13 @@ export interface DatabaseSecretProps { * @default - no master secret information will be included */ readonly masterSecret?: ISecret; + + /** + * Characters to not include in the generated password. + * + * @default "\"@/" + */ + readonly excludeCharacters?: string; } /** @@ -61,7 +68,7 @@ export class DatabaseSecret extends Secret { masterarn: props.masterSecret?.secretArn, }), generateStringKey: 'password', - excludeCharacters: '"@/', + excludeCharacters: props.excludeCharacters ?? '"@/', }, }); } diff --git a/packages/@aws-cdk/aws-docdb/lib/instance.ts b/packages/@aws-cdk/aws-docdb/lib/instance.ts index a7563cb601388..1a791b99a0bf5 100644 --- a/packages/@aws-cdk/aws-docdb/lib/instance.ts +++ b/packages/@aws-cdk/aws-docdb/lib/instance.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IDatabaseCluster } from './cluster-ref'; import { CfnDBInstance } from './docdb.generated'; @@ -102,7 +103,7 @@ abstract class DatabaseInstanceBase extends cdk.Resource implements IDatabaseIns return cdk.Stack.of(this).formatArn({ service: 'docdb', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.instanceIdentifier, }); } diff --git a/packages/@aws-cdk/aws-docdb/lib/props.ts b/packages/@aws-cdk/aws-docdb/lib/props.ts index 9cd24b1fce1bc..d02f8768973a9 100644 --- a/packages/@aws-cdk/aws-docdb/lib/props.ts +++ b/packages/@aws-cdk/aws-docdb/lib/props.ts @@ -53,6 +53,13 @@ export interface Login { * @default default master key */ readonly kmsKey?: kms.IKey; + + /** + * Specifies characters to not include in generated passwords. + * + * @default "\"@/" + */ + readonly excludeCharacters?: string; } /** diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index cc8a7bb9802a3..d60317ab084a9 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index cb1f8653509df..6628520118c84 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -1,4 +1,4 @@ -import { expect as expectCDK, haveResource, ResourcePart, arrayWith } from '@aws-cdk/assert-internal'; +import { expect as expectCDK, haveResource, ResourcePart, arrayWith, haveResourceLike, objectLike } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -293,6 +293,29 @@ describe('DatabaseCluster', () => { })); }); + test('creates a secret with excludeCharacters', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + masterUser: { + username: 'admin', + excludeCharacters: '"@/()[]', + }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::SecretsManager::Secret', { + GenerateSecretString: objectLike({ + ExcludeCharacters: '\"@/()[]', + }), + })); + }); + test('create an encrypted cluster with custom KMS key', () => { // GIVEN const stack = testStack(); diff --git a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json index 6187414b9eb75..c063376359496 100644 --- a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.expected.json @@ -601,7 +601,9 @@ "PasswordLength": 41, "SecretStringTemplate": "{\"username\":\"docdb\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "DatabaseSecretAttachmentE5D1B020": { "Type": "AWS::SecretsManager::SecretTargetAttachment", diff --git a/packages/@aws-cdk/aws-docdb/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-docdb/test/integ.cluster.expected.json index e8737956c6440..1b661827dd1ec 100644 --- a/packages/@aws-cdk/aws-docdb/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-docdb/test/integ.cluster.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -506,4 +506,4 @@ "DeletionPolicy": "Delete" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json index 3893d9f854cee..6e16e263c4129 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -33,12 +33,12 @@ "aws-sdk-mock": "^5.4.0", "eslint": "^7.32.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.24.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "lambda-tester": "^3.6.0", - "nock": "^13.1.3" + "nock": "^13.2.1" } } diff --git a/packages/@aws-cdk/aws-dynamodb-global/package.json b/packages/@aws-cdk/aws-dynamodb-global/package.json index 4c0f401086469..c84dcc4073b42 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/package.json @@ -60,8 +60,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "peerDependencies": { "@aws-cdk/aws-dynamodb": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts b/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts index ed84a3f8a6145..5a6a45d5be3a3 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { Attribute, AttributeType, StreamViewType, Table } from '@aws-cdk/aws-dynamodb'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { GlobalTable, GlobalTableProps } from '../lib'; @@ -18,7 +19,7 @@ const STACK_PROPS: GlobalTableProps = { regions: ['us-east-1', 'us-east-2', 'us-west-2'], }; -describe('Default Global DynamoDB stack', () => { +describeDeprecated('Default Global DynamoDB stack', () => { test('global dynamo', () => { const stack = new Stack(); new GlobalTable(stack, CONSTRUCT_NAME, STACK_PROPS); @@ -58,7 +59,7 @@ describe('Default Global DynamoDB stack', () => { }); }); -test('GlobalTable generated stacks inherit their account from the parent stack', () => { +testDeprecated('GlobalTable generated stacks inherit their account from the parent stack', () => { const app = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const stack = new Stack(app, 'GlobalTableStack', { env: { account: '123456789012', region: 'us-east-1' } }); @@ -85,7 +86,7 @@ test('GlobalTable generated stacks inherit their account from the parent stack', }); }); -describe('Enforce StreamSpecification', () => { +describeDeprecated('Enforce StreamSpecification', () => { test('global dynamo should only allow NEW_AND_OLD_IMAGES', () => { const stack = new Stack(); @@ -100,7 +101,7 @@ describe('Enforce StreamSpecification', () => { }); }); -describe('Check getting tables', () => { +describeDeprecated('Check getting tables', () => { test('global dynamo should only allow NEW_AND_OLD_IMAGES', () => { const stack = new Stack(); const regTables = new GlobalTable(stack, CONSTRUCT_NAME, { diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts index 90965454a6828..85ecc746b8ae5 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts @@ -5,6 +5,10 @@ import { GlobalTable } from '../lib'; const app = new App(); const stack = new Stack(app, 'Default'); + +const deprecated = process.env.JSII_DEPRECATED; +process.env.JSII_DEPRECATED = 'quiet'; + new GlobalTable(stack, 'globdynamodbinteg', { partitionKey: { name: 'hashKey', type: AttributeType.STRING }, tableName: 'integrationtest', @@ -12,3 +16,9 @@ new GlobalTable(stack, 'globdynamodbinteg', { removalPolicy: RemovalPolicy.DESTROY, }); app.synth(); + +if (deprecated) { + process.env.JSII_DEPRECATED = deprecated; +} else { + delete process.env.JSII_DEPRECATED; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index b07946c52d65f..b79b1e0efe465 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -14,10 +14,8 @@ Here is a minimal deployable DynamoDB table definition: ```ts -import * as dynamodb from '@aws-cdk/aws-dynamodb'; - const table = new dynamodb.Table(this, 'Table', { - partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING } + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, }); ``` @@ -28,7 +26,8 @@ factory method. This method accepts table name or table ARN which describes the existing table: ```ts -const table = Table.fromTableArn(this, 'ImportedTable', 'arn:aws:dynamodb:us-east-1:111111111:table/my-table'); +declare const user: iam.User; +const table = dynamodb.Table.fromTableArn(this, 'ImportedTable', 'arn:aws:dynamodb:us-east-1:111111111:table/my-table'); // now you can just call methods on the table table.grantReadWriteData(user); ``` @@ -50,11 +49,9 @@ DynamoDB supports two billing modes: * PAY_PER_REQUEST - on-demand pricing and scaling. You only pay for what you use and there is no read and write capacity for the table or its global secondary indexes. ```ts -import * as dynamodb from '@aws-cdk/aws-dynamodb'; - const table = new dynamodb.Table(this, 'Table', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, - billingMode: dynamodb.BillingMode.PAY_PER_REQUEST + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, }); ``` @@ -81,8 +78,6 @@ https://aws.amazon.com/blogs/database/how-to-use-aws-cloudformation-to-configure You can create DynamoDB Global Tables by setting the `replicationRegions` property on a `Table`: ```ts -import * as dynamodb from '@aws-cdk/aws-dynamodb'; - const globalTable = new dynamodb.Table(this, 'Table', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'], @@ -100,7 +95,7 @@ you have to make sure write auto-scaling is enabled for that Table: const globalTable = new dynamodb.Table(this, 'Table', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'], - billingMode: BillingMode.PROVISIONED, + billingMode: dynamodb.BillingMode.PROVISIONED, }); globalTable.autoScaleWriteCapacity({ @@ -131,11 +126,9 @@ All user data stored in Amazon DynamoDB is fully encrypted at rest. When creatin Creating a Table encrypted with a customer managed CMK: ```ts -import dynamodb = require('@aws-cdk/aws-dynamodb'); - -const table = new dynamodb.Table(stack, 'MyTable', { +const table = new dynamodb.Table(this, 'MyTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, - encryption: TableEncryption.CUSTOMER_MANAGED, + encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED, }); // You can access the CMK that was added to the stack on your behalf by the Table construct via: @@ -145,15 +138,14 @@ const tableEncryptionKey = table.encryptionKey; You can also supply your own key: ```ts -import dynamodb = require('@aws-cdk/aws-dynamodb'); -import kms = require('@aws-cdk/aws-kms'); +import * as kms from '@aws-cdk/aws-kms'; -const encryptionKey = new kms.Key(stack, 'Key', { - enableKeyRotation: true +const encryptionKey = new kms.Key(this, 'Key', { + enableKeyRotation: true, }); -const table = new dynamodb.Table(stack, 'MyTable', { +const table = new dynamodb.Table(this, 'MyTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, - encryption: TableEncryption.CUSTOMER_MANAGED, + encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED, encryptionKey, // This will be exposed as table.encryptionKey }); ``` @@ -161,11 +153,9 @@ const table = new dynamodb.Table(stack, 'MyTable', { In order to use the AWS managed CMK instead, change the code to: ```ts -import dynamodb = require('@aws-cdk/aws-dynamodb'); - -const table = new dynamodb.Table(stack, 'MyTable', { +const table = new dynamodb.Table(this, 'MyTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, - encryption: TableEncryption.AWS_MANAGED, + encryption: dynamodb.TableEncryption.AWS_MANAGED, }); // In this case, the CMK _cannot_ be accessed through table.encryptionKey. @@ -176,11 +166,13 @@ const table = new dynamodb.Table(stack, 'MyTable', { To get the partition key and sort key of the table or indexes you have configured: ```ts -const { partitionKey, sortKey } = table.schema(); +declare const table: dynamodb.Table; +const schema = table.schema(); +const partitionKey = schema.partitionKey; +const sortKey = schema.sortKey; // In case you want to get schema details for any secondary index - -const { partitionKey, sortKey } = table.schema(INDEX_NAME); +// const { partitionKey, sortKey } = table.schema(INDEX_NAME); ``` ## Kinesis Stream @@ -188,7 +180,6 @@ const { partitionKey, sortKey } = table.schema(INDEX_NAME); A Kinesis Data Stream can be configured on the DynamoDB table to capture item-level changes. ```ts -import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as kinesis from '@aws-cdk/aws-kinesis'; const stream = new kinesis.Stream(this, 'Stream'); diff --git a/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts b/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts index 1554dcc84004d..38109b1a4a614 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts @@ -49,12 +49,13 @@ export async function isCompleteHandler(event: IsCompleteRequest): Promise r.RegionName === event.ResourceProperties.Region); const replicaActive = !!(regionReplica?.ReplicaStatus === 'ACTIVE'); + const skipReplicationCompletedWait = event.ResourceProperties.SkipReplicationCompletedWait ?? false; switch (event.RequestType) { case 'Create': case 'Update': // Complete when replica is reported as ACTIVE - return { IsComplete: tableActive && replicaActive }; + return { IsComplete: tableActive && (replicaActive || skipReplicationCompletedWait) }; case 'Delete': // Complete when replica is gone return { IsComplete: tableActive && regionReplica === undefined }; diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index a2b1aae5ee2eb..6e6b79d253d1a 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as kms from '@aws-cdk/aws-kms'; import { + ArnFormat, Aws, CfnCondition, CfnCustomResource, CfnResource, CustomResource, Duration, Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; @@ -231,6 +232,22 @@ export interface TableOptions extends SchemaOptions { */ readonly replicationTimeout?: Duration; + /** + * Indicates whether CloudFormation stack waits for replication to finish. + * If set to false, the CloudFormation resource will mark the resource as + * created and replication will be completed asynchronously. This property is + * ignored if replicationRegions property is not set. + * + * DO NOT UNSET this property if adding/removing multiple replicationRegions + * in one deployment, as CloudFormation only supports one region replication + * at a time. CDK overcomes this limitation by waiting for replication to + * finish before starting new replicationRegion. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-globaltable.html#cfn-dynamodb-globaltable-replicas + * @default true + */ + readonly waitForReplicationToFinish?: boolean; + /** * Whether CloudWatch contributor insights is enabled. * @@ -723,7 +740,7 @@ abstract class TableBase extends Resource implements ITable { return new cloudwatch.Metric({ namespace: 'AWS/DynamoDB', metricName, - dimensions: { + dimensionsMap: { TableName: this.tableName, }, ...props, @@ -756,17 +773,18 @@ abstract class TableBase extends Resource implements ITable { * @deprecated use `metricSystemErrorsForOperations`. */ public metricSystemErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - if (!props?.dimensions?.Operation) { + if (!props?.dimensions?.Operation && !props?.dimensionsMap?.Operation) { // 'Operation' must be passed because its an operational metric. throw new Error("'Operation' dimension must be passed for the 'SystemErrors' metric."); } - const dimensions = { + const dimensionsMap = { TableName: this.tableName, ...props?.dimensions ?? {}, + ...props?.dimensionsMap ?? {}, }; - return this.metric('SystemErrors', { statistic: 'sum', ...props, dimensions }); + return this.metric('SystemErrors', { statistic: 'sum', ...props, dimensionsMap }); } /** @@ -783,7 +801,7 @@ abstract class TableBase extends Resource implements ITable { // overriding 'dimensions' here because this metric is an account metric. // see 'UserErrors' in https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/metrics-dimensions.html - return this.metric('UserErrors', { statistic: 'sum', ...props, dimensions: {} }); + return this.metric('UserErrors', { statistic: 'sum', ...props, dimensionsMap: {} }); } /** @@ -812,19 +830,19 @@ abstract class TableBase extends Resource implements ITable { * You can customize this by using the `statistic` and `period` properties. */ public metricSuccessfulRequestLatency(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - if (!props?.dimensions?.Operation) { + if (!props?.dimensions?.Operation && !props?.dimensionsMap?.Operation) { throw new Error("'Operation' dimension must be passed for the 'SuccessfulRequestLatency' metric."); } - const dimensions = { + const dimensionsMap = { TableName: this.tableName, - Operation: props.dimensions.Operation, + Operation: props.dimensionsMap?.Operation ?? props.dimensions?.Operation, }; return new cloudwatch.Metric({ - ...DynamoDBMetrics.successfulRequestLatencyAverage(dimensions), + ...DynamoDBMetrics.successfulRequestLatencyAverage(dimensionsMap), ...props, - dimensions, + dimensionsMap, }).attachTo(this); } @@ -882,7 +900,7 @@ abstract class TableBase extends Resource implements ITable { const metric = this.metric(metricName, { ...props, - dimensions: { + dimensionsMap: { TableName: this.tableName, Operation: operation, ...props?.dimensions, @@ -1032,7 +1050,7 @@ export class Table extends TableBase { if (!attrs.tableArn) { throw new Error('One of tableName or tableArn is required!'); } arn = attrs.tableArn; - const maybeTableName = stack.parseArn(attrs.tableArn).resourceName; + const maybeTableName = stack.splitArn(attrs.tableArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!maybeTableName) { throw new Error('ARN for DynamoDB table must be in the form: ...'); } name = maybeTableName; } else { @@ -1152,7 +1170,7 @@ export class Table extends TableBase { } if (props.replicationRegions && props.replicationRegions.length > 0) { - this.createReplicaTables(props.replicationRegions, props.replicationTimeout); + this.createReplicaTables(props.replicationRegions, props.replicationTimeout, props.waitForReplicationToFinish); } } @@ -1494,7 +1512,7 @@ export class Table extends TableBase { * * @param regions regions where to create tables */ - private createReplicaTables(regions: string[], timeout?: Duration) { + private createReplicaTables(regions: string[], timeout?: Duration, waitForReplicationToFinish?: boolean) { const stack = Stack.of(this); if (!Token.isUnresolved(stack.region) && regions.includes(stack.region)) { @@ -1524,6 +1542,7 @@ export class Table extends TableBase { properties: { TableName: this.tableName, Region: region, + SkipReplicationCompletedWait: waitForReplicationToFinish === undefined ? undefined : !waitForReplicationToFinish, }, }); currentRegion.node.addDependency( diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 71428a04816cf..d63167cf3d5de 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,14 +84,14 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "sinon": "^9.2.4", - "ts-jest": "^26.5.6" + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-dynamodb/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..1f9bb33819183 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import dynamodb = require('@aws-cdk/aws-dynamodb'); +import iam = require('@aws-cdk/aws-iam'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 56ab48f19f501..ded635da9983f 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -4,9 +4,10 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as kms from '@aws-cdk/aws-kms'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; +import { testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { App, Aws, CfnDeletionPolicy, ConstructNode, Duration, PhysicalName, RemovalPolicy, Resource, Stack, Tags } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; -import { testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { Construct } from 'constructs'; import { Attribute, @@ -39,7 +40,7 @@ const GSI_NAME = 'MyGSI'; const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.STRING }; const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.BINARY }; const GSI_NON_KEY = 'gsiNonKey'; -function* GSI_GENERATOR(): Generator { +function * GSI_GENERATOR(): Generator { let n = 0; while (true) { const globalSecondaryIndexProps: GlobalSecondaryIndexProps = { @@ -50,7 +51,7 @@ function* GSI_GENERATOR(): Generator { n++; } } -function* NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { +function * NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { let n = 0; while (true) { yield `${nonKeyPrefix}${n}`; @@ -62,7 +63,7 @@ function* NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { +function * LSI_GENERATOR(): Generator { let n = 0; while (true) { const localSecondaryIndexProps: LocalSecondaryIndexProps = { @@ -318,7 +319,7 @@ describe('default properties', () => { }); }); -test('when specifying every property', () => { +testDeprecated('when specifying every property', () => { const stack = new Stack(); const stream = new kinesis.Stream(stack, 'MyStream'); const table = new Table(stack, CONSTRUCT_NAME, { @@ -469,7 +470,7 @@ test('fails if encryption key is used with default encryption', () => { })).toThrow('`encryptionKey cannot be specified unless encryption is set to TableEncryption.CUSTOMER_MANAGED (it was set to ${encryptionType})`'); }); -test('fails if encryption key is used with serverSideEncryption', () => { +testDeprecated('fails if encryption key is used with serverSideEncryption', () => { const stack = new Stack(); const encryptionKey = new kms.Key(stack, 'Key', { enableKeyRotation: true, @@ -482,7 +483,7 @@ test('fails if encryption key is used with serverSideEncryption', () => { })).toThrow(/encryptionKey cannot be specified when serverSideEncryption is specified. Use encryption instead/); }); -test('fails if both encryption and serverSideEncryption is specified', () => { +testDeprecated('fails if both encryption and serverSideEncryption is specified', () => { const stack = new Stack(); expect(() => new Table(stack, 'Table A', { tableName: TABLE_NAME, @@ -1603,7 +1604,7 @@ describe('metrics', () => { }); - test('Can use metricSystemErrors without the TableName dimension', () => { + testDeprecated('Can use metricSystemErrors without the TableName dimension', () => { const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1617,7 +1618,7 @@ describe('metrics', () => { }); - test('Using metricSystemErrors without the Operation dimension will fail', () => { + testDeprecated('Using metricSystemErrors without the Operation dimension will fail', () => { const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1672,7 +1673,7 @@ describe('metrics', () => { }); - test('Can use metricSystemErrors on a Dynamodb Table', () => { + testDeprecated('Can use metricSystemErrors on a Dynamodb Table', () => { // GIVEN const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1680,7 +1681,7 @@ describe('metrics', () => { }); // THEN - expect(stack.resolve(table.metricSystemErrors({ dimensions: { TableName: table.tableName, Operation: 'GetItem' } }))).toEqual({ + expect(stack.resolve(table.metricSystemErrors({ dimensionsMap: { TableName: table.tableName, Operation: 'GetItem' } }))).toEqual({ period: Duration.minutes(5), dimensions: { TableName: { Ref: 'TableCD117FA1' }, Operation: 'GetItem' }, namespace: 'AWS/DynamoDB', @@ -1741,7 +1742,7 @@ describe('metrics', () => { partitionKey: { name: 'id', type: AttributeType.STRING }, }); - expect(table.metricSuccessfulRequestLatency({ dimensions: { Operation: 'GetItem' } }).dimensions).toEqual({ + expect(table.metricSuccessfulRequestLatency({ dimensionsMap: { Operation: 'GetItem' } }).dimensions).toEqual({ TableName: table.tableName, Operation: 'GetItem', }); @@ -1755,7 +1756,7 @@ describe('metrics', () => { partitionKey: { name: 'id', type: AttributeType.STRING }, }); - expect(() => table.metricSuccessfulRequestLatency({ dimensions: { TableName: table.tableName } })) + expect(() => table.metricSuccessfulRequestLatency({ dimensionsMap: { TableName: table.tableName } })) .toThrow(/'Operation' dimension must be passed for the 'SuccessfulRequestLatency' metric./); }); @@ -1769,7 +1770,7 @@ describe('metrics', () => { // THEN expect(stack.resolve(table.metricSuccessfulRequestLatency({ - dimensions: { + dimensionsMap: { TableName: table.tableName, Operation: 'GetItem', }, @@ -1859,7 +1860,7 @@ describe('grants', () => { testGrant(['*'], (p, t) => t.grantFullAccess(p)); }); - test('"Table.grantListStreams" allows principal to list all streams', () => { + testDeprecated('"Table.grantListStreams" allows principal to list all streams', () => { // GIVEN const stack = new Stack(); const user = new iam.User(stack, 'user'); @@ -2438,6 +2439,60 @@ describe('global', () => { }); }); + test('create replicas without waiting to finish replication', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new Table(stack, 'Table', { + partitionKey: { + name: 'id', + type: AttributeType.STRING, + }, + replicationRegions: [ + 'eu-west-2', + 'eu-central-1', + ], + waitForReplicationToFinish: false, + }); + + // THEN + expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Properties: { + TableName: { + Ref: 'TableCD117FA1', + }, + Region: 'eu-west-2', + SkipReplicationCompletedWait: true, + }, + Condition: 'TableStackRegionNotEqualseuwest2A03859E7', + }, ResourcePart.CompleteDefinition); + + expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Properties: { + TableName: { + Ref: 'TableCD117FA1', + }, + Region: 'eu-central-1', + SkipReplicationCompletedWait: true, + }, + Condition: 'TableStackRegionNotEqualseucentral199D46FC0', + }, ResourcePart.CompleteDefinition); + + expect(SynthUtils.toCloudFormation(stack).Conditions).toEqual({ + TableStackRegionNotEqualseuwest2A03859E7: { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, + ], + }, + TableStackRegionNotEqualseucentral199D46FC0: { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, + ], + }, + }); + }); + test('grantReadData', () => { const stack = new Stack(); const table = new Table(stack, 'Table', { diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts index 83495c01355cd..53e0bd7b20f17 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts @@ -1,5 +1,5 @@ import { App, RemovalPolicy, Stack, Tags } from '@aws-cdk/core'; -import { Attribute, AttributeType, BillingMode, ProjectionType, StreamViewType, Table } from '../lib'; +import { Attribute, AttributeType, BillingMode, ProjectionType, StreamViewType, Table, TableEncryption } from '../lib'; // CDK parameters const STACK_NAME = 'aws-cdk-dynamodb'; @@ -49,7 +49,7 @@ new Table(stack, TABLE, { const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, - serverSideEncryption: true, + encryption: TableEncryption.AWS_MANAGED, stream: StreamViewType.KEYS_ONLY, billingMode: BillingMode.PAY_PER_REQUEST, timeToLiveAttribute: 'timeToLive', diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts index e01d3dd996101..f016561c441db 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { App, RemovalPolicy, Stack, Tags } from '@aws-cdk/core'; -import { Attribute, AttributeType, ProjectionType, StreamViewType, Table } from '../lib'; +import { Attribute, AttributeType, ProjectionType, StreamViewType, Table, TableEncryption } from '../lib'; // CDK parameters const STACK_NAME = 'aws-cdk-dynamodb'; @@ -48,7 +48,7 @@ const table = new Table(stack, TABLE, { const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, - serverSideEncryption: true, + encryption: TableEncryption.AWS_MANAGED, stream: StreamViewType.KEYS_ONLY, timeToLiveAttribute: 'timeToLive', partitionKey: TABLE_PARTITION_KEY, diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index a9ac3794580b4..f673e24111e9f 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -93,17 +93,21 @@ must specify the environment where the stack will be deployed. You can gain full control over the availability zones selection strategy by overriding the Stack's [`get availabilityZones()`](https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/core/lib/stack.ts) method: -```ts +```text +// This example is only available in TypeScript + class MyStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // ... + } + get availabilityZones(): string[] { return ['us-west-2a', 'us-west-2b']; } - constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - ... - } } ``` @@ -121,11 +125,13 @@ The example below will place the endpoint into two AZs (`us-east-1a` and `us-eas in Isolated subnets: ```ts -new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { +declare const vpc: ec2.Vpc; + +new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { vpc, - service: new InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), + service: new ec2.InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), subnets: { - subnetType: SubnetType.ISOLATED, + subnetType: ec2.SubnetType.ISOLATED, availabilityZones: ['us-east-1a', 'us-east-1c'] } }); @@ -134,9 +140,13 @@ new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { You can also specify specific subnet objects for granular control: ```ts -new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { +declare const vpc: ec2.Vpc; +declare const subnet1: ec2.Subnet; +declare const subnet2: ec2.Subnet; + +new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { vpc, - service: new InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), + service: new ec2.InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), subnets: { subnets: [subnet1, subnet2] } @@ -193,14 +203,16 @@ gets routed, pass `allowAllTraffic: false` and access the `NatInstanceProvider.connections` member after having passed it to the VPC: ```ts -const provider = NatProvider.instance({ - instanceType: /* ... */, +declare const instanceType: ec2.InstanceType; + +const provider = ec2.NatProvider.instance({ + instanceType, allowAllTraffic: false, }); -new Vpc(stack, 'TheVPC', { +new ec2.Vpc(this, 'TheVPC', { natGatewayProvider: provider, }); -provider.connections.allowFrom(Peer.ipv4('1.2.3.4/8'), Port.tcp(80)); +provider.connections.allowFrom(ec2.Peer.ipv4('1.2.3.4/8'), ec2.Port.tcp(80)); ``` ### Advanced Subnet Configuration @@ -284,6 +296,8 @@ DatabaseSubnet3 |`ISOLATED`|`10.0.6.32/28`|#3|Only routes within the VPC If you need access to the internet gateway, you can get its ID like so: ```ts +declare const vpc: ec2.Vpc; + const igwId = vpc.internetGatewayId; ``` @@ -305,18 +319,19 @@ Internet Gateway created for the public subnet - perhaps for routing a VPN connection - you can do so like this: ```ts -const vpc = ec2.Vpc(this, "VPC", { +const vpc = new ec2.Vpc(this, "VPC", { subnetConfiguration: [{ - subnetType: SubnetType.PUBLIC, + subnetType: ec2.SubnetType.PUBLIC, name: 'Public', },{ - subnetType: SubnetType.ISOLATED, + subnetType: ec2.SubnetType.ISOLATED, name: 'Isolated', }] -}) -(vpc.isolatedSubnets[0] as Subnet).addRoute("StaticRoute", { - routerId: vpc.internetGatewayId, - routerType: RouterType.GATEWAY, +}); + +(vpc.isolatedSubnets[0] as ec2.Subnet).addRoute("StaticRoute", { + routerId: vpc.internetGatewayId!, + routerType: ec2.RouterType.GATEWAY, destinationCidrBlock: "8.8.8.8/32", }) ``` @@ -412,7 +427,7 @@ following limitations: Using `Vpc.fromVpcAttributes()` looks like this: ```ts -const vpc = ec2.Vpc.fromVpcAttributes(stack, 'VPC', { +const vpc = ec2.Vpc.fromVpcAttributes(this, 'VPC', { vpcId: 'vpc-1234', availabilityZones: ['us-east-1a', 'us-east-1b'], @@ -423,7 +438,7 @@ const vpc = ec2.Vpc.fromVpcAttributes(stack, 'VPC', { privateSubnetIds: Fn.importListValue('PrivateSubnetIds', 2), // OR: split an imported string to a list of known length - isolatedSubnetIds: Fn.split(',', ssm.StringParameter.valueForStringParameter(stack, `MyParameter`), 2), + isolatedSubnetIds: Fn.split(',', ssm.StringParameter.valueForStringParameter(this, `MyParameter`), 2), }); ``` @@ -457,7 +472,11 @@ have security groups, you have to add an **Egress** rule to one Security Group, and an **Ingress** rule to the other. The connections object will automatically take care of this for you: -```ts fixture=conns +```ts +declare const loadBalancer: elbv2.ApplicationLoadBalancer; +declare const appFleet: autoscaling.AutoScalingGroup; +declare const dbFleet: autoscaling.AutoScalingGroup; + // Allow connections from anywhere loadBalancer.connections.allowFromAnyIpv4(ec2.Port.tcp(443), 'Allow inbound HTTPS'); @@ -472,7 +491,10 @@ appFleet.connections.allowTo(dbFleet, ec2.Port.tcp(443), 'App can call database' There are various classes that implement the connection peer part: -```ts fixture=conns +```ts +declare const appFleet: autoscaling.AutoScalingGroup; +declare const dbFleet: autoscaling.AutoScalingGroup; + // Simple connection peers let peer = ec2.Peer.ipv4('10.0.0.0/16'); peer = ec2.Peer.anyIpv4(); @@ -484,7 +506,11 @@ appFleet.connections.allowTo(peer, ec2.Port.tcp(443), 'Allow outbound HTTPS'); Any object that has a security group can itself be used as a connection peer: -```ts fixture=conns +```ts +declare const fleet1: autoscaling.AutoScalingGroup; +declare const fleet2: autoscaling.AutoScalingGroup; +declare const appFleet: autoscaling.AutoScalingGroup; + // These automatically create appropriate ingress and egress rules in both security groups fleet1.connections.allowTo(fleet2, ec2.Port.tcp(80), 'Allow between fleets'); @@ -518,7 +544,11 @@ If the object you're calling the peering method on has a default port associated For example: -```ts fixture=conns +```ts +declare const listener: elbv2.ApplicationListener; +declare const appFleet: autoscaling.AutoScalingGroup; +declare const rdsDatabase: rds.DatabaseCluster; + // Port implicit in listener listener.connections.allowDefaultPortFromAnyIpv4('Allow public'); @@ -547,6 +577,30 @@ const mySecurityGroupWithoutInlineRules = new ec2.SecurityGroup(this, 'SecurityG mySecurityGroupWithoutInlineRules.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world'); ``` +### Importing an existing security group + +If you know the ID and the configuration of the security group to import, you can use `SecurityGroup.fromSecurityGroupId`: + +```ts +const sg = ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroupImport', 'sg-1234', { + allowAllOutbound: true, +}); +``` + +Alternatively, use lookup methods to import security groups if you do not know the ID or the configuration details. Method `SecurityGroup.fromLookupByName` looks up a security group if the secruity group ID is unknown. + +```ts fixture=with-vpc +const sg = ec2.SecurityGroup.fromLookupByName(this, 'SecurityGroupLookup', 'security-group-name', vpc); +``` + +If the security group ID is known and configuration details are unknown, use method `SecurityGroup.fromLookupById` instead. This method will lookup property `allowAllOutbound` from the current configuration of the security group. + +```ts +const sg = ec2.SecurityGroup.fromLookupById(this, 'SecurityGroupLookup', 'sg-1234'); +``` + +The result of `SecurityGroup.fromLookupByName` and `SecurityGroup.fromLookupById` operations will be written to a file called `cdk.context.json`. You must commit this file to source control so that the lookup values are available in non-privileged environments such as CI build steps, and to ensure your template builds are repeatable. + ## Machine Images (AMIs) AMIs control the OS that gets launched when you start your EC2 instance. The EC2 @@ -637,9 +691,11 @@ By default, CDK will place a VPC endpoint in one subnet per AZ. If you wish to o use the `subnets` parameter as follows: ```ts -new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { +declare const vpc: ec2.Vpc; + +new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { vpc, - service: new InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), + service: new ec2.InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), // Choose which availability zones to place the VPC endpoint in, based on // available AZs subnets: { @@ -654,9 +710,11 @@ AZs an endpoint service is available in, and will ensure the VPC endpoint is not These AZs will be stored in cdk.context.json. ```ts -new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { +declare const vpc: ec2.Vpc; + +new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { vpc, - service: new InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), + service: new ec2.InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), // Choose which availability zones to place the VPC endpoint in, based on // available AZs lookupSupportedAzs: true @@ -668,7 +726,12 @@ create VPC endpoints without having to configure name, ports, etc. For example, use in your VPC: ``` ts -new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { vpc, service: InterfaceVpcEndpointAwsService.KEYSPACES }); +declare const vpc: ec2.Vpc; + +new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { + vpc, + service: ec2.InterfaceVpcEndpointAwsService.KEYSPACES, +}); ``` #### Security groups for interface VPC endpoints @@ -678,7 +741,9 @@ automatically allowed from the VPC CIDR. Use the `connections` object to allow traffic to flow to the endpoint: -```ts fixture=conns +```ts +declare const myEndpoint: ec2.InterfaceVpcEndpoint; + myEndpoint.connections.allowDefaultPortFromAnyIpv4(); ``` @@ -689,10 +754,13 @@ Alternatively, existing security groups can be used by specifying the `securityG A VPC endpoint service enables you to expose a Network Load Balancer(s) as a provider service to consumers, who connect to your service over a VPC endpoint. You can restrict access to your service via allowed principals (anything that extends ArnPrincipal), and require that new connections be manually accepted. ```ts -new VpcEndpointService(this, 'EndpointService', { +declare const networkLoadBalancer1: elbv2.NetworkLoadBalancer; +declare const networkLoadBalancer2: elbv2.NetworkLoadBalancer; + +new ec2.VpcEndpointService(this, 'EndpointService', { vpcEndpointServiceLoadBalancers: [networkLoadBalancer1, networkLoadBalancer2], acceptanceRequired: true, - allowedPrincipals: [new ArnPrincipal('arn:aws:iam::123456789012:root')] + allowedPrincipals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:root')] }); ``` @@ -700,9 +768,11 @@ Endpoint services support private DNS, which makes it easier for clients to conn You can enable private DNS on an endpoint service like so: ```ts -import { VpcEndpointServiceDomainName } from '@aws-cdk/aws-route53'; +import { HostedZone, VpcEndpointServiceDomainName } from '@aws-cdk/aws-route53'; +declare const zone: HostedZone; +declare const vpces: ec2.VpcEndpointService; -new VpcEndpointServiceDomainName(stack, 'EndpointDomain', { +new VpcEndpointServiceDomainName(this, 'EndpointDomain', { endpointService: vpces, domainName: 'my-stuff.aws-cdk.dev', publicHostedZone: zone, @@ -744,7 +814,7 @@ By default, a new security group is created and logging is enabled. Moreover, a authorize all users to the VPC CIDR is created. To customize authorization rules, set the `authorizeAllUsersToVpcCidr` prop to `false` -and use `addaddAuthorizationRule()`: +and use `addAuthorizationRule()`: ```ts fixture=client-vpn const endpoint = vpc.addClientVpnEndpoint('Endpoint', { @@ -796,7 +866,15 @@ For the full set of capabilities of this system, see the documentation for Here is an example of applying some configuration to an instance: ```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + new ec2.Instance(this, 'Instance', { + vpc, + instanceType, + machineImage, + // Showing the most complex setup, if you have simpler requirements // you can use `CloudFormationInit.fromElements()`. init: ec2.CloudFormationInit.fromConfigSets({ @@ -812,9 +890,9 @@ new ec2.Instance(this, 'Instance', { config: new ec2.InitConfig([ // Create a JSON file from tokens (can also create other files) ec2.InitFile.fromObject('/etc/stack.json', { - stackId: stack.stackId, - stackName: stack.stackName, - region: stack.region, + stackId: Stack.of(this).stackId, + stackName: Stack.of(this).stackName, + region: Stack.of(this).region, }), // Create a group and user @@ -834,10 +912,10 @@ new ec2.Instance(this, 'Instance', { timeout: Duration.minutes(30), // Optional, whether to include the --url argument when running cfn-init and cfn-signal commands (false by default) - includeUrl: true + includeUrl: true, // Optional, whether to include the --role argument when running cfn-init and cfn-signal commands (false by default) - includeRole: true + includeRole: true, }, }); ``` @@ -849,11 +927,13 @@ config writes a config file for nginx, extracts an archive to the root directory restarts nginx so that it picks up the new config and files: ```ts +declare const myBucket: s3.Bucket; + const handle = new ec2.InitServiceRestartHandle(); ec2.CloudFormationInit.fromElements( ec2.InitFile.fromString('/etc/nginx/nginx.conf', '...', { serviceRestartHandles: [handle] }), - ec2.InitSource.fromBucket('/var/www/html', myBucket, 'html.zip', { serviceRestartHandles: [handle] }), + ec2.InitSource.fromS3Object('/var/www/html', myBucket, 'html.zip', { serviceRestartHandles: [handle] }), ec2.InitService.enable('nginx', { serviceRestartHandle: handle, }) @@ -887,16 +967,16 @@ with the command `aws ec2-instance-connect send-ssh-public-key` to provide your EBS volume for the bastion host can be encrypted like: -```ts - const host = new ec2.BastionHostLinux(stack, 'BastionHost', { - vpc, - blockDevices: [{ - deviceName: 'EBSBastionHost', - volume: BlockDeviceVolume.ebs(10, { - encrypted: true, - }), - }], - }); +```ts fixture=with-vpc +const host = new ec2.BastionHostLinux(this, 'BastionHost', { + vpc, + blockDevices: [{ + deviceName: 'EBSBastionHost', + volume: ec2.BlockDeviceVolume.ebs(10, { + encrypted: true, + }), + }], +}); ``` ### Block Devices @@ -906,8 +986,17 @@ root device (`/dev/sda1`) size to 50 GiB, and adds another EBS-backed device map size: ```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + new ec2.Instance(this, 'Instance', { + vpc, + instanceType, + machineImage, + // ... + blockDevices: [ { deviceName: '/dev/sda1', @@ -931,15 +1020,12 @@ A notable restriction is that a Volume can only be attached to instances in the The following demonstrates how to create a 500 GiB encrypted Volume in the `us-west-2a` availability zone, and give a role the ability to attach that Volume to a specific instance: ```ts -const instance = new ec2.Instance(this, 'Instance', { - // ... -}); -const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.AccountRootPrincipal(), -}); +declare const instance: ec2.Instance; +declare const role: iam.Role; + const volume = new ec2.Volume(this, 'Volume', { availabilityZone: 'us-west-2a', - size: cdk.Size.gibibytes(500), + size: Size.gibibytes(500), encrypted: true, }); @@ -952,12 +1038,8 @@ If you need to grant an instance the ability to attach/detach an EBS volume to/f will lead to an unresolvable circular reference between the instance role and the instance. In this case, use `grantAttachVolumeByResourceTag` and `grantDetachVolumeByResourceTag` as follows: ```ts -const instance = new ec2.Instance(this, 'Instance', { - // ... -}); -const volume = new ec2.Volume(this, 'Volume', { - // ... -}); +declare const instance: ec2.Instance; +declare const volume: ec2.Volume; const attachGrant = volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance]); const detachGrant = volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance]); @@ -973,12 +1055,9 @@ to attach and detach your Volumes to/from instances, and how to format them for The following is a sample skeleton of EC2 UserData that can be used to attach a Volume to the Linux instance that it is running on: ```ts -const volume = new ec2.Volume(this, 'Volume', { - // ... -}); -const instance = new ec2.Instance(this, 'Instance', { - // ... -}); +declare const instance: ec2.Instance; +declare const volume: ec2.Volume; + volume.grantAttachVolumeByResourceTag(instance.grantPrincipal, [instance]); const targetDevice = '/dev/xvdz'; instance.userData.addCommands( @@ -994,6 +1073,42 @@ instance.userData.addCommands( ); ``` +### Configuring Instance Metadata Service (IMDS) + +#### Toggling IMDSv1 + +You can configure [EC2 Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) options to either +allow both IMDSv1 and IMDSv2 or enforce IMDSv2 when interacting with the IMDS. + +To do this for a single `Instance`, you can use the `requireImdsv2` property. +The example below demonstrates IMDSv2 being required on a single `Instance`: + +```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + +new ec2.Instance(this, 'Instance', { + vpc, + instanceType, + machineImage, + + // ... + + requireImdsv2: true, +}); +``` + +You can also use the either the `InstanceRequireImdsv2Aspect` for EC2 instances or the `LaunchTemplateRequireImdsv2Aspect` for EC2 launch templates +to apply the operation to multiple instances or launch templates, respectively. + +The following example demonstrates how to use the `InstanceRequireImdsv2Aspect` to require IMDSv2 for all EC2 instances in a stack: + +```ts +const aspect = new ec2.InstanceRequireImdsv2Aspect(); +Aspects.of(this).add(aspect); +``` + ## VPC Flow Logs VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (). @@ -1003,6 +1118,8 @@ By default a flow log will be created with CloudWatch Logs as the destination. You can create a flow log like this: ```ts +declare const vpc: ec2.Vpc; + new ec2.FlowLog(this, 'FlowLog', { resourceType: ec2.FlowLogResourceType.fromVpc(vpc) }) @@ -1039,6 +1156,8 @@ If you want to customize any of the destination resources you can provide your o *CloudWatch Logs* ```ts +declare const vpc: ec2.Vpc; + const logGroup = new logs.LogGroup(this, 'MyCustomLogGroup'); const role = new iam.Role(this, 'MyCustomRole', { @@ -1054,6 +1173,7 @@ new ec2.FlowLog(this, 'FlowLog', { *S3* ```ts +declare const vpc: ec2.Vpc; const bucket = new s3.Bucket(this, 'MyCustomBucket'); @@ -1076,19 +1196,24 @@ User data enables you to run a script when your instances start up. In order to A user data could be configured to run a script found in an asset through the following: ```ts -const asset = new Asset(this, 'Asset', {path: path.join(__dirname, 'configure.sh')}); -const instance = new ec2.Instance(this, 'Instance', { - // ... - }); +import { Asset } from '@aws-cdk/aws-s3-assets'; + +declare const instance: ec2.Instance; + +const asset = new Asset(this, 'Asset', { + path: './configure.sh' +}); + const localPath = instance.userData.addS3DownloadCommand({ bucket:asset.bucket, bucketKey:asset.s3ObjectKey, + region: 'us-east-1', // Optional }); instance.userData.addExecuteFileCommand({ filePath:localPath, arguments: '--verbose -y' }); -asset.grantRead( instance.role ); +asset.grantRead(instance.role); ``` ### Multipart user data @@ -1124,7 +1249,7 @@ multipartUserData.addPart(ec2.MultipartBody.fromUserData(bootHookConf, 'text/clo // Execute the rest of setup multipartUserData.addPart(ec2.MultipartBody.fromUserData(setupCommands)); -new ec2.LaunchTemplate(stack, '', { +new ec2.LaunchTemplate(this, '', { userData: multipartUserData, blockDevices: [ // Block device configuration rest @@ -1144,7 +1269,7 @@ method on `MultipartUserData` with the `makeDefault` argument set to `true`: ```ts const multipartUserData = new ec2.MultipartUserData(); const commandsUserData = ec2.UserData.forLinux(); -multipartUserData.addUserDataPart(commandsUserData, MultipartBody.SHELL_SCRIPT, true); +multipartUserData.addUserDataPart(commandsUserData, ec2.MultipartBody.SHELL_SCRIPT, true); // Adding commands to the multipartUserData adds them to commandsUserData, and vice-versa. multipartUserData.addCommands('touch /root/multi.txt'); @@ -1165,14 +1290,14 @@ Importing an existing subnet looks like this: ```ts // Supply all properties -const subnet = Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', { +const subnet1 = ec2.Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', { subnetId: 's-1234', availabilityZone: 'pub-az-4465', routeTableId: 'rt-145' }); // Supply only subnet id -const subnet = Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234'); +const subnet2 = ec2.Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234'); ``` ## Launch Templates @@ -1186,10 +1311,10 @@ an instance. For information on Launch Templates please see the The following demonstrates how to create a launch template with an Amazon Machine Image, and security group. ```ts -const vpc = new ec2.Vpc(...); -// ... +declare const vpc: ec2.Vpc; + const template = new ec2.LaunchTemplate(this, 'LaunchTemplate', { - machineImage: new ec2.AmazonMachineImage(), + machineImage: ec2.MachineImage.latestAmazonLinux(), securityGroup: new ec2.SecurityGroup(this, 'LaunchTemplateSG', { vpc: vpc, }), diff --git a/packages/@aws-cdk/aws-ec2/lib/aspects/index.ts b/packages/@aws-cdk/aws-ec2/lib/aspects/index.ts new file mode 100644 index 0000000000000..5685e9b46d036 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/aspects/index.ts @@ -0,0 +1 @@ +export * from './require-imdsv2-aspect'; diff --git a/packages/@aws-cdk/aws-ec2/lib/aspects/require-imdsv2-aspect.ts b/packages/@aws-cdk/aws-ec2/lib/aspects/require-imdsv2-aspect.ts new file mode 100644 index 0000000000000..f1a5270f1fb08 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/aspects/require-imdsv2-aspect.ts @@ -0,0 +1,150 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnLaunchTemplate } from '../ec2.generated'; +import { Instance } from '../instance'; +import { LaunchTemplate } from '../launch-template'; + +/** + * Properties for `RequireImdsv2Aspect`. + */ +interface RequireImdsv2AspectProps { + /** + * Whether warning annotations from this Aspect should be suppressed or not. + * + * @default - false + */ + readonly suppressWarnings?: boolean; +} + +/** + * Base class for Aspect that makes IMDSv2 required. + */ +abstract class RequireImdsv2Aspect implements cdk.IAspect { + protected readonly suppressWarnings: boolean; + + constructor(props?: RequireImdsv2AspectProps) { + this.suppressWarnings = props?.suppressWarnings ?? false; + } + + abstract visit(node: cdk.IConstruct): void; + + /** + * Adds a warning annotation to a node, unless `suppressWarnings` is true. + * + * @param node The scope to add the warning to. + * @param message The warning message. + */ + protected warn(node: cdk.IConstruct, message: string) { + if (this.suppressWarnings !== true) { + cdk.Annotations.of(node).addWarning(`${RequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`); + } + } +} + +/** + * Properties for `InstanceRequireImdsv2Aspect`. + */ +export interface InstanceRequireImdsv2AspectProps extends RequireImdsv2AspectProps { + /** + * Whether warnings that would be raised when an Instance is associated with an existing Launch Template + * should be suppressed or not. + * + * You can set this to `true` if `LaunchTemplateImdsAspect` is being used alongside this Aspect to + * suppress false-positive warnings because any Launch Templates associated with Instances will still be covered. + * + * @default - false + */ + readonly suppressLaunchTemplateWarning?: boolean; +} + +/** + * Aspect that applies IMDS configuration on EC2 Instance constructs. + * + * This aspect configures IMDS on an EC2 instance by creating a Launch Template with the + * IMDS configuration and associating that Launch Template with the instance. If an Instance + * is already associated with a Launch Template, a warning will (optionally) be added to the + * construct node and it will be skipped. + * + * To cover Instances already associated with Launch Templates, use `LaunchTemplateImdsAspect`. + */ +export class InstanceRequireImdsv2Aspect extends RequireImdsv2Aspect { + private readonly suppressLaunchTemplateWarning: boolean; + + constructor(props?: InstanceRequireImdsv2AspectProps) { + super(props); + this.suppressLaunchTemplateWarning = props?.suppressLaunchTemplateWarning ?? false; + } + + visit(node: cdk.IConstruct): void { + if (!(node instanceof Instance)) { + return; + } + if (node.instance.launchTemplate !== undefined) { + this.warn(node, 'Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.'); + return; + } + + const name = `${node.node.id}LaunchTemplate`; + const launchTemplate = new CfnLaunchTemplate(node, 'LaunchTemplate', { + launchTemplateData: { + metadataOptions: { + httpTokens: 'required', + }, + }, + launchTemplateName: name, + }); + node.instance.launchTemplate = { + launchTemplateName: name, + version: launchTemplate.getAtt('LatestVersionNumber').toString(), + }; + } + + protected warn(node: cdk.IConstruct, message: string) { + if (this.suppressLaunchTemplateWarning !== true) { + super.warn(node, message); + } + } +} + +/** + * Properties for `LaunchTemplateRequireImdsv2Aspect`. + */ +export interface LaunchTemplateRequireImdsv2AspectProps extends RequireImdsv2AspectProps {} + +/** + * Aspect that applies IMDS configuration on EC2 Launch Template constructs. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-metadataoptions.html + */ +export class LaunchTemplateRequireImdsv2Aspect extends RequireImdsv2Aspect { + constructor(props?: LaunchTemplateRequireImdsv2AspectProps) { + super(props); + } + + visit(node: cdk.IConstruct): void { + if (!(node instanceof LaunchTemplate)) { + return; + } + + const launchTemplate = node.node.tryFindChild('Resource') as CfnLaunchTemplate; + const data = launchTemplate.launchTemplateData; + if (cdk.isResolvableObject(data)) { + this.warn(node, 'LaunchTemplateData is a CDK token.'); + return; + } + + const metadataOptions = (data as CfnLaunchTemplate.LaunchTemplateDataProperty).metadataOptions; + if (cdk.isResolvableObject(metadataOptions)) { + this.warn(node, 'LaunchTemplateData.MetadataOptions is a CDK token.'); + return; + } + + const newData: CfnLaunchTemplate.LaunchTemplateDataProperty = { + ...data, + metadataOptions: { + ...metadataOptions, + httpTokens: 'required', + }, + }; + launchTemplate.launchTemplateData = newData; + } +} diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index 1b10e6fa1d566..4b0741044e4dd 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -1,3 +1,4 @@ +export * from './aspects'; export * from './bastion-host'; export * from './connections'; export * from './cfn-init'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 1b778fee66e6e..ec5f2b91e17e7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -68,6 +68,26 @@ export enum InstanceClass { */ M5AD = 'm5ad', + /** + * Standard instances for high performance computing, 5th generation + */ + STANDARD5_HIGH_PERFORMANCE = 'm5n', + + /** + * Standard instances for high performance computing, 5th generation + */ + M5N = 'm5n', + + /** + * Standard instances with local NVME drive for high performance computing, 5th generation + */ + STANDARD5_NVME_DRIVE_HIGH_PERFORMANCE = 'm5dn', + + /** + * Standard instances with local NVME drive for high performance computing, 5th generation + */ + M5DN = 'm5dn', + /** * Memory optimized instances, 3rd generation */ @@ -208,11 +228,26 @@ export enum InstanceClass { */ C5 = 'c5', + /** + * Compute optimized instances, 6th generation + */ + COMPUTE6_INTEL = 'c6i', + + /** + * Compute optimized instances, 6th generation + */ + C6I = 'c6i', + /** * Compute optimized instances with local NVME drive, 5th generation */ COMPUTE5_NVME_DRIVE = 'c5d', + /** + * Compute optimized instances with local NVME drive, 5th generation + */ + C5D = 'c5d', + /** * Compute optimized instances based on AMD EPYC, 5th generation. */ @@ -224,9 +259,14 @@ export enum InstanceClass { C5A = 'c5a', /** - * Compute optimized instances with local NVME drive, 5th generation + * Compute optimized instances with local NVME drive based on AMD EPYC, 5th generation. */ - C5D = 'c5d', + COMPUTE5_AMD_NVME_DRIVE = 'c5ad', + + /** + * Compute optimized instances with local NVME drive based on AMD EPYC, 5th generation. + */ + C5AD = 'c5ad', /** * Compute optimized instances for high performance computing, 5th generation @@ -372,6 +412,20 @@ export enum InstanceClass { */ X1E = 'x1e', + /** + * Memory-intensive instances, 2nd generation with Graviton2 processors + * + * This instance type can be used only in RDS. It is not supported in EC2. + */ + MEMORY_INTENSIVE_2_GRAVITON2 = 'x2g', + + /** + * Memory-intensive instances, 2nd generation with Graviton2 processors + * + * This instance type can be used only in RDS. It is not supported in EC2. + */ + X2G = 'x2g', + /** * Memory-intensive instances, 2nd generation with Graviton2 processors and local NVME drive */ @@ -412,6 +466,16 @@ export enum InstanceClass { */ G4DN = 'g4dn', + /** + * Graphics-optimized instances, 5th generation + */ + GRAPHICS5 = 'g5', + + /** + * Graphics-optimized instances, 5th generation + */ + G5 = 'g5', + /** * Parallel-processing optimized instances, 2nd generation */ @@ -428,10 +492,20 @@ export enum InstanceClass { PARALLEL3 = 'p3', /** - * Parallel-processing optimized instances, 3nd generation + * Parallel-processing optimized instances, 3rd generation */ P3 = 'p3', + /** + * Parallel-processing optimized instances, 4th generation + */ + PARALLEL4 = 'p4d', + + /** + * Parallel-processing optimized instances, 4th generation + */ + P4D = 'p4d', + /** * Arm processor based instances, 1st generation */ @@ -602,6 +676,11 @@ export enum InstanceSize { */ XLARGE32 = '32xlarge', + /** + * Instance size XLARGE48 (48xlarge) + */ + XLARGE48 = '48xlarge', + /** * Instance size METAL (metal) */ diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index 813d4d5f43880..85b05fc71734b 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -1,8 +1,9 @@ import * as crypto from 'crypto'; import * as iam from '@aws-cdk/aws-iam'; -import { Annotations, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '@aws-cdk/core'; +import { Annotations, Aspects, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { InstanceRequireImdsv2Aspect } from './aspects'; import { CloudFormationInit } from './cfn-init'; import { Connections, IConnectable } from './connections'; import { CfnInstance } from './ec2.generated'; @@ -230,6 +231,13 @@ export interface InstanceProps { * @default - default options */ readonly initOptions?: ApplyCloudFormationInitOptions; + + /** + * Whether IMDSv2 should be required on this instance. + * + * @default - false + */ + readonly requireImdsv2?: boolean; } /** @@ -408,6 +416,10 @@ export class Instance extends Resource implements IInstance { return `${originalLogicalId}${digest}`; }, })); + + if (props.requireImdsv2) { + Aspects.of(this).add(new InstanceRequireImdsv2Aspect()); + } } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/launch-template.ts b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts index fdc03755c0268..ae7f5316c01af 100644 --- a/packages/@aws-cdk/aws-ec2/lib/launch-template.ts +++ b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts @@ -12,8 +12,10 @@ import { TagType, Tags, Token, + Aspects, } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { LaunchTemplateRequireImdsv2Aspect } from '.'; import { Connections, IConnectable } from './connections'; import { CfnLaunchTemplate } from './ec2.generated'; import { InstanceType } from './instance-types'; @@ -332,6 +334,13 @@ export interface LaunchTemplateProps { * @default No security group is assigned. */ readonly securityGroup?: ISecurityGroup; + + /** + * Whether IMDSv2 should be required on launched instances. + * + * @default - false + */ + readonly requireImdsv2?: boolean; } /** @@ -637,6 +646,10 @@ export class LaunchTemplate extends Resource implements ILaunchTemplate, iam.IGr this.latestVersionNumber = resource.attrLatestVersionNumber; this.launchTemplateId = resource.ref; this.versionNumber = Token.asString(resource.getAtt('LatestVersionNumber')); + + if (props.requireImdsv2) { + Aspects.of(this).add(new LaunchTemplateRequireImdsv2Aspect()); + } } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/nat.ts b/packages/@aws-cdk/aws-ec2/lib/nat.ts index 4743799762b51..f4ce8aa242053 100644 --- a/packages/@aws-cdk/aws-ec2/lib/nat.ts +++ b/packages/@aws-cdk/aws-ec2/lib/nat.ts @@ -233,10 +233,8 @@ class NatGatewayProvider extends NatProvider { // Create the NAT gateways let i = 0; for (const sub of options.natSubnets) { - const gateway = sub.addNatGateway(); - if (this.props.eipAllocationIds) { - gateway.allocationId = pickN(i, this.props.eipAllocationIds); - } + const eipAllocationId = this.props.eipAllocationIds ? pickN(i, this.props.eipAllocationIds) : undefined; + const gateway = sub.addNatGateway(eipAllocationId); this.gateways.add(sub.availabilityZone, gateway.ref); i++; } diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 2c01da7aef943..163cb8079ccdb 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -68,6 +68,8 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { public readonly connections: Connections = new Connections({ securityGroups: [this] }); public readonly defaultPort?: Port; + private peerAsTokenCount: number = 0; + constructor(scope: Construct, id: string, props?: ResourceProps) { super(scope, id, props); @@ -83,7 +85,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { description = `from ${peer.uniqueId}:${connection}`; } - const [scope, id] = determineRuleScope(this, peer, connection, 'from', remoteRule); + const [scope, id] = this.determineRuleScope(peer, connection, 'from', remoteRule); // Skip duplicates if (scope.node.tryFindChild(id) === undefined) { @@ -101,7 +103,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { description = `to ${peer.uniqueId}:${connection}`; } - const [scope, id] = determineRuleScope(this, peer, connection, 'to', remoteRule); + const [scope, id] = this.determineRuleScope(peer, connection, 'to', remoteRule); // Skip duplicates if (scope.node.tryFindChild(id) === undefined) { @@ -121,75 +123,82 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { public toEgressRuleConfig(): any { return { destinationSecurityGroupId: this.securityGroupId }; } -} -/** - * Determine where to parent a new ingress/egress rule - * - * A SecurityGroup rule is parented under the group it's related to, UNLESS - * we're in a cross-stack scenario with another Security Group. In that case, - * we respect the 'remoteRule' flag and will parent under the other security - * group. - * - * This is necessary to avoid cyclic dependencies between stacks, since both - * ingress and egress rules will reference both security groups, and a naive - * parenting will lead to the following situation: - * - * ╔════════════════════╗ ╔════════════════════╗ - * ║ ┌───────────┐ ║ ║ ┌───────────┐ ║ - * ║ │ GroupA │◀────╬─┐ ┌───╬───▶│ GroupB │ ║ - * ║ └───────────┘ ║ │ │ ║ └───────────┘ ║ - * ║ ▲ ║ │ │ ║ ▲ ║ - * ║ │ ║ │ │ ║ │ ║ - * ║ │ ║ │ │ ║ │ ║ - * ║ ┌───────────┐ ║ └───┼───╬────┌───────────┐ ║ - * ║ │ EgressA │─────╬─────┘ ║ │ IngressB │ ║ - * ║ └───────────┘ ║ ║ └───────────┘ ║ - * ║ ║ ║ ║ - * ╚════════════════════╝ ╚════════════════════╝ - * - * By having the ability to switch the parent, we avoid the cyclic reference by - * keeping all rules in a single stack. - * - * If this happens, we also have to change the construct ID, because - * otherwise we might have two objects with the same ID if we have - * multiple reversed security group relationships. - * - * ╔═══════════════════════════════════╗ - * ║┌───────────┐ ║ - * ║│ GroupB │ ║ - * ║└───────────┘ ║ - * ║ ▲ ║ - * ║ │ ┌───────────┐ ║ - * ║ ├────"from A"──│ IngressB │ ║ - * ║ │ └───────────┘ ║ - * ║ │ ┌───────────┐ ║ - * ║ ├─────"to B"───│ EgressA │ ║ - * ║ │ └───────────┘ ║ - * ║ │ ┌───────────┐ ║ - * ║ └─────"to B"───│ EgressC │ ║ <-- oops - * ║ └───────────┘ ║ - * ╚═══════════════════════════════════╝ - */ -function determineRuleScope( - group: SecurityGroupBase, - peer: IPeer, - connection: Port, - fromTo: 'from' | 'to', - remoteRule?: boolean): [SecurityGroupBase, string] { - - if (remoteRule && SecurityGroupBase.isSecurityGroup(peer) && differentStacks(group, peer)) { - // Reversed - const reversedFromTo = fromTo === 'from' ? 'to' : 'from'; - return [peer, `${group.uniqueId}:${connection} ${reversedFromTo}`]; - } else { - // Regular (do old ID escaping to in order to not disturb existing deployments) - return [group, `${fromTo} ${renderPeer(peer)}:${connection}`.replace('/', '_')]; + /** + * Determine where to parent a new ingress/egress rule + * + * A SecurityGroup rule is parented under the group it's related to, UNLESS + * we're in a cross-stack scenario with another Security Group. In that case, + * we respect the 'remoteRule' flag and will parent under the other security + * group. + * + * This is necessary to avoid cyclic dependencies between stacks, since both + * ingress and egress rules will reference both security groups, and a naive + * parenting will lead to the following situation: + * + * ╔════════════════════╗ ╔════════════════════╗ + * ║ ┌───────────┐ ║ ║ ┌───────────┐ ║ + * ║ │ GroupA │◀────╬─┐ ┌───╬───▶│ GroupB │ ║ + * ║ └───────────┘ ║ │ │ ║ └───────────┘ ║ + * ║ ▲ ║ │ │ ║ ▲ ║ + * ║ │ ║ │ │ ║ │ ║ + * ║ │ ║ │ │ ║ │ ║ + * ║ ┌───────────┐ ║ └───┼───╬────┌───────────┐ ║ + * ║ │ EgressA │─────╬─────┘ ║ │ IngressB │ ║ + * ║ └───────────┘ ║ ║ └───────────┘ ║ + * ║ ║ ║ ║ + * ╚════════════════════╝ ╚════════════════════╝ + * + * By having the ability to switch the parent, we avoid the cyclic reference by + * keeping all rules in a single stack. + * + * If this happens, we also have to change the construct ID, because + * otherwise we might have two objects with the same ID if we have + * multiple reversed security group relationships. + * + * ╔═══════════════════════════════════╗ + * ║┌───────────┐ ║ + * ║│ GroupB │ ║ + * ║└───────────┘ ║ + * ║ ▲ ║ + * ║ │ ┌───────────┐ ║ + * ║ ├────"from A"──│ IngressB │ ║ + * ║ │ └───────────┘ ║ + * ║ │ ┌───────────┐ ║ + * ║ ├─────"to B"───│ EgressA │ ║ + * ║ │ └───────────┘ ║ + * ║ │ ┌───────────┐ ║ + * ║ └─────"to B"───│ EgressC │ ║ <-- oops + * ║ └───────────┘ ║ + * ╚═══════════════════════════════════╝ + */ + + protected determineRuleScope( + peer: IPeer, + connection: Port, + fromTo: 'from' | 'to', + remoteRule?: boolean): [SecurityGroupBase, string] { + + if (remoteRule && SecurityGroupBase.isSecurityGroup(peer) && differentStacks(this, peer)) { + // Reversed + const reversedFromTo = fromTo === 'from' ? 'to' : 'from'; + return [peer, `${this.uniqueId}:${connection} ${reversedFromTo}`]; + } else { + // Regular (do old ID escaping to in order to not disturb existing deployments) + return [this, `${fromTo} ${this.renderPeer(peer)}:${connection}`.replace('/', '_')]; + } } -} -function renderPeer(peer: IPeer) { - return Token.isUnresolved(peer.uniqueId) ? '{IndirectPeer}' : peer.uniqueId; + private renderPeer(peer: IPeer) { + if (Token.isUnresolved(peer.uniqueId)) { + // Need to return a unique value each time a peer + // is an unresolved token, else the duplicate skipper + // in `sg.addXxxRule` can detect unique rules as duplicates + return this.peerAsTokenCount++ ? `'{IndirectPeer${this.peerAsTokenCount}}'` : '{IndirectPeer}'; + } else { + return peer.uniqueId; + } + } } function differentStacks(group1: SecurityGroupBase, group2: SecurityGroupBase) { @@ -308,7 +317,7 @@ export interface SecurityGroupImportOptions { * you would import it like this: * * ```ts - * const securityGroup = SecurityGroup.fromSecurityGroupId(this, 'SG', 'sg-12345', { + * const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'SG', 'sg-12345', { * mutable: false * }); * ``` @@ -316,25 +325,25 @@ export interface SecurityGroupImportOptions { export class SecurityGroup extends SecurityGroupBase { /** * Look up a security group by id. + * + * @deprecated Use `fromLookupById()` instead */ public static fromLookup(scope: Construct, id: string, securityGroupId: string) { - if (Token.isUnresolved(securityGroupId)) { - throw new Error('All arguments to look up a security group must be concrete (no Tokens)'); - } + return this.fromLookupAttributes(scope, id, { securityGroupId }); + } - const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, { - provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER, - props: { securityGroupId }, - dummyValue: { - securityGroupId: 'sg-12345', - allowAllOutbound: true, - } as cxapi.SecurityGroupContextResponse, - }).value; + /** + * Look up a security group by id. + */ + public static fromLookupById(scope: Construct, id: string, securityGroupId: string) { + return this.fromLookupAttributes(scope, id, { securityGroupId }); + } - return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, { - allowAllOutbound: attributes.allowAllOutbound, - mutable: true, - }); + /** + * Look up a security group by name. + */ + public static fromLookupByName(scope: Construct, id: string, securityGroupName: string, vpc: IVpc) { + return this.fromLookupAttributes(scope, id, { securityGroupName, vpc }); } /** @@ -378,6 +387,33 @@ export class SecurityGroup extends SecurityGroupBase { : new ImmutableImport(scope, id); } + /** + * Look up a security group. + */ + private static fromLookupAttributes(scope: Construct, id: string, options: SecurityGroupLookupOptions) { + if (Token.isUnresolved(options.securityGroupId) || Token.isUnresolved(options.securityGroupName) || Token.isUnresolved(options.vpc?.vpcId)) { + throw new Error('All arguments to look up a security group must be concrete (no Tokens)'); + } + + const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, { + provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER, + props: { + securityGroupId: options.securityGroupId, + securityGroupName: options.securityGroupName, + vpcId: options.vpc?.vpcId, + }, + dummyValue: { + securityGroupId: 'sg-12345', + allowAllOutbound: true, + } as cxapi.SecurityGroupContextResponse, + }).value; + + return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, { + allowAllOutbound: attributes.allowAllOutbound, + mutable: true, + }); + } + /** * An attribute that represents the security group name. * @@ -565,13 +601,12 @@ export class SecurityGroup extends SecurityGroupBase { */ private removeNoTrafficRule() { if (this.disableInlineRules) { - const [scope, id] = determineRuleScope( - this, + const [scope, id] = this.determineRuleScope( NO_TRAFFIC_PEER, NO_TRAFFIC_PORT, 'to', - false); - + false, + ); scope.node.tryRemoveChild(id); } else { const i = this.directEgressRules.findIndex(r => egressRulesEqual(r, MATCH_NO_TRAFFIC)); @@ -688,3 +723,37 @@ function egressRulesEqual(a: CfnSecurityGroup.EgressProperty, b: CfnSecurityGrou function isAllTrafficRule(rule: any) { return rule.cidrIp === '0.0.0.0/0' && rule.ipProtocol === '-1'; } + +/** + * Properties for looking up an existing SecurityGroup. + * + * Either `securityGroupName` or `securityGroupId` has to be specified. + */ +interface SecurityGroupLookupOptions { + /** + * The name of the security group + * + * If given, will import the SecurityGroup with this name. + * + * @default Don't filter on securityGroupName + */ + readonly securityGroupName?: string; + + /** + * The ID of the security group + * + * If given, will import the SecurityGroup with this ID. + * + * @default Don't filter on securityGroupId + */ + readonly securityGroupId?: string; + + /** + * The VPC of the security group + * + * If given, will filter the SecurityGroup based on the VPC. + * + * @default Don't filter on VPC + */ + readonly vpc?: IVpc, +} diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts index 9b835744b3a6e..1e92eb888e6f8 100644 --- a/packages/@aws-cdk/aws-ec2/lib/user-data.ts +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -37,6 +37,12 @@ export interface S3DownloadOptions { */ readonly localFile?: string; + /** + * The region of the S3 Bucket (needed for access via VPC Gateway) + * @default none + */ + readonly region?: string + } /** @@ -156,7 +162,7 @@ class LinuxUserData extends UserData { const localPath = ( params.localFile && params.localFile.length !== 0 ) ? params.localFile : `/tmp/${ params.bucketKey }`; this.addCommands( `mkdir -p $(dirname '${localPath}')`, - `aws s3 cp '${s3Path}' '${localPath}'`, + `aws s3 cp '${s3Path}' '${localPath}'` + (params.region !== undefined ? ` --region ${params.region}` : ''), ); return localPath; @@ -215,7 +221,7 @@ class WindowsUserData extends UserData { const localPath = ( params.localFile && params.localFile.length !== 0 ) ? params.localFile : `C:/temp/${ params.bucketKey }`; this.addCommands( `mkdir (Split-Path -Path '${localPath}' ) -ea 0`, - `Read-S3Object -BucketName '${params.bucket.bucketName}' -key '${params.bucketKey}' -file '${localPath}' -ErrorAction Stop`, + `Read-S3Object -BucketName '${params.bucket.bucketName}' -key '${params.bucketKey}' -file '${localPath}' -ErrorAction Stop` + (params.region !== undefined ? ` -Region ${params.region}` : ''), ); return localPath; } @@ -483,7 +489,11 @@ export class MultipartUserData extends UserData { * If `makeDefault` is false, then this is the same as calling: * * ```ts - * multiPart.addPart(MultipartBody.fromUserData(userData, contentType)); + * declare const multiPart: ec2.MultipartUserData; + * declare const userData: ec2.UserData; + * declare const contentType: string; + * + * multiPart.addPart(ec2.MultipartBody.fromUserData(userData, contentType)); * ``` * * An undefined `makeDefault` defaults to either: diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index a25ca459b7c63..87e92b3f5006a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -19,7 +19,7 @@ export interface BlockDevice { /** * The device name exposed to the EC2 instance * - * @example '/dev/sdh', 'xvdh' + * For example, a value like `/dev/sdh`, `xvdh`. * * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html */ @@ -28,8 +28,7 @@ export interface BlockDevice { /** * Defines the block device volume, to be either an Amazon EBS volume or an ephemeral instance store volume * - * @example BlockDeviceVolume.ebs(15), BlockDeviceVolume.ephemeral(0) - * + * For example, a value like `BlockDeviceVolume.ebs(15)`, `BlockDeviceVolume.ephemeral(0)`. */ readonly volume: BlockDeviceVolume; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index de64072868391..1f454f9d63f65 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -121,6 +121,8 @@ export interface GatewayVpcEndpointOptions { * @default - All subnets in the VPC * @example * + * declare const vpc: ec2.Vpc; + * * vpc.addGatewayEndpoint('DynamoDbEndpoint', { * service: ec2.GatewayVpcEndpointAwsService.DYNAMODB, * // Add only to ISOLATED subnets @@ -264,6 +266,8 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly CODEBUILD_FIPS = new InterfaceVpcEndpointAwsService('codebuild-fips'); public static readonly CODECOMMIT = new InterfaceVpcEndpointAwsService('codecommit'); public static readonly CODECOMMIT_FIPS = new InterfaceVpcEndpointAwsService('codecommit-fips'); + public static readonly CODEGURU_PROFILER = new InterfaceVpcEndpointAwsService('codeguru-profiler'); + public static readonly CODEGURU_REVIEWER = new InterfaceVpcEndpointAwsService('codeguru-reviewer'); public static readonly CODEPIPELINE = new InterfaceVpcEndpointAwsService('codepipeline'); public static readonly CONFIG = new InterfaceVpcEndpointAwsService('config'); public static readonly EC2 = new InterfaceVpcEndpointAwsService('ec2'); @@ -307,6 +311,7 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly STEP_FUNCTIONS = new InterfaceVpcEndpointAwsService('states'); public static readonly LAMBDA = new InterfaceVpcEndpointAwsService('lambda'); public static readonly TRANSCRIBE = new InterfaceVpcEndpointAwsService('transcribe'); + public static readonly XRAY = new InterfaceVpcEndpointAwsService('xray'); /** * The name of the service. diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts index 3f9f49dba2540..8db845763dd0e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts @@ -48,4 +48,11 @@ export interface VpcLookupOptions { * @default aws-cdk:subnet-name */ readonly subnetGroupNameTag?: string; + + /** + * Optional to override inferred region + * + * @default Current stack's environment region + */ + readonly region?: string; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index b8194eb161c1d..6d1722b3135d8 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,7 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Annotations, ConcreteDependable, ContextProvider, DependableTrait, IConstruct, - IDependable, IResource, Lazy, Resource, Stack, Token, Tags, Names, + IDependable, IResource, Lazy, Resource, Stack, Token, Tags, Names, Arn, } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; @@ -78,6 +78,12 @@ export interface IVpc extends IResource { */ readonly vpcId: string; + /** + * ARN for this VPC + * @attribute + */ + readonly vpcArn: string; + /** * CIDR range for this VPC * @@ -357,6 +363,11 @@ abstract class VpcBase extends Resource implements IVpc { */ public abstract readonly vpcId: string; + /** + * Arn of this VPC + */ + public abstract readonly vpcArn: string; + /** * CIDR range for this VPC */ @@ -1118,9 +1129,15 @@ export class Vpc extends VpcBase { filter.isDefault = options.isDefault ? 'true' : 'false'; } + const overrides: {[key: string]: string} = {}; + if (options.region) { + overrides.region = options.region; + } + const attributes: cxapi.VpcContextResponse = ContextProvider.getValue(scope, { provider: cxschema.ContextProvider.VPC_PROVIDER, props: { + ...overrides, filter, returnAsymmetricSubnets: true, subnetGroupNameTag: options.subnetGroupNameTag, @@ -1147,6 +1164,11 @@ export class Vpc extends VpcBase { */ public readonly vpcId: string; + /** + * @attribute + */ + public readonly vpcArn: string; + /** * @attribute */ @@ -1277,6 +1299,11 @@ export class Vpc extends VpcBase { this.availabilityZones = this.availabilityZones.slice(0, maxAZs); this.vpcId = this.resource.ref; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, stack); const defaultSubnet = props.natGateways === 0 ? Vpc.DEFAULT_SUBNETS_NO_NAT : Vpc.DEFAULT_SUBNETS; this.subnetConfiguration = ifUndefined(props.subnetConfiguration, defaultSubnet); @@ -1813,11 +1840,11 @@ export class PublicSubnet extends Subnet implements IPublicSubnet { * Also adds the EIP for the managed NAT. * @returns A ref to the the NAT Gateway ID */ - public addNatGateway() { + public addNatGateway(eipAllocationId?: string) { // Create a NAT Gateway in this public subnet const ngw = new CfnNatGateway(this, 'NATGateway', { subnetId: this.subnetId, - allocationId: new CfnEIP(this, 'EIP', { + allocationId: eipAllocationId ?? new CfnEIP(this, 'EIP', { domain: 'vpc', }).attrAllocationId, }); @@ -1853,6 +1880,7 @@ function ifUndefined(value: T | undefined, defaultValue: T): T { class ImportedVpc extends VpcBase { public readonly vpcId: string; + public readonly vpcArn: string; public readonly publicSubnets: ISubnet[]; public readonly privateSubnets: ISubnet[]; public readonly isolatedSubnets: ISubnet[]; @@ -1864,6 +1892,11 @@ class ImportedVpc extends VpcBase { super(scope, id); this.vpcId = props.vpcId; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, Stack.of(this)); this.cidr = props.vpcCidrBlock; this.availabilityZones = props.availabilityZones; this._vpnGatewayId = props.vpnGatewayId; @@ -1897,6 +1930,7 @@ class ImportedVpc extends VpcBase { class LookedUpVpc extends VpcBase { public readonly vpcId: string; + public readonly vpcArn: string; public readonly internetConnectivityEstablished: IDependable = new ConcreteDependable(); public readonly availabilityZones: string[]; public readonly publicSubnets: ISubnet[]; @@ -1908,6 +1942,11 @@ class LookedUpVpc extends VpcBase { super(scope, id); this.vpcId = props.vpcId; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, Stack.of(this)); this.cidr = props.vpcCidrBlock; this._vpnGatewayId = props.vpnGatewayId; this.incompleteSubnetDefinition = isIncomplete; diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 1124e2d561741..00190b44dd5f8 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,9 +86,9 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", @@ -117,6 +124,7 @@ }, "awslint": { "exclude": [ + "resource-attribute:@aws-cdk/aws-ec2.NetworkAclEntry.networkAclEntryId", "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId", "no-unused-type:@aws-cdk/aws-ec2.ConnectionRule", "no-unused-type:@aws-cdk/aws-ec2.Protocol", @@ -131,6 +139,7 @@ "props-physical-name:@aws-cdk/aws-ec2.InterfaceVpcEndpointProps", "props-physical-name:@aws-cdk/aws-ec2.VpcEndpointServiceProps", "from-method:@aws-cdk/aws-ec2.Instance", + "from-method:@aws-cdk/aws-ec2.NetworkAclEntry", "from-method:@aws-cdk/aws-ec2.VpcEndpointService", "attribute-tag:@aws-cdk/aws-ec2.BastionHostLinux.instance", "attribute-tag:@aws-cdk/aws-ec2.Instance.instance", @@ -224,6 +233,8 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_FIPS", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_GIT", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_GIT_FIPS", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEGURU_PROFILER", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEGURU_REVIEWER", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEPIPELINE", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CONFIG", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.EC2", @@ -262,6 +273,7 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STEP_FUNCTIONS", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.LAMBDA", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.TRANSCRIBE", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.XRAY", "docs-public-apis:@aws-cdk/aws-ec2.Port.toString", "docs-public-apis:@aws-cdk/aws-ec2.PrivateSubnet.fromPrivateSubnetAttributes", "docs-public-apis:@aws-cdk/aws-ec2.PublicSubnet.fromPublicSubnetAttributes", diff --git a/packages/@aws-cdk/aws-ec2/rosetta/client-vpn.ts-fixture b/packages/@aws-cdk/aws-ec2/rosetta/client-vpn.ts-fixture index 4886d590211df..34c83a31ced35 100644 --- a/packages/@aws-cdk/aws-ec2/rosetta/client-vpn.ts-fixture +++ b/packages/@aws-cdk/aws-ec2/rosetta/client-vpn.ts-fixture @@ -9,7 +9,7 @@ class Fixture extends Stack { const vpc = new ec2.Vpc(this, 'VPC'); const samlProvider = new iam.SamlProvider(this, 'Provider', { - metadataDocument: SamlMetadataDocument.fromXml('xml'), + metadataDocument: iam.SamlMetadataDocument.fromXml('xml'), }) /// here diff --git a/packages/@aws-cdk/aws-ec2/rosetta/conns.ts-fixture b/packages/@aws-cdk/aws-ec2/rosetta/conns.ts-fixture deleted file mode 100644 index f29d9a1816a6e..0000000000000 --- a/packages/@aws-cdk/aws-ec2/rosetta/conns.ts-fixture +++ /dev/null @@ -1,26 +0,0 @@ -// Fixture with fake connectables -import { Construct, Stack } from '@aws-cdk/core'; -import ec2 = require('@aws-cdk/aws-ec2'); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - - const loadBalancer = new FakeConnectable(); - const appFleet = new FakeConnectable(); - const dbFleet = new FakeConnectable(); - const rdsDatabase = new FakeConnectable(); - const fleet1 = new FakeConnectable(); - const fleet2 = new FakeConnectable(); - const listener = new FakeConnectable(); - const myEndpoint = new FakeConnectable(); - - /// here - } -} - -class FakeConnectable implements ec2.IConnectable { - public readonly connections = new ec2.Connections({ securityGroups: [] }); -} diff --git a/packages/@aws-cdk/aws-ec2/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ec2/rosetta/default.ts-fixture index d4ecd7e92a6f1..df2d29f125b46 100644 --- a/packages/@aws-cdk/aws-ec2/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-ec2/rosetta/default.ts-fixture @@ -1,7 +1,13 @@ // Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; +import { Aspects, Construct, Duration, Fn, Size, Stack, StackProps } from '@aws-cdk/core'; import ec2 = require('@aws-cdk/aws-ec2'); +import s3 = require('@aws-cdk/aws-s3'); import iam = require('@aws-cdk/aws-iam'); +import logs = require('@aws-cdk/aws-logs'); +import ssm = require('@aws-cdk/aws-ssm'); +import autoscaling = require('@aws-cdk/aws-autoscaling'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); +import rds = require('@aws-cdk/aws-rds'); class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-ec2/test/aspects/require-imdsv2-aspect.test.ts b/packages/@aws-cdk/aws-ec2/test/aspects/require-imdsv2-aspect.test.ts new file mode 100644 index 0000000000000..ade2eaeab1f1d --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/aspects/require-imdsv2-aspect.test.ts @@ -0,0 +1,205 @@ +import { + countResources, + expect as expectCDK, + haveResourceLike, +} from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; +import * as cdk from '@aws-cdk/core'; +import { + CfnLaunchTemplate, + Instance, + InstanceRequireImdsv2Aspect, + InstanceType, + LaunchTemplate, + LaunchTemplateRequireImdsv2Aspect, + MachineImage, + Vpc, +} from '../../lib'; + +describe('RequireImdsv2Aspect', () => { + let app: cdk.App; + let stack: cdk.Stack; + let vpc: Vpc; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'Stack'); + vpc = new Vpc(stack, 'Vpc'); + }); + + test('suppresses warnings', () => { + // GIVEN + const aspect = new LaunchTemplateRequireImdsv2Aspect({ + suppressWarnings: true, + }); + const errmsg = 'ERROR'; + const visitMock = jest.spyOn(aspect, 'visit').mockImplementation((node) => { + // @ts-ignore + aspect.warn(node, errmsg); + }); + const construct = new cdk.Construct(stack, 'Construct'); + + // WHEN + aspect.visit(construct); + + // THEN + expect(visitMock).toHaveBeenCalled(); + expect(construct.node.metadataEntry).not.toContainEqual({ + data: expect.stringContaining(errmsg), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + + describe('InstanceRequireImdsv2Aspect', () => { + test('requires IMDSv2', () => { + // GIVEN + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }); + const aspect = new InstanceRequireImdsv2Aspect(); + + // WHEN + cdk.Aspects.of(stack).add(aspect); + app.synth(); + + // THEN + const launchTemplate = instance.node.tryFindChild('LaunchTemplate') as LaunchTemplate; + expect(launchTemplate).toBeDefined(); + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateName: stack.resolve(launchTemplate.launchTemplateName), + LaunchTemplateData: { + MetadataOptions: { + HttpTokens: 'required', + }, + }, + })); + expectCDK(stack).to(haveResourceLike('AWS::EC2::Instance', { + LaunchTemplate: { + LaunchTemplateName: stack.resolve(launchTemplate.launchTemplateName), + }, + })); + }); + + test('does not toggle when Instance has a LaunchTemplate', () => { + // GIVEN + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }); + instance.instance.launchTemplate = { + launchTemplateName: 'name', + version: 'version', + }; + const aspect = new InstanceRequireImdsv2Aspect(); + + // WHEN + cdk.Aspects.of(stack).add(aspect); + + // THEN + // Aspect normally creates a LaunchTemplate for the Instance to toggle IMDSv1, + // so we can assert that one was not created + expectCDK(stack).to(countResources('AWS::EC2::LaunchTemplate', 0)); + expect(instance.node.metadataEntry).toContainEqual({ + data: expect.stringContaining('Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.'), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + + test('suppresses Launch Template warnings', () => { + // GIVEN + const instance = new Instance(stack, 'Instance', { + vpc, + instanceType: new InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }); + instance.instance.launchTemplate = { + launchTemplateName: 'name', + version: 'version', + }; + const aspect = new InstanceRequireImdsv2Aspect({ + suppressLaunchTemplateWarning: true, + }); + + // WHEN + aspect.visit(instance); + + // THEN + expect(instance.node.metadataEntry).not.toContainEqual({ + data: expect.stringContaining('Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.'), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + }); + + describe('LaunchTemplateRequireImdsv2Aspect', () => { + test('warns when LaunchTemplateData is a CDK token', () => { + // GIVEN + const launchTemplate = new LaunchTemplate(stack, 'LaunchTemplate'); + const cfnLaunchTemplate = launchTemplate.node.tryFindChild('Resource') as CfnLaunchTemplate; + cfnLaunchTemplate.launchTemplateData = fakeToken(); + const aspect = new LaunchTemplateRequireImdsv2Aspect(); + + // WHEN + aspect.visit(launchTemplate); + + // THEN + expect(launchTemplate.node.metadataEntry).toContainEqual({ + data: expect.stringContaining('LaunchTemplateData is a CDK token.'), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + + test('warns when MetadataOptions is a CDK token', () => { + // GIVEN + const launchTemplate = new LaunchTemplate(stack, 'LaunchTemplate'); + const cfnLaunchTemplate = launchTemplate.node.tryFindChild('Resource') as CfnLaunchTemplate; + cfnLaunchTemplate.launchTemplateData = { + metadataOptions: fakeToken(), + } as CfnLaunchTemplate.LaunchTemplateDataProperty; + const aspect = new LaunchTemplateRequireImdsv2Aspect(); + + // WHEN + aspect.visit(launchTemplate); + + // THEN + expect(launchTemplate.node.metadataEntry).toContainEqual({ + data: expect.stringContaining('LaunchTemplateData.MetadataOptions is a CDK token.'), + type: 'aws:cdk:warning', + trace: undefined, + }); + }); + + test('requires IMDSv2', () => { + // GIVEN + new LaunchTemplate(stack, 'LaunchTemplate'); + const aspect = new LaunchTemplateRequireImdsv2Aspect(); + + // WHEN + cdk.Aspects.of(stack).add(aspect); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + MetadataOptions: { + HttpTokens: 'required', + }, + }, + })); + }); + }); +}); + +function fakeToken(): cdk.IResolvable { + return { + creationStack: [], + resolve: (_c) => {}, + toString: () => '', + }; +} diff --git a/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts b/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts index 75896912f3661..8f1c4951d2bf3 100644 --- a/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts @@ -664,7 +664,7 @@ describe('InitSource', () => { test('fromS3Object uses object URL', () => { // GIVEN - const bucket = s3.Bucket.fromBucketName(stack, 'bucket', 'MyBucket'); + const bucket = s3.Bucket.fromBucketName(stack, 'bucket', 'mybucket'); const source = ec2.InitSource.fromS3Object('/tmp/foo', bucket, 'myKey'); // WHEN @@ -672,7 +672,7 @@ describe('InitSource', () => { // THEN expect(rendered).toEqual({ - '/tmp/foo': expect.stringContaining('/MyBucket/myKey'), + '/tmp/foo': expect.stringContaining('/mybucket/myKey'), }); }); diff --git a/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts index 37d4fe2d72d28..2a9cce5e76719 100644 --- a/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts @@ -667,7 +667,7 @@ class SingletonLocationSythesizer extends DefaultStackSynthesizer { public addFileAsset(_asset: FileAssetSource): FileAssetLocation { const httpUrl = 'https://MyBucket.s3.amazonaws.com/MyAsset'; return { - bucketName: 'MyAssetBucket', + bucketName: 'myassetbucket', objectKey: 'MyAssetFile', httpUrl, s3ObjectUrl: httpUrl, diff --git a/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts b/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts index 5390eb43adb85..8570fa4fafacc 100644 --- a/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import { Connections, IClientVpnEndpoint } from '../lib'; import { ClientVpnAuthorizationRule } from '../lib/client-vpn-authorization-rule'; @@ -41,7 +42,7 @@ describe('ClientVpnAuthorizationRule constructor', () => { ), ); }); - test('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { + testDeprecated('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { const clientVpnEndoint: IClientVpnEndpoint = { endpointId: 'typoTypo', targetNetworksAssociated: [], @@ -79,7 +80,7 @@ describe('ClientVpnAuthorizationRule constructor', () => { }).toThrow(); expect(stack.node.children.length).toBe(0); }); - test('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { + testDeprecated('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { const clientVpnEndoint: IClientVpnEndpoint = { endpointId: 'myClientVpnEndpoint', targetNetworksAssociated: [], diff --git a/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts b/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts index fde37f49335ae..5bb1ee9722139 100644 --- a/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { SamlMetadataDocument, SamlProvider } from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import * as ec2 from '../lib'; import { @@ -46,7 +47,7 @@ describe('ClientVpnRoute constructor', () => { expect(stack).toCountResources('AWS::EC2::ClientVpnRoute', 1); expect(stack.node.children.length).toBe(3); }); - test('either clientVpnEndoint (deprecated, typo) or clientVpnEndpoint is required', () => { + testDeprecated('either clientVpnEndoint (deprecated, typo) or clientVpnEndpoint is required', () => { expect(() => { new ClientVpnRoute(stack, 'RouteNoEndointOrEndpoint', { cidr: '0.0.0.0/0', @@ -58,7 +59,7 @@ describe('ClientVpnRoute constructor', () => { ), ); }); - test('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { + testDeprecated('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { const samlProvider = new SamlProvider(stack, 'Provider', { metadataDocument: SamlMetadataDocument.fromXml('xml'), }); @@ -98,7 +99,7 @@ describe('ClientVpnRoute constructor', () => { expect(stack).toCountResources('AWS::EC2::VPC', 1); expect(stack.node.children.length).toBe(1); }); - test('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { + testDeprecated('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { const samlProvider = new SamlProvider(stack, 'Provider', { metadataDocument: SamlMetadataDocument.fromXml('xml'), }); diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 884021f518a84..4a0f40ae1df8f 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -7,7 +7,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Stack } from '@aws-cdk/core'; import { AmazonLinuxImage, BlockDeviceVolume, CloudFormationInit, - EbsDeviceVolumeType, InitCommand, Instance, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType, UserData, Vpc, + EbsDeviceVolumeType, InitCommand, Instance, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType, LaunchTemplate, UserData, Vpc, } from '../lib'; @@ -314,8 +314,8 @@ describe('instance', () => { }); // THEN - expect(instance.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(instance.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(instance.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(instance.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -337,8 +337,8 @@ describe('instance', () => { }); // THEN - expect(instance.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(instance.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(instance.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(instance.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -361,6 +361,36 @@ describe('instance', () => { }); + + test('instance requires IMDSv2', () => { + // WHEN + const instance = new Instance(stack, 'Instance', { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: new InstanceType('t2.micro'), + requireImdsv2: true, + }); + + // Force stack synth so the InstanceRequireImdsv2Aspect is applied + SynthUtils.synthesize(stack); + + // THEN + const launchTemplate = instance.node.tryFindChild('LaunchTemplate') as LaunchTemplate; + expect(launchTemplate).toBeDefined(); + expect(stack).toHaveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateName: stack.resolve(launchTemplate.launchTemplateName), + LaunchTemplateData: { + MetadataOptions: { + HttpTokens: 'required', + }, + }, + }); + expect(stack).toHaveResourceLike('AWS::EC2::Instance', { + LaunchTemplate: { + LaunchTemplateName: stack.resolve(launchTemplate.launchTemplateName), + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts index 27399affe8149..6196a60541a12 100644 --- a/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts @@ -509,6 +509,22 @@ describe('LaunchTemplate', () => { }, }); }); + + test('Requires IMDSv2', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + requireImdsv2: true, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + MetadataOptions: { + HttpTokens: 'required', + }, + }, + }); + }); }); describe('LaunchTemplate marketOptions', () => { @@ -550,7 +566,7 @@ describe('LaunchTemplate marketOptions', () => { }); // THEN - expect(template.node.metadata).toHaveLength(expectedErrors); + expect(template.node.metadataEntry).toHaveLength(expectedErrors); }); test('for bad duration', () => { diff --git a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts index 09ccc6cdc682e..c7c3662f8592f 100644 --- a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Intrinsic, Lazy, Stack, Token } from '@aws-cdk/core'; import { Peer, Port, SecurityGroup, SecurityGroupProps, Vpc } from '../lib'; @@ -208,6 +209,30 @@ describe('security group', () => { }); + test('can add multiple rules using tokens on same security group', () => { + // GIVEN + const stack = new Stack(undefined, 'TestStack', { env: { account: '12345678', region: 'dummy' } }); + const vpc = new Vpc(stack, 'VPC'); + const sg = new SecurityGroup(stack, 'SG', { vpc }); + + const p1 = Lazy.string({ produce: () => 'dummyid1' }); + const p2 = Lazy.string({ produce: () => 'dummyid2' }); + const peer1 = Peer.prefixList(p1); + const peer2 = Peer.prefixList(p2); + + // WHEN + sg.addIngressRule(peer1, Port.tcp(5432), 'Rule 1'); + sg.addIngressRule(peer2, Port.tcp(5432), 'Rule 2'); + + // THEN -- no crash + expect(stack).toHaveResourceLike('AWS::EC2::SecurityGroupIngress', { + Description: 'Rule 1', + }); + expect(stack).toHaveResourceLike('AWS::EC2::SecurityGroupIngress', { + Description: 'Rule 2', + }); + }); + test('if tokens are used in ports, `canInlineRule` should be false to avoid cycles', () => { // GIVEN const p1 = Lazy.number({ produce: () => 80 }); @@ -312,7 +337,7 @@ describe('security group', () => { }); }); - test('can look up a security group', () => { + testDeprecated('can look up a security group', () => { const app = new App(); const stack = new Stack(app, 'stack', { env: { @@ -326,8 +351,131 @@ describe('security group', () => { expect(securityGroup.securityGroupId).toEqual('sg-12345'); expect(securityGroup.allowAllOutbound).toEqual(true); + }); + + test('can look up a security group by id', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupById(stack, 'SG1', 'sg-12345'); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); + + }); + + test('can look up a security group by name and vpc', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: 'vpc-1234', + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'sg-12345', vpc); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); + + }); + + test('can look up a security group by id and vpc', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: 'vpc-1234', + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'my-security-group', vpc); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); + + }); + + test('throws if securityGroupId is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'sg-12345' })); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); + + }); + + test('throws if securityGroupName is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'my-security-group' })); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); + + }); + + test('throws if vpc id is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: Lazy.string({ produce: () => 'vpc-1234' }), + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupByName(stack, 'stack', 'my-security-group', vpc); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); }); + }); function testRulesAreInlined(contextDisableInlineRules: boolean | undefined | null, optionsDisableInlineRules: boolean | undefined) { diff --git a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts index 272ad60a84b00..e2596d8699abd 100644 --- a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts @@ -85,6 +85,35 @@ describe('user data', () => { 'Read-S3Object -BucketName \'test2\' -key \'filename2.bat\' -file \'c:\\test\\location\\otherScript.bat\' -ErrorAction Stop', ); + }); + test('can windows userdata download S3 files with given region', () => { + // GIVEN + const stack = new Stack(); + const userData = ec2.UserData.forWindows(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + const bucket2 = Bucket.fromBucketName( stack, 'testBucket2', 'test2' ); + + // WHEN + userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.bat', + region: 'us-east-1', + } ); + userData.addS3DownloadCommand({ + bucket: bucket2, + bucketKey: 'filename2.bat', + localFile: 'c:\\test\\location\\otherScript.bat', + region: 'us-east-1', + } ); + + // THEN + const rendered = userData.render(); + expect(rendered).toEqual('mkdir (Split-Path -Path \'C:/temp/filename.bat\' ) -ea 0\n' + + 'Read-S3Object -BucketName \'test\' -key \'filename.bat\' -file \'C:/temp/filename.bat\' -ErrorAction Stop -Region us-east-1\n' + + 'mkdir (Split-Path -Path \'c:\\test\\location\\otherScript.bat\' ) -ea 0\n' + + 'Read-S3Object -BucketName \'test2\' -key \'filename2.bat\' -file \'c:\\test\\location\\otherScript.bat\' -ErrorAction Stop -Region us-east-1', + ); + }); test('can windows userdata execute files', () => { // GIVEN @@ -189,6 +218,36 @@ describe('user data', () => { 'aws s3 cp \'s3://test2/filename2.sh\' \'c:\\test\\location\\otherScript.sh\'', ); + }); + test('can linux userdata download S3 files from specific region', () => { + // GIVEN + const stack = new Stack(); + const userData = ec2.UserData.forLinux(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + const bucket2 = Bucket.fromBucketName( stack, 'testBucket2', 'test2' ); + + // WHEN + userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.sh', + region: 'us-east-1', + } ); + userData.addS3DownloadCommand({ + bucket: bucket2, + bucketKey: 'filename2.sh', + localFile: 'c:\\test\\location\\otherScript.sh', + region: 'us-east-1', + } ); + + // THEN + const rendered = userData.render(); + expect(rendered).toEqual('#!/bin/bash\n' + + 'mkdir -p $(dirname \'/tmp/filename.sh\')\n' + + 'aws s3 cp \'s3://test/filename.sh\' \'/tmp/filename.sh\' --region us-east-1\n' + + 'mkdir -p $(dirname \'c:\\test\\location\\otherScript.sh\')\n' + + 'aws s3 cp \'s3://test2/filename2.sh\' \'c:\\test\\location\\otherScript.sh\' --region us-east-1', + ); + }); test('can linux userdata execute files', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts index 4ad08203a525e..30c0ab1cf2a80 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts @@ -250,6 +250,24 @@ describe('vpc from lookup', () => { restoreContextProvider(previous); }); + test('passes account and region', () => { + const previous = mockVpcContextProviderWith({ + vpcId: 'vpc-1234', + subnetGroups: [], + }, options => { + expect(options.region).toEqual('region-1234'); + }); + + const stack = new Stack(); + const vpc = Vpc.fromLookup(stack, 'Vpc', { + vpcId: 'vpc-1234', + region: 'region-1234', + }); + + expect(vpc.vpcId).toEqual('vpc-1234'); + + restoreContextProvider(previous); + }); }); }); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index ca375dc7fe321..b7922e877c64b 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { isSuperObject, MatchStyle, SynthUtils } from '@aws-cdk/assert-internal'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnOutput, CfnResource, Fn, Lazy, Stack, Tags } from '@aws-cdk/core'; import { AclCidr, @@ -36,7 +37,12 @@ describe('vpc', () => { const stack = getTestStack(); const vpc = new Vpc(stack, 'TheVPC'); expect(stack.resolve(vpc.vpcId)).toEqual({ Ref: 'TheVPC92636AB0' }); + }); + test('vpc.vpcArn returns a token to the VPC ID', () => { + const stack = getTestStack(); + const vpc = new Vpc(stack, 'TheVPC'); + expect(stack.resolve(vpc.vpcArn)).toEqual({ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':ec2:us-east-1:123456789012:vpc/', { Ref: 'TheVPC92636AB0' }]] }); }); test('it uses the correct network range', () => { @@ -361,7 +367,7 @@ describe('vpc', () => { } }); - test('with custom subents and natGateways = 2 there should be only two NATGW', () => { + test('with custom subnets and natGateways = 2 there should be only two NATGW', () => { const stack = getTestStack(); new Vpc(stack, 'TheVPC', { cidr: '10.0.0.0/21', @@ -580,6 +586,31 @@ describe('vpc', () => { }); + test('EIP passed with NAT gateway does not create duplicate EIP', () => { + const stack = getTestStack(); + new Vpc(stack, 'VPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.PRIVATE_WITH_NAT, + }, + ], + natGatewayProvider: NatProvider.gateway({ eipAllocationIds: ['b'] }), + natGateways: 1, + }); + expect(stack).toCountResources('AWS::EC2::EIP', 0); + expect(stack).toHaveResource('AWS::EC2::NatGateway', { + AllocationId: 'b', + }); + }); + test('with mis-matched nat and subnet configs it throws', () => { const stack = getTestStack(); expect(() => new Vpc(stack, 'VPC', { @@ -992,7 +1023,7 @@ describe('vpc', () => { }); - test('can configure Security Groups of NAT instances with allowAllTraffic false', () => { + testDeprecated('can configure Security Groups of NAT instances with allowAllTraffic false', () => { // GIVEN const stack = getTestStack(); @@ -1786,4 +1817,4 @@ function hasTags(expectedTags: Array<{Key: string, Value: string}>): (props: any throw e; } }; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index e579baf55ac41..78e706587dd16 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ecr from '@aws-cdk/aws-ecr'; -import { Annotations, AssetStaging, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, Stage } from '@aws-cdk/core'; +import { Annotations, AssetStaging, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, Stage, CfnResource } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; @@ -147,6 +147,30 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { */ public readonly assetHash: string; + /** + * The path to the asset, relative to the current Cloud Assembly + * + * If asset staging is disabled, this will just be the original path. + * + * If asset staging is enabled it will be the staged path. + */ + private readonly assetPath: string; + + /** + * The path to the Dockerfile, relative to the assetPath + */ + private readonly dockerfilePath?: string; + + /** + * Build args to pass to the `docker build` command. + */ + private readonly dockerBuildArgs?: { [key: string]: string }; + + /** + * Docker target to build to + */ + private readonly dockerBuildTarget?: string; + constructor(scope: Construct, id: string, props: DockerImageAssetProps) { super(scope, id); @@ -160,7 +184,8 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { } // validate the docker file exists - const file = path.join(dir, props.file || 'Dockerfile'); + this.dockerfilePath = props.file || 'Dockerfile'; + const file = path.join(dir, this.dockerfilePath); if (!fs.existsSync(file)) { throw new Error(`Cannot find file at ${file}`); } @@ -223,10 +248,14 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { this.assetHash = staging.assetHash; const stack = Stack.of(this); + this.assetPath = staging.relativeStagedPath(stack); + this.dockerBuildArgs = props.buildArgs; + this.dockerBuildTarget = props.target; + const location = stack.synthesizer.addDockerImageAsset({ - directoryName: staging.relativeStagedPath(stack), - dockerBuildArgs: props.buildArgs, - dockerBuildTarget: props.target, + directoryName: this.assetPath, + dockerBuildArgs: this.dockerBuildArgs, + dockerBuildTarget: this.dockerBuildTarget, dockerFile: props.file, sourceHash: staging.assetHash, }); @@ -234,6 +263,38 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName); this.imageUri = location.imageUri; } + + /** + * Adds CloudFormation template metadata to the specified resource with + * information that indicates which resource property is mapped to this local + * asset. This can be used by tools such as SAM CLI to provide local + * experience such as local invocation and debugging of Lambda functions. + * + * Asset metadata will only be included if the stack is synthesized with the + * "aws:cdk:enable-asset-metadata" context key defined, which is the default + * behavior when synthesizing via the CDK Toolkit. + * + * @see https://github.com/aws/aws-cdk/issues/1432 + * + * @param resource The CloudFormation resource which is using this asset [disable-awslint:ref-via-interface] + * @param resourceProperty The property name where this asset is referenced + */ + public addResourceMetadata(resource: CfnResource, resourceProperty: string) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + // tell tools such as SAM CLI that the resourceProperty of this resource + // points to a local path and include the path to de dockerfile, docker build args, and target, + // in order to enable local invocation of this function. + resource.cfnOptions.metadata = resource.cfnOptions.metadata || { }; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.assetPath; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY] = this.dockerfilePath; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY] = this.dockerBuildArgs; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty; + } + } function validateProps(props: DockerImageAssetProps) { diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index b6b407041eda6..42e33d793d481 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -70,10 +70,10 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/proxyquire": "^1.3.28", "aws-cdk": "0.0.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "proxyquire": "^2.1.3" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts index e08ef41d2d868..a42fd2451d8e3 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts @@ -2,10 +2,10 @@ import * as fs from 'fs'; import * as path from 'path'; import { expect as ourExpect, haveResource } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated, testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { DockerImageAsset } from '../lib'; /* eslint-disable quote-props */ @@ -50,7 +50,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).buildArgs).toEqual({ a: 'b' }); }); @@ -126,7 +126,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).target).toEqual('a-target'); }); @@ -142,7 +142,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).file).toEqual('Dockerfile.Custom'); }); @@ -256,12 +256,20 @@ describe('image asset', () => { }); - testFutureBehavior('docker directory is staged without files specified in .dockerignore', flags, App, (app) => { - testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app); - }); + describeDeprecated('docker ignore option', () => { + // The 'ignoreMode' property is both deprecated and not deprecated in DockerImageAssetProps interface. + // The interface through a complex set of inheritance chain has a 'ignoreMode' prop that is deprecated + // and another 'ignoreMode' prop that is not deprecated. + // Using a 'describeDeprecated' block here since there's no way to work around this craziness. + // When the deprecated property is removed source code, this block can be dropped. + + testFutureBehavior('docker directory is staged without files specified in .dockerignore', flags, App, (app) => { + testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app); + }); - testFutureBehavior('docker directory is staged without files specified in .dockerignore with IgnoreMode.GLOB', flags, App, (app) => { - testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app, IgnoreMode.GLOB); + testFutureBehavior('docker directory is staged without files specified in .dockerignore with IgnoreMode.GLOB', flags, App, (app) => { + testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app, IgnoreMode.GLOB); + }); }); testFutureBehavior('docker directory is staged with allow-listed files specified in .dockerignore', flags, App, (app) => { @@ -315,7 +323,7 @@ describe('image asset', () => { }); - test('fails if using token as repositoryName', () => { + testDeprecated('fails if using token as repositoryName', () => { // GIVEN const stack = new Stack(); const token = Lazy.string({ produce: () => 'foo' }); @@ -340,7 +348,6 @@ describe('image asset', () => { const asset4 = new DockerImageAsset(stack, 'Asset4', { directory, buildArgs: { opt1: '123', opt2: 'boom' } }); const asset5 = new DockerImageAsset(stack, 'Asset5', { directory, file: 'Dockerfile.Custom', target: 'NonDefaultTarget' }); const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' }); - const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, repositoryName: 'foo' }); expect(asset1.assetHash).toEqual('365b5d951fc5f725f78093a07e3e1cc7819b4cbe582ca71a4c344752c23bf409'); expect(asset2.assetHash).toEqual('9560a36f786f317c5e1abb986b58269b2453ed1cab16c36fd9b76646c837078c'); @@ -348,9 +355,19 @@ describe('image asset', () => { expect(asset4.assetHash).toEqual('72b961f96e358b8dad935719cfc2704c3d14a46434871825ac81e3b94caa4853'); expect(asset5.assetHash).toEqual('c23d34b3a1dac5a80c42e8fa6c88a0ac697eb709a6f36ebdb6e36ee8c75edc75'); expect(asset6.assetHash).toEqual('7e950a9b08c58d371c1658e04d377c0ec59d89a47fc245a86a50525b36a8949b'); - expect(asset7.assetHash).toEqual('313dd1f45a939b77fa8a4eb7780190aa7a20a40c95f503eca9e099186643d717'); }); + + testDeprecated('repositoryName is included in the asset id', () => { + const stack = new Stack(); + const directory = path.join(__dirname, 'demo-image-custom-docker-file'); + + const asset1 = new DockerImageAsset(stack, 'Asset1', { directory }); + const asset2 = new DockerImageAsset(stack, 'Asset2', { directory, repositoryName: 'foo' }); + + expect(asset1.assetHash).toEqual('b5d181eb114c889020f9d59961ac4ad5d65f49c571c0aafd5ce2be9464bc2d13'); + expect(asset2.assetHash).toEqual('0b48fa3f7f75365962e6e18f52608ec4e4451f8ecc0b58abdb063c5381569471'); + }); }); function testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app: App, ignoreMode?: IgnoreMode) { diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index 03172cac6ff82..193d5de2f0645 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -27,16 +27,19 @@ const repository = new ecr.Repository(this, 'Repository'); Amazon ECR image scanning helps in identifying software vulnerabilities in your container images. You can manually scan container images stored in Amazon ECR, or you can configure your repositories to scan images when you push them to a repository. To create a new repository to scan on push, simply enable `imageScanOnPush` in the properties ```ts -const repository = new ecr.Repository(stack, 'Repo', { - imageScanOnPush: true +const repository = new ecr.Repository(this, 'Repo', { + imageScanOnPush: true, }); ``` To create an `onImageScanCompleted` event rule and trigger the event target ```ts +declare const repository: ecr.Repository; +declare const target: SomeTarget; + repository.onImageScanCompleted('ImageScanComplete') - .addTarget(...) + .addTarget(target); ``` ### Authorization Token @@ -50,10 +53,7 @@ A Docker authorization token can be obtained using the `GetAuthorizationToken` E grants an IAM user access to call this API. ```ts -import * as iam from '@aws-cdk/aws-iam'; -import * as ecr from '@aws-cdk/aws-ecr'; - -const user = new iam.User(this, 'User', { ... }); +const user = new iam.User(this, 'User'); ecr.AuthorizationToken.grantRead(user); ``` @@ -65,10 +65,7 @@ higher rate and bandwidth limits. The following code snippet grants an IAM user access to retrieve an authorization token for the public gallery. ```ts -import * as iam from '@aws-cdk/aws-iam'; -import * as ecr from '@aws-cdk/aws-ecr'; - -const user = new iam.User(this, 'User', { ... }); +const user = new iam.User(this, 'User'); ecr.PublicGalleryAuthorizationToken.grantRead(user); ``` @@ -79,7 +76,7 @@ This user can then proceed to login to the registry using one of the [authentica You can set tag immutability on images in our repository using the `imageTagMutability` construct prop. ```ts -new ecr.Repository(stack, 'Repo', { imageTagMutability: ecr.TagMutability.IMMUTABLE }); +new ecr.Repository(this, 'Repo', { imageTagMutability: ecr.TagMutability.IMMUTABLE }); ``` ## Automatically clean up repositories @@ -91,6 +88,7 @@ against that image. For example, the following deletes images older than is important here): ```ts +declare const repository: ecr.Repository; repository.addLifecycleRule({ tagPrefixList: ['prod'], maxImageCount: 9999 }); -repository.addLifecycleRule({ maxImageAge: cdk.Duration.days(30) }); +repository.addLifecycleRule({ maxImageAge: Duration.days(30) }); ``` diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index af8547ba5edaf..74df965ff5d58 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,7 +1,7 @@ import { EOL } from 'os'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { IConstruct, Construct } from 'constructs'; import { CfnRepository } from './ecr.generated'; import { LifecycleRule, TagStatus } from './lifecycle'; @@ -167,7 +167,7 @@ export abstract class RepositoryBase extends Resource implements IRepository { * @private */ private repositoryUriWithSuffix(suffix?: string): string { - const parts = this.stack.parseArn(this.repositoryArn); + const parts = this.stack.splitArn(this.repositoryArn, ArnFormat.SLASH_RESOURCE_NAME); return `${parts.account}.dkr.ecr.${parts.region}.${this.stack.urlSuffix}/${this.repositoryName}${suffix}`; } @@ -411,7 +411,9 @@ export class Repository extends RepositoryBase { } } - return new Import(scope, id); + return new Import(scope, id, { + environmentFromArn: repositoryArn, + }); } public static fromRepositoryName(scope: Construct, id: string, repositoryName: string): IRepository { diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 1884a03470d31..c9039866abcc0 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -81,7 +88,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecr/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ecr/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6381522ef9b78 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/rosetta/default.ts-fixture @@ -0,0 +1,23 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as ecr from '@aws-cdk/aws-ecr'; +import * as events from '@aws-cdk/aws-events'; +import * as iam from '@aws-cdk/aws-iam'; + +class SomeTarget implements events.IRuleTarget { + public bind(): events.RuleTargetConfig { + return { + arn: 'ARN1', + }; + } +} + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index 00212e704e294..386682e0b75d9 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -24,14 +24,15 @@ To define an Amazon ECS service that is behind an application load balancer, ins * `ApplicationLoadBalancedEc2Service` ```ts -const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service(this, 'Service', { cluster, memoryLimitMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", - TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value", }, }, desiredCount: 2, @@ -41,7 +42,8 @@ const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service * `ApplicationLoadBalancedFargateService` ```ts -const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, cpu: 512, @@ -78,7 +80,8 @@ Additionally, if more than one application target group are needed, instantiate ```ts // One application load balancer with one listener and two target groups. -const loadBalancedEc2Service = new ApplicationMultipleTargetGroupsEc2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedEc2Service = new ecsPatterns.ApplicationMultipleTargetGroupsEc2Service(this, 'Service', { cluster, memoryLimitMiB: 256, taskImageOptions: { @@ -91,9 +94,9 @@ const loadBalancedEc2Service = new ApplicationMultipleTargetGroupsEc2Service(sta { containerPort: 90, pathPattern: 'a/b/c', - priority: 10 - } - ] + priority: 10, + }, + ], }); ``` @@ -101,7 +104,8 @@ const loadBalancedEc2Service = new ApplicationMultipleTargetGroupsEc2Service(sta ```ts // One application load balancer with one listener and two target groups. -const loadBalancedFargateService = new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationMultipleTargetGroupsFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, cpu: 512, @@ -115,9 +119,9 @@ const loadBalancedFargateService = new ApplicationMultipleTargetGroupsFargateSer { containerPort: 90, pathPattern: 'a/b/c', - priority: 10 - } - ] + priority: 10, + }, + ], }); ``` @@ -128,14 +132,15 @@ To define an Amazon ECS service that is behind a network load balancer, instanti * `NetworkLoadBalancedEc2Service` ```ts -const loadBalancedEcsService = new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedEcsService = new ecsPatterns.NetworkLoadBalancedEc2Service(this, 'Service', { cluster, memoryLimitMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", - TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value", }, }, desiredCount: 2, @@ -145,7 +150,8 @@ const loadBalancedEcsService = new ecsPatterns.NetworkLoadBalancedEc2Service(sta * `NetworkLoadBalancedFargateService` ```ts -const loadBalancedFargateService = new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.NetworkLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, cpu: 512, @@ -167,7 +173,8 @@ Additionally, if more than one network target group is needed, instantiate one o ```ts // Two network load balancers, each with their own listener and target group. -const loadBalancedEc2Service = new NetworkMultipleTargetGroupsEc2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedEc2Service = new ecsPatterns.NetworkMultipleTargetGroupsEc2Service(this, 'Service', { cluster, memoryLimitMiB: 256, taskImageOptions: { @@ -178,29 +185,29 @@ const loadBalancedEc2Service = new NetworkMultipleTargetGroupsEc2Service(stack, name: 'lb1', listeners: [ { - name: 'listener1' - } - ] + name: 'listener1', + }, + ], }, { name: 'lb2', listeners: [ { - name: 'listener2' - } - ] - } + name: 'listener2', + }, + ], + }, ], targetGroups: [ { containerPort: 80, - listener: 'listener1' + listener: 'listener1', }, { containerPort: 90, - listener: 'listener2' - } - ] + listener: 'listener2', + }, + ], }); ``` @@ -208,7 +215,8 @@ const loadBalancedEc2Service = new NetworkMultipleTargetGroupsEc2Service(stack, ```ts // Two network load balancers, each with their own listener and target group. -const loadBalancedFargateService = new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.NetworkMultipleTargetGroupsFargateService(this, 'Service', { cluster, memoryLimitMiB: 512, taskImageOptions: { @@ -219,29 +227,29 @@ const loadBalancedFargateService = new NetworkMultipleTargetGroupsFargateService name: 'lb1', listeners: [ { - name: 'listener1' - } - ] + name: 'listener1', + }, + ], }, { name: 'lb2', listeners: [ { - name: 'listener2' - } - ] - } + name: 'listener2', + }, + ], + }, ], targetGroups: [ { containerPort: 80, - listener: 'listener1' + listener: 'listener1', }, { containerPort: 90, - listener: 'listener2' - } - ] + listener: 'listener2', + }, + ], }); ``` @@ -252,7 +260,8 @@ To define a service that creates a queue and reads from that queue, instantiate * `QueueProcessingEc2Service` ```ts -const queueProcessingEc2Service = new QueueProcessingEc2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +const queueProcessingEc2Service = new ecsPatterns.QueueProcessingEc2Service(this, 'Service', { cluster, memoryLimitMiB: 1024, image: ecs.ContainerImage.fromRegistry('test'), @@ -261,9 +270,8 @@ const queueProcessingEc2Service = new QueueProcessingEc2Service(stack, 'Service' desiredTaskCount: 2, environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", - TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value", }, - queue, maxScalingCapacity: 5, containerName: 'test', }); @@ -272,7 +280,8 @@ const queueProcessingEc2Service = new QueueProcessingEc2Service(stack, 'Service' * `QueueProcessingFargateService` ```ts -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { cluster, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -281,9 +290,8 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' desiredTaskCount: 2, environment: { TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", - TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value", }, - queue, maxScalingCapacity: 5, containerName: 'test', }); @@ -299,29 +307,31 @@ To define a task that runs periodically, there are 2 options: ```ts // Instantiate an Amazon EC2 Task to run at a scheduled interval -const ecsScheduledTask = new ScheduledEc2Task(stack, 'ScheduledTask', { +declare const cluster: ecs.Cluster; +const ecsScheduledTask = new ecsPatterns.ScheduledEc2Task(this, 'ScheduledTask', { cluster, scheduledEc2TaskImageOptions: { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryLimitMiB: 256, environment: { name: 'TRIGGER', value: 'CloudWatch Events' }, }, - schedule: events.Schedule.expression('rate(1 minute)'), + schedule: appscaling.Schedule.expression('rate(1 minute)'), enabled: true, - ruleName: 'sample-scheduled-task-rule' + ruleName: 'sample-scheduled-task-rule', }); ``` * `ScheduledFargateTask` ```ts -const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { +declare const cluster: ecs.Cluster; +const scheduledFargateTask = new ecsPatterns.ScheduledFargateTask(this, 'ScheduledFargateTask', { cluster, scheduledFargateTaskImageOptions: { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryLimitMiB: 512, }, - schedule: events.Schedule.expression('rate(1 minute)'), + schedule: appscaling.Schedule.expression('rate(1 minute)'), platformVersion: ecs.FargatePlatformVersion.LATEST, }); ``` @@ -333,7 +343,6 @@ In addition to using the constructs, users can also add logic to customize these ### Configure HTTPS on an ApplicationLoadBalancedFargateService ```ts -import { ApplicationLoadBalancedFargateService } from './application-load-balanced-fargate-service'; import { HostedZone } from '@aws-cdk/aws-route53'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; import { SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; @@ -341,8 +350,10 @@ import { SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; const domainZone = HostedZone.fromLookup(this, 'Zone', { domainName: 'example.com' }); const certificate = Certificate.fromCertificateArn(this, 'Cert', 'arn:aws:acm:us-east-1:123456:certificate/abcdefg'); -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { - vpc +declare const vpc: ec2.Vpc; +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { + vpc, cluster, certificate, sslPolicy: SslPolicy.RECOMMENDED, @@ -358,10 +369,8 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta ### Add Schedule-Based Auto-Scaling to an ApplicationLoadBalancedFargateService ```ts -import { Schedule } from '@aws-cdk/aws-applicationautoscaling'; -import { ApplicationLoadBalancedFargateService, ApplicationLoadBalancedFargateServiceProps } from './application-load-balanced-fargate-service'; - -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -377,12 +386,12 @@ const scalableTarget = loadBalancedFargateService.service.autoScaleTaskCount({ }); scalableTarget.scaleOnSchedule('DaytimeScaleDown', { - schedule: Schedule.cron({ hour: '8', minute: '0'}), + schedule: appscaling.Schedule.cron({ hour: '8', minute: '0'}), minCapacity: 1, }); scalableTarget.scaleOnSchedule('EveningRushScaleUp', { - schedule: Schedule.cron({ hour: '20', minute: '0'}), + schedule: appscaling.Schedule.cron({ hour: '20', minute: '0'}), minCapacity: 10, }); ``` @@ -390,9 +399,8 @@ scalableTarget.scaleOnSchedule('EveningRushScaleUp', { ### Add Metric-Based Auto-Scaling to an ApplicationLoadBalancedFargateService ```ts -import { ApplicationLoadBalancedFargateService } from './application-load-balanced-fargate-service'; - -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -419,9 +427,8 @@ scalableTarget.scaleOnMemoryUtilization('MemoryScaling', { ### Change the default Deployment Controller ```ts -import { ApplicationLoadBalancedFargateService } from './application-load-balanced-fargate-service'; - -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -443,7 +450,8 @@ deployment circuit breaker and optionally enable `rollback` for automatic rollba for more details. ```ts -const service = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -458,7 +466,8 @@ const service = new ApplicationLoadBalancedFargateService(stack, 'Service', { ### Set deployment configuration on QueueProcessingService ```ts -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { cluster, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -466,17 +475,18 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' enableLogging: false, desiredTaskCount: 2, environment: {}, - queue, maxScalingCapacity: 5, maxHealthyPercent: 200, - minHealthPercent: 66, + minHealthyPercent: 66, }); ``` ### Set taskSubnets and securityGroups for QueueProcessingFargateService ```ts -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +declare const vpc: ec2.Vpc; +declare const securityGroup: ec2.SecurityGroup; +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { vpc, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -488,7 +498,8 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' ### Define tasks with public IPs for QueueProcessingFargateService ```ts -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +declare const vpc: ec2.Vpc; +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { vpc, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -499,24 +510,24 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' ### Define tasks with custom queue parameters for QueueProcessingFargateService ```ts -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +declare const vpc: ec2.Vpc; +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { vpc, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), maxReceiveCount: 42, - retentionPeriod: cdk.Duration.days(7), - visibilityTimeout: cdk.Duration.minutes(5), + retentionPeriod: Duration.days(7), + visibilityTimeout: Duration.minutes(5), }); ``` ### Set capacityProviderStrategies for QueueProcessingFargateService ```ts -const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); -const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); +declare const cluster: ecs.Cluster; cluster.enableFargateCapacityProviders(); -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { cluster, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -536,19 +547,21 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' ### Set capacityProviderStrategies for QueueProcessingEc2Service ```ts -const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); -const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); -const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'asg', { +import * as autoscaling from '@aws-cdk/aws-autoscaling'; + +const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 1 }); +const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc }); +const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'asg', { vpc, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), machineImage: ecs.EcsOptimizedImage.amazonLinux2(), }); -const capacityProvider = new ecs.AsgCapacityProvider(stack, 'provider', { +const capacityProvider = new ecs.AsgCapacityProvider(this, 'provider', { autoScalingGroup, }); cluster.addAsgCapacityProvider(capacityProvider); -const queueProcessingFargateService = new QueueProcessingFargateService(stack, 'Service', { +const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateService(this, 'Service', { cluster, memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), @@ -563,7 +576,8 @@ const queueProcessingFargateService = new QueueProcessingFargateService(stack, ' ### Select specific vpc subnets for ApplicationLoadBalancedFargateService ```ts -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -571,8 +585,8 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }, - vpcSubnets: { - subnets: [ec2.Subnet.fromSubnetId(stack, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')], + taskSubnets: { + subnets: [ec2.Subnet.fromSubnetId(this, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')], }, }); ``` @@ -580,13 +594,14 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta ### Set PlatformVersion for ScheduledFargateTask ```ts -const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { +declare const cluster: ecs.Cluster; +const scheduledFargateTask = new ecsPatterns.ScheduledFargateTask(this, 'ScheduledFargateTask', { cluster, scheduledFargateTaskImageOptions: { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryLimitMiB: 512, }, - schedule: events.Schedule.expression('rate(1 minute)'), + schedule: appscaling.Schedule.expression('rate(1 minute)'), platformVersion: ecs.FargatePlatformVersion.VERSION1_4, }); ``` @@ -594,18 +609,17 @@ const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTa ### Set SecurityGroups for ScheduledFargateTask ```ts -const stack = new cdk.Stack(); -const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); -const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); -const securityGroup = new ec2.SecurityGroup(stack, 'SG', { vpc }); +const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 1 }); +const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc }); +const securityGroup = new ec2.SecurityGroup(this, 'SG', { vpc }); -const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { +const scheduledFargateTask = new ecsPatterns.ScheduledFargateTask(this, 'ScheduledFargateTask', { cluster, scheduledFargateTaskImageOptions: { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryLimitMiB: 512, }, - schedule: events.Schedule.expression('rate(1 minute)'), + schedule: appscaling.Schedule.expression('rate(1 minute)'), securityGroups: [securityGroup], }); ``` @@ -626,12 +640,20 @@ If a desiredCount is not passed in as input to the above constructs, CloudFormat To enable the feature flag, ensure that the REMOVE_DEFAULT_DESIRED_COUNT flag within an application stack context is set to true, like so: ```ts +declare const stack: Stack; stack.node.setContext(cxapi.ECS_REMOVE_DEFAULT_DESIRED_COUNT, true); ``` The following is an example of an application with the REMOVE_DEFAULT_DESIRED_COUNT feature flag enabled: -```ts +```ts nofixture +import { App, Stack } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as path from 'path'; + const app = new App(); const stack = new Stack(app, 'aws-ecs-patterns-queue'); @@ -641,7 +663,7 @@ const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2, }); -new QueueProcessingFargateService(stack, 'QueueProcessingService', { +new ecsPatterns.QueueProcessingFargateService(stack, 'QueueProcessingService', { vpc, memoryLimitMiB: 512, image: new ecs.AssetImage(path.join(__dirname, '..', 'sqs-reader')), @@ -653,28 +675,31 @@ new QueueProcessingFargateService(stack, 'QueueProcessingService', { The following is an example of deploying an application along with a metrics sidecar container that utilizes `dockerLabels` for discovery: ```ts -const service = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +declare const vpc: ec2.Vpc; +const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, vpc, desiredCount: 1, taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + dockerLabels: { + 'application.label.one': 'first_label', + 'application.label.two': 'second_label', + }, }, - dockerLabels: { - 'application.label.one': 'first_label' - 'application.label.two': 'second_label' - } }); service.taskDefinition.addContainer('Sidecar', { - image: ContainerImage.fromRegistry('example/metrics-sidecar') -} + image: ecs.ContainerImage.fromRegistry('example/metrics-sidecar'), +}); ``` ### Select specific load balancer name ApplicationLoadBalancedFargateService ```ts -const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { cluster, memoryLimitMiB: 1024, desiredCount: 1, @@ -682,8 +707,8 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }, - vpcSubnets: { - subnets: [ec2.Subnet.fromSubnetId(stack, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')], + taskSubnets: { + subnets: [ec2.Subnet.fromSubnetId(this, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')], }, loadBalancerName: 'application-lb-name', }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index e2cafab14e71a..f9a18d7c88bde 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -4,7 +4,14 @@ import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerDefinition, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Protocol, Secret, } from '@aws-cdk/aws-ecs'; -import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; +import { + ApplicationListener, + ApplicationLoadBalancer, + ApplicationProtocol, + ApplicationTargetGroup, ListenerCertificate, + ListenerCondition, + SslPolicy, +} from '@aws-cdk/aws-elasticloadbalancingv2'; import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets'; @@ -469,6 +476,14 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon protected registerECSTargets(service: BaseService, container: ContainerDefinition, targets: ApplicationTargetProps[]): ApplicationTargetGroup { for (const targetProps of targets) { + const conditions: Array = []; + if (targetProps.hostHeader) { + conditions.push(ListenerCondition.hostHeaders([targetProps.hostHeader])); + } + if (targetProps.pathPattern) { + conditions.push(ListenerCondition.pathPatterns([targetProps.pathPattern])); + } + const targetGroup = this.findListener(targetProps.listener).addTargets(`ECSTargetGroup${container.containerName}${targetProps.containerPort}`, { port: 80, targets: [ @@ -478,8 +493,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon protocol: targetProps.protocol, }), ], - hostHeader: targetProps.hostHeader, - pathPattern: targetProps.pathPattern, + conditions, priority: targetProps.priority, }); this.targetGroups.push(targetGroup); @@ -519,7 +533,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon certificate = undefined; } if (certificate !== undefined) { - listener.addCertificateArns(`Arns${props.listenerName}`, [certificate.certificateArn]); + listener.addCertificates(`Arns${props.listenerName}`, [ListenerCertificate.fromArn(certificate.certificateArn)]); } return listener; diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index 2c293dc3173de..67634b3cc95e0 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -320,14 +320,14 @@ export abstract class QueueProcessingServiceBase extends CoreConstruct { // Determine the desired task count (minimum) and maximum scaling capacity if (!this.node.tryGetContext(cxapi.ECS_REMOVE_DEFAULT_DESIRED_COUNT)) { - this.minCapacity = props.minScalingCapacity || this.desiredCount; + this.minCapacity = props.minScalingCapacity ?? this.desiredCount; this.maxCapacity = props.maxScalingCapacity || (2 * this.desiredCount); } else { if (props.desiredTaskCount != null) { - this.minCapacity = props.minScalingCapacity || this.desiredCount; + this.minCapacity = props.minScalingCapacity ?? this.desiredCount; this.maxCapacity = props.maxScalingCapacity || (2 * this.desiredCount); } else { - this.minCapacity = props.minScalingCapacity || 1; + this.minCapacity = props.minScalingCapacity ?? 1; this.maxCapacity = props.maxScalingCapacity || 2; } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index 19a2501355223..81f243f4a6fa6 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -86,7 +86,7 @@ export interface ApplicationLoadBalancedFargateServiceProps extends ApplicationL readonly platformVersion?: FargatePlatformVersion; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * @default - A new security group is created. */ diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts index e6f8b89c736b3..033d19dc39612 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts @@ -77,7 +77,7 @@ export interface QueueProcessingFargateServiceProps extends QueueProcessingServi readonly taskSubnets?: ec2.SubnetSelection; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * @default - A new security group is created. */ diff --git a/packages/@aws-cdk/aws-ecs-patterns/package.json b/packages/@aws-cdk/aws-ecs-patterns/package.json index 4e776d3b82cce..96a5eccecc75f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/package.json +++ b/packages/@aws-cdk/aws-ecs-patterns/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -70,8 +77,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecs-patterns/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ecs-patterns/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e998fef0a9210 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; +import * as cxapi from '@aws-cdk/cx-api'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json index 5ce81fe00ad59..2364b79c8b0b5 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json @@ -954,6 +954,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "instance", "VpcId": { "Ref": "Vpc8378EB38" @@ -965,6 +971,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "instance", "VpcId": { "Ref": "Vpc8378EB38" @@ -985,9 +997,11 @@ "Conditions": [ { "Field": "path-pattern", - "Values": [ - "a/b/c" - ] + "PathPatternConfig": { + "Values": [ + "a/b/c" + ] + } } ], "ListenerArn": { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts index 575ab92b40cfb..767c7a13f0df6 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts @@ -1,8 +1,18 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; -import { InstanceType, Vpc } from '@aws-cdk/aws-ec2'; -import { AwsLogDriver, Cluster, ContainerImage, Ec2TaskDefinition, PropagatedTagSource, Protocol } from '@aws-cdk/aws-ecs'; +import { MachineImage, Vpc } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { + AsgCapacityProvider, + AwsLogDriver, + Cluster, + ContainerImage, + Ec2TaskDefinition, + PropagatedTagSource, + Protocol, +} from '@aws-cdk/aws-ecs'; import { ApplicationProtocol, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { PublicHostedZone } from '@aws-cdk/aws-route53'; @@ -16,7 +26,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ApplicationMultipleTargetGroupsEc2Service(stack, 'Service', { @@ -74,7 +90,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -273,7 +295,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -320,7 +348,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -442,7 +476,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -530,7 +570,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'MyVpc', {}); const cluster = new Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -599,7 +645,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('test', { @@ -624,7 +676,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -639,7 +697,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -817,7 +881,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -838,7 +908,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new NetworkMultipleTargetGroupsEc2Service(stack, 'Service', { @@ -910,7 +986,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -1104,7 +1186,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -1151,7 +1239,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -1237,7 +1331,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'MyVpc', {}); const cluster = new Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -1306,7 +1406,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('test', { @@ -1331,7 +1437,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -1346,7 +1458,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -1465,7 +1583,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts index 6248c9c4d14dd..c8f29cfbc695c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts @@ -1,8 +1,11 @@ import { ABSENT, arrayWith, objectLike, SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationProtocolVersion, NetworkLoadBalancer, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; import { PublicHostedZone } from '@aws-cdk/aws-route53'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; @@ -15,7 +18,13 @@ test('test ECS loadbalanced construct', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -70,7 +79,13 @@ test('ApplicationLoadBalancedEc2Service desiredCount can be undefined when featu const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -114,7 +129,13 @@ test('NetworkLoadBalancedEc2Service desiredCount can be undefined when feature f const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -197,7 +218,13 @@ test('test ECS loadbalanced construct with memoryReservationMiB', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -225,7 +252,13 @@ test('creates AWS Cloud Map service for Private DNS namespace with application l const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -295,7 +328,13 @@ test('creates AWS Cloud Map service for Private DNS namespace with network load const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -851,7 +890,13 @@ test('ALB - throws if desiredTaskCount is 0', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -871,7 +916,13 @@ test('NLB - throws if desiredTaskCount is 0', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -943,7 +994,13 @@ test('ALB - having *HealthyPercent properties', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -971,7 +1028,13 @@ test('ALB - includes provided protocol version properties', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -999,7 +1062,13 @@ test('NLB - having *HealthyPercent properties', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1027,7 +1096,13 @@ test('ALB - having deployment controller', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1054,7 +1129,13 @@ test('NLB - having deployment controller', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1081,7 +1162,13 @@ test('ALB with circuit breaker', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1112,7 +1199,13 @@ test('NLB with circuit breaker', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1143,7 +1236,13 @@ test('NetworkLoadbalancedEC2Service accepts previously created load balancer', ( const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); const container = taskDef.addContainer('Container', { @@ -1174,7 +1273,13 @@ test('NetworkLoadBalancedEC2Service accepts imported load balancer', () => { const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, vpc, @@ -1213,7 +1318,13 @@ test('ApplicationLoadBalancedEC2Service accepts previously created load balancer const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); const alb = new ApplicationLoadBalancer(stack, 'NLB', { vpc, @@ -1248,7 +1359,13 @@ test('ApplicationLoadBalancedEC2Service accepts imported load balancer', () => { const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { loadBalancerArn: albArn, @@ -1287,7 +1404,13 @@ test('test ECS loadbalanced construct default/open security group', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1314,7 +1437,13 @@ test('test ECS loadbalanced construct closed security group', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts index 60e6a7d0f6969..9a64cfd40428e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts @@ -1,9 +1,13 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as ecsPatterns from '../../lib'; @@ -13,7 +17,13 @@ test('test ECS queue worker service construct - with only required props', () => 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -86,7 +96,13 @@ test('test ECS queue worker service construct - with remove default desiredCount const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -107,7 +123,13 @@ test('test ECS queue worker service construct - with optional props for queues', 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -177,12 +199,18 @@ test('test ECS queue worker service construct - with optional props for queues', }); }); -test('test ECS queue worker service construct - with optional props', () => { +testDeprecated('test ECS queue worker service construct - with optional props', () => { // 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'ecs-test-queue', { queueName: 'ecs-test-sqs-queue', }); @@ -272,12 +300,18 @@ test('test ECS queue worker service construct - with optional props', () => { }); }); -test('can set desiredTaskCount to 0', () => { +testDeprecated('can set desiredTaskCount to 0', () => { // 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -295,12 +329,18 @@ test('can set desiredTaskCount to 0', () => { }); }); -test('throws if desiredTaskCount and maxScalingCapacity are 0', () => { +testDeprecated('throws if desiredTaskCount and maxScalingCapacity are 0', () => { // 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -318,7 +358,13 @@ test('can set custom containerName', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts index 745d6a17460bc..9524d7a4d145c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts @@ -1,6 +1,9 @@ import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as events from '@aws-cdk/aws-events'; import * as cdk from '@aws-cdk/core'; import { ScheduledEc2Task } from '../../lib'; @@ -10,9 +13,14 @@ test('Can create a scheduled Ec2 Task - with only required props', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -70,9 +78,13 @@ test('Can create a scheduled Ec2 Task - with optional props', () => { const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -195,9 +207,13 @@ test('Scheduled Ec2 Task - with MemoryReservation defined', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -238,9 +254,13 @@ test('Scheduled Ec2 Task - with Command defined', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -287,9 +307,13 @@ test('throws if desiredTaskCount is 0', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json index 76713f6d4ef64..ee6317ba0f94f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.expected.json @@ -467,6 +467,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json index dc589f5d6c48f..1be85fbb5e688 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json @@ -453,6 +453,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json index 24b4bb012e384..e43d5c95abf8b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.circuit-breaker-load-balanced-fargate-service.expected.json @@ -453,6 +453,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json index b3921b410fabd..6d2654abe539c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json @@ -502,6 +502,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json index 61b34b57938cd..a61c1948d90fe 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-autocreate.expected.json @@ -95,6 +95,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "EcsDefaultClusterMnL3mNNYNVpc7788A521" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json index d63db295a39cc..771eef386379d 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3-vpconly.expected.json @@ -450,6 +450,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json index 886e4a35dcf63..7ea739f68ff99 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json @@ -453,6 +453,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.expected.json index f43e4e5aa4e0b..48bab449fe610 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.expected.json @@ -642,7 +642,7 @@ "Type": "AWS::ApplicationAutoScaling::ScalableTarget", "Properties": { "MaxCapacity": 2, - "MinCapacity": 1, + "MinCapacity": 0, "ResourceId": { "Fn::Join": [ "", diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.ts index 540b8d2302e9a..a0c679f4afaa8 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service.ts @@ -15,6 +15,7 @@ new QueueProcessingFargateService(stack, 'QueueProcessingService', { vpc, memoryLimitMiB: 512, image: new ecs.AssetImage(path.join(__dirname, '..', 'sqs-reader')), + minScalingCapacity: 0, }); app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json index 7fa930b16d66d..91c1f4e575e23 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json @@ -692,6 +692,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 765d86788277e..0f4c0ab29ba86 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -1,8 +1,11 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { DnsValidatedCertificate } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2'; import * as iam from '@aws-cdk/aws-iam'; import * as route53 from '@aws-cdk/aws-route53'; @@ -318,7 +321,13 @@ test('test load balanced service with family defined', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts index 916bcb43cdf63..6f91b633d1249 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts @@ -1,8 +1,12 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as ecsPatterns from '../../lib'; @@ -12,7 +16,13 @@ test('test fargate queue worker service construct - with only required props', ( 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { @@ -127,7 +137,13 @@ test('test fargate queue worker service construct - with optional props for queu 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { @@ -224,7 +240,13 @@ test('test Fargate queue worker service construct - without desiredCount specifi 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue', }); @@ -308,12 +330,18 @@ test('test Fargate queue worker service construct - without desiredCount specifi }); }); -test('test Fargate queue worker service construct - with optional props', () => { +testDeprecated('test Fargate queue worker service construct - with optional props', () => { // 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue', }); @@ -400,7 +428,13 @@ test('can set custom containerName', () => { 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') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 8ffbde1d9a0e8..2c4f04e64b257 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -23,7 +23,7 @@ The following example creates an Amazon ECS cluster, adds capacity to it, and runs a service on it: ```ts -import * as ecs from '@aws-cdk/aws-ecs'; +declare const vpc: ec2.Vpc; // Create an ECS cluster const cluster = new ecs.Cluster(this, 'Cluster', { @@ -89,8 +89,10 @@ tasks on. You can run many tasks on a single cluster. The following code creates a cluster that can run AWS Fargate tasks: ```ts +declare const vpc: ec2.Vpc; + const cluster = new ecs.Cluster(this, 'Cluster', { - vpc: vpc + vpc, }); ``` @@ -105,8 +107,10 @@ with various instance types. The following example creates an Amazon ECS cluster and adds capacity to it: ```ts +declare const vpc: ec2.Vpc; + const cluster = new ecs.Cluster(this, 'Cluster', { - vpc: vpc + vpc, }); // Either add default capacity @@ -119,7 +123,7 @@ cluster.addCapacity('DefaultAutoScalingGroupCapacity', { const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType: new ec2.InstanceType('t2.xlarge'), - machineImage: EcsOptimizedImage.amazonLinux(), + machineImage: ecs.EcsOptimizedImage.amazonLinux(), // Or use Amazon ECS-Optimized Amazon Linux 2 AMI // machineImage: EcsOptimizedImage.amazonLinux2(), desiredCapacity: 3, @@ -143,9 +147,11 @@ to periodically update to the latest AMI manually by using the [CDK CLI context management commands](https://docs.aws.amazon.com/cdk/latest/guide/context.html): ```ts +declare const vpc: ec2.Vpc; const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { - // ... - machineImage: EcsOptimizedImage.amazonLinux({ cacheInContext: true }), + machineImage: ecs.EcsOptimizedImage.amazonLinux({ cachedInContext: true }), + vpc, + instanceType: new ec2.InstanceType('t2.micro'), }); ``` @@ -159,6 +165,8 @@ The following example will create a capacity with self-managed Amazon EC2 capaci The following example adds Bottlerocket capacity to the cluster: ```ts +declare const cluster: ecs.Cluster; + cluster.addCapacity('bottlerocket-asg', { minCapacity: 2, instanceType: new ec2.InstanceType('c5.large'), @@ -174,6 +182,8 @@ for use when launching your EC2 instances that are powered by Arm-based AWS Graviton Processors. ```ts +declare const cluster: ecs.Cluster; + cluster.addCapacity('graviton-cluster', { minCapacity: 2, instanceType: new ec2.InstanceType('c6g.large'), @@ -184,10 +194,12 @@ cluster.addCapacity('graviton-cluster', { Bottlerocket is also supported: ```ts +declare const cluster: ecs.Cluster; + cluster.addCapacity('graviton-cluster', { minCapacity: 2, instanceType: new ec2.InstanceType('c6g.large'), - machineImage: ecs.MachineImageType.BOTTLEROCKET, + machineImageType: ecs.MachineImageType.BOTTLEROCKET, }); ``` @@ -196,6 +208,8 @@ cluster.addCapacity('graviton-cluster', { To add spot instances into the cluster, you must specify the `spotPrice` in the `ecs.AddCapacityOptions` and optionally enable the `spotInstanceDraining` property. ```ts +declare const cluster: ecs.Cluster; + // Add an AutoScalingGroup with spot instances to the existing cluster cluster.addCapacity('AsgSpot', { maxCapacity: 2, @@ -217,7 +231,8 @@ then you may do so by providing a KMS key for the `topicEncryptionKey` property ```ts // Given -const key = kms.Key(...); +declare const cluster: ecs.Cluster; +declare const key: kms.Key; // Then, use that key to encrypt the lifecycle-event SNS Topic. cluster.addCapacity('ASGEncryptedSNS', { instanceType: new ec2.InstanceType("t2.xlarge"), @@ -244,7 +259,7 @@ For a `FargateTaskDefinition`, specify the task size (`memoryLimitMiB` and `cpu` ```ts const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', { memoryLimitMiB: 512, - cpu: 256 + cpu: 256, }); ``` @@ -255,13 +270,17 @@ On Fargate Platform Version 1.4.0 or later, you may specify up to 200GiB of const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', { memoryLimitMiB: 512, cpu: 256, - ephemeralStorageGiB: 100 + ephemeralStorageGiB: 100, }); ``` To add containers to a task definition, call `addContainer()`: ```ts +const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', { + memoryLimitMiB: 512, + cpu: 256, +}); const container = fargateTaskDefinition.addContainer("WebContainer", { // Use an image from DockerHub image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), @@ -273,13 +292,13 @@ For a `Ec2TaskDefinition`: ```ts const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef', { - networkMode: NetworkMode.BRIDGE + networkMode: ecs.NetworkMode.BRIDGE, }); const container = ec2TaskDefinition.addContainer("WebContainer", { // Use an image from DockerHub image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), - memoryLimitMiB: 1024 + memoryLimitMiB: 1024, // ... other options here ... }); ``` @@ -292,7 +311,7 @@ const externalTaskDefinition = new ecs.ExternalTaskDefinition(this, 'TaskDef'); const container = externalTaskDefinition.addContainer("WebContainer", { // Use an image from DockerHub image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), - memoryLimitMiB: 1024 + memoryLimitMiB: 1024, // ... other options here ... }); ``` @@ -302,34 +321,42 @@ You can specify container properties when you add them to the task definition, o To add a port mapping when adding a container to the task definition, specify the `portMappings` option: ```ts +declare const taskDefinition: ecs.TaskDefinition; + taskDefinition.addContainer("WebContainer", { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 1024, - portMappings: [{ containerPort: 3000 }] + portMappings: [{ containerPort: 3000 }], }); ``` To add port mappings directly to a container definition, call `addPortMappings()`: ```ts +declare const container: ecs.ContainerDefinition; + container.addPortMappings({ - containerPort: 3000 + containerPort: 3000, }); ``` To add data volumes to a task definition, call `addVolume()`: ```ts +const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', { + memoryLimitMiB: 512, + cpu: 256, +}); const volume = { // Use an Elastic FileSystem name: "mydatavolume", - efsVolumeConfiguration: ecs.EfsVolumeConfiguration({ - fileSystemId: "EFS" + efsVolumeConfiguration: { + fileSystemId: "EFS", // ... other options here ... - }) + }, }; -const container = fargateTaskDefinition.addVolume("mydatavolume"); +const container = fargateTaskDefinition.addVolume(volume); ``` > Note: ECS Anywhere doesn't support volume attachments in the task definition. @@ -345,7 +372,7 @@ The following example uses both: const taskDefinition = new ecs.TaskDefinition(this, 'TaskDef', { memoryMiB: '512', cpu: '256', - networkMode: NetworkMode.AWS_VPC, + networkMode: ecs.NetworkMode.AWS_VPC, compatibility: ecs.Compatibility.EC2_AND_FARGATE, }); ``` @@ -372,6 +399,12 @@ obtained from either DockerHub or from ECR repositories, built directly from a l To pass environment variables to the container, you can use the `environment`, `environmentFiles`, and `secrets` props. ```ts +declare const secret: secretsmanager.Secret; +declare const dbSecret: secretsmanager.Secret; +declare const parameter: ssm.StringParameter; +declare const taskDefinition: ecs.TaskDefinition; +declare const s3Bucket: s3.Bucket; + taskDefinition.addContainer('container', { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), memoryLimitMiB: 1024, @@ -386,7 +419,7 @@ taskDefinition.addContainer('container', { SECRET: ecs.Secret.fromSecretsManager(secret), DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), // Reference a specific JSON field, (requires platform version 1.4.0 or later for Fargate tasks) PARAMETER: ecs.Secret.fromSsmParameter(parameter), - } + }, }); ``` @@ -394,6 +427,25 @@ The task execution role is automatically granted read permissions on the secrets files is restricted to the EC2 launch type for files hosted on S3. Further details provided in the AWS documentation about [specifying environment variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html). +### System controls + +To set system controls (kernel parameters) on the container, use the `systemControls` prop: + +```ts +declare const taskDefinition: ecs.TaskDefinition; + +taskDefinition.addContainer('container', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + memoryLimitMiB: 1024, + systemControls: [ + { + namespace: 'net', + value: 'ipv4.tcp_tw_recycle', + }, + ], +}); +``` + ## Service A `Service` instantiates a `TaskDefinition` on a `Cluster` a given number of @@ -402,24 +454,26 @@ If a task fails, Amazon ECS automatically restarts the task. ```ts -const taskDefinition; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition, - desiredCount: 5 + desiredCount: 5, }); ``` ECS Anywhere service definition looks like: ```ts -const taskDefinition; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; const service = new ecs.ExternalService(this, 'Service', { cluster, taskDefinition, - desiredCount: 5 + desiredCount: 5, }); ``` @@ -434,7 +488,9 @@ deployment circuit breaker and optionally enable `rollback` for automatic rollba for more details. ```ts -const service = new ecs.FargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition, circuitBreaker: { rollback: true }, @@ -448,22 +504,23 @@ const service = new ecs.FargateService(stack, 'Service', { `Services` are load balancing targets and can be added to a target group, which will be attached to an application/network load balancers: ```ts -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; - -const service = new ecs.FargateService(this, 'Service', { /* ... */ }); +declare const vpc: ec2.Vpc; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition }); const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true }); const listener = lb.addListener('Listener', { port: 80 }); const targetGroup1 = listener.addTargets('ECS1', { port: 80, - targets: [service] + targets: [service], }); const targetGroup2 = listener.addTargets('ECS2', { port: 80, targets: [service.loadBalancerTarget({ containerName: 'MyContainer', containerPort: 8080 - })] + })], }); ``` @@ -474,9 +531,10 @@ Note that in the example above, the default `service` only allows you to registe Alternatively, you can also create all load balancer targets to be registered in this service, add them to target groups, and attach target groups to listeners accordingly. ```ts -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; - -const service = new ecs.FargateService(this, 'Service', { /* ... */ }); +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +declare const vpc: ec2.Vpc; +const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition }); const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true }); const listener = lb.addListener('Listener', { port: 80 }); @@ -512,11 +570,12 @@ for the alternatives. `Services` can also be directly attached to a classic load balancer as targets: ```ts -import * as elb from '@aws-cdk/aws-elasticloadbalancing'; - -const service = new ecs.Ec2Service(this, 'Service', { /* ... */ }); +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +declare const vpc: ec2.Vpc; +const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition }); -const lb = new elb.LoadBalancer(stack, 'LB', { vpc }); +const lb = new elb.LoadBalancer(this, 'LB', { vpc }); lb.addListener({ externalPort: 80 }); lb.addTarget(service); ``` @@ -524,15 +583,16 @@ lb.addTarget(service); Similarly, if you want to have more control over load balancer targeting: ```ts -import * as elb from '@aws-cdk/aws-elasticloadbalancing'; +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +declare const vpc: ec2.Vpc; +const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition }); -const service = new ecs.Ec2Service(this, 'Service', { /* ... */ }); - -const lb = new elb.LoadBalancer(stack, 'LB', { vpc }); +const lb = new elb.LoadBalancer(this, 'LB', { vpc }); lb.addListener({ externalPort: 80 }); lb.addTarget(service.loadBalancerTarget({ containerName: 'MyContainer', - containerPort: 80 + containerPort: 80, })); ``` @@ -547,15 +607,17 @@ You can configure the task count of a service to match demand. Task auto-scaling configured by calling `autoScaleTaskCount()`: ```ts +declare const target: elbv2.ApplicationTargetGroup; +declare const service: ecs.BaseService; const scaling = service.autoScaleTaskCount({ maxCapacity: 10 }); scaling.scaleOnCpuUtilization('CpuScaling', { - targetUtilizationPercent: 50 + targetUtilizationPercent: 50, }); scaling.scaleOnRequestCount('RequestScaling', { requestsPerTarget: 10000, - targetGroup: target -}) + targetGroup: target, +}); ``` Task auto-scaling is powered by *Application Auto-Scaling*. @@ -567,19 +629,18 @@ To start an Amazon ECS task on an Amazon EC2-backed Cluster, instantiate an `@aws-cdk/aws-events-targets.EcsTask` instead of an `Ec2Service`: ```ts -import * as targets from '@aws-cdk/aws-events-targets'; - +declare const cluster: ecs.Cluster; // Create a Task Definition for the container to start const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, '..', 'eventhandler-image')), memoryLimitMiB: 256, - logging: new ecs.AwsLogDriver({ streamPrefix: 'EventDemo', mode: AwsLogDriverMode.NON_BLOCKING }) + logging: new ecs.AwsLogDriver({ streamPrefix: 'EventDemo', mode: ecs.AwsLogDriverMode.NON_BLOCKING }), }); // An Rule that describes the event trigger (in this case a scheduled run) const rule = new events.Rule(this, 'Rule', { - schedule: events.Schedule.expression('rate(1 min)') + schedule: events.Schedule.expression('rate(1 min)'), }); // Pass an environment variable to the container 'TheContainer' in the task @@ -592,8 +653,8 @@ rule.addTarget(new targets.EcsTask({ environment: [{ name: 'I_WAS_TRIGGERED', value: 'From CloudWatch Events' - }] - }] + }], + }], })); ``` @@ -609,6 +670,7 @@ Currently Supported Log Drivers: - splunk - syslog - awsfirelens +- Generic ### awslogs Log Driver @@ -618,7 +680,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'EventDemo' }) + logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'EventDemo' }), }); ``` @@ -630,7 +692,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.fluentd() + logging: ecs.LogDrivers.fluentd(), }); ``` @@ -642,7 +704,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.gelf({ address: 'my-gelf-address' }) + logging: ecs.LogDrivers.gelf({ address: 'my-gelf-address' }), }); ``` @@ -654,7 +716,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.journald() + logging: ecs.LogDrivers.journald(), }); ``` @@ -666,7 +728,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.jsonFile() + logging: ecs.LogDrivers.jsonFile(), }); ``` @@ -679,9 +741,9 @@ taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, logging: ecs.LogDrivers.splunk({ - secretToken: cdk.SecretValue.secretsManager('my-splunk-token'), - url: 'my-splunk-url' - }) + token: SecretValue.secretsManager('my-splunk-token'), + url: 'my-splunk-url', + }), }); ``` @@ -693,7 +755,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), memoryLimitMiB: 256, - logging: ecs.LogDrivers.syslog() + logging: ecs.LogDrivers.syslog(), }); ``` @@ -710,14 +772,17 @@ taskDefinition.addContainer('TheContainer', { Name: 'firehose', region: 'us-west-2', delivery_stream: 'my-stream', - } - }) + }, + }), }); ``` To pass secrets to the log configuration, use the `secretOptions` property of the log configuration. The task execution role is automatically granted read permissions on the secrets/parameters. ```ts +declare const secret: secretsmanager.Secret; +declare const parameter: ssm.StringParameter; + const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('example-image'), @@ -730,7 +795,7 @@ taskDefinition.addContainer('TheContainer', { apikey: ecs.Secret.fromSecretsManager(secret), host: ecs.Secret.fromSsmParameter(parameter), }, - }) + }), }); ``` @@ -747,9 +812,9 @@ taskDefinition.addContainer('TheContainer', { logging: new ecs.GenericLogDriver({ logDriver: 'fluentd', options: { - tag: 'example-tag' - } - }) + tag: 'example-tag', + }, + }), }); ``` @@ -759,7 +824,10 @@ To register your ECS service with a CloudMap Service Registry, you may add the `cloudMapOptions` property to your service: ```ts -const service = new ecs.Ec2Service(stack, 'Service', { +declare const taskDefinition: ecs.TaskDefinition; +declare const cluster: ecs.Cluster; + +const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition, cloudMapOptions: { @@ -774,8 +842,14 @@ By default, `SRV` DNS record types will target the default container and default port. However, you may target a different container and port on the same ECS task: ```ts +declare const taskDefinition: ecs.TaskDefinition; +declare const cluster: ecs.Cluster; + // Add a container to the task definition -const specificContainer = taskDefinition.addContainer(...); +const specificContainer = taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + memoryLimitMiB: 2048, +}); // Add a port mapping specificContainer.addPortMappings({ @@ -783,7 +857,7 @@ specificContainer.addPortMappings({ protocol: ecs.Protocol.TCP, }); -new ecs.Ec2Service(stack, 'Service', { +new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition, cloudMapOptions: { @@ -802,8 +876,8 @@ You may associate an ECS service with a specific CloudMap service. To do this, use the service's `associateCloudMapService` method: ```ts -const cloudMapService = new cloudmap.Service(...); -const ecsService = new ecs.FargateService(...); +declare const cloudMapService: cloudmap.Service; +declare const ecsService: ecs.FargateService; ecsService.associateCloudMapService({ service: cloudMapService, @@ -827,18 +901,20 @@ cluster. This will add both `FARGATE` and `FARGATE_SPOT` as available capacity providers on your cluster. ```ts -const cluster = new ecs.Cluster(stack, 'FargateCPCluster', { +declare const vpc: ec2.Vpc; + +const cluster = new ecs.Cluster(this, 'FargateCPCluster', { vpc, enableFargateCapacityProviders: true, }); -const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); +const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }); -new ecs.FargateService(stack, 'FargateService', { +new ecs.FargateService(this, 'FargateService', { cluster, taskDefinition, capacityProviderStrategies: [ @@ -849,7 +925,7 @@ new ecs.FargateService(stack, 'FargateService', { { capacityProvider: 'FARGATE', weight: 1, - } + }, ], }); ``` @@ -869,11 +945,13 @@ running on them. If you want to disable this behavior, set both `enableManagedScaling` to and `enableManagedTerminationProtection` to `false`. ```ts -const cluster = new ecs.Cluster(stack, 'Cluster', { +declare const vpc: ec2.Vpc; + +const cluster = new ecs.Cluster(this, 'Cluster', { vpc, }); -const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'ASG', { +const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType: new ec2.InstanceType('t2.micro'), machineImage: ecs.EcsOptimizedImage.amazonLinux2(), @@ -881,26 +959,26 @@ const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'ASG', { maxCapacity: 100, }); -const capacityProvider = new ecs.AsgCapacityProvider(stack, 'AsgCapacityProvider', { +const capacityProvider = new ecs.AsgCapacityProvider(this, 'AsgCapacityProvider', { autoScalingGroup, }); cluster.addAsgCapacityProvider(capacityProvider); -const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); +const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryReservationMiB: 256, }); -new ecs.Ec2Service(stack, 'EC2Service', { +new ecs.Ec2Service(this, 'EC2Service', { cluster, taskDefinition, capacityProviderStrategies: [ { capacityProvider: capacityProvider.capacityProviderName, weight: 1, - } + }, ], }); ``` @@ -919,7 +997,7 @@ const inferenceAccelerators = [{ deviceType: 'eia2.medium', }]; -const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { +const taskDefinition = new ecs.Ec2TaskDefinition(this, 'Ec2TaskDef', { inferenceAccelerators, }); ``` @@ -929,6 +1007,7 @@ field and set it to a list of device names used for the inference accelerators. list should match a `DeviceName` for an `InferenceAccelerator` specified in the task definition. ```ts +declare const taskDefinition: ecs.TaskDefinition; const inferenceAcceleratorResources = ['device1']; taskDefinition.addContainer('cont', { @@ -948,7 +1027,10 @@ To enable the ECS Exec feature for your containers, set the boolean flag `enable your `Ec2Service` or `FargateService`. ```ts -const service = new ecs.Ec2Service(stack, 'Service', { +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; + +const service = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition, enableExecuteCommand: true, @@ -967,19 +1049,20 @@ of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch to these resources on creation. ```ts -const kmsKey = new kms.Key(stack, 'KmsKey'); +declare const vpc: ec2.Vpc; +const kmsKey = new kms.Key(this, 'KmsKey'); // Pass the KMS key in the `encryptionKey` field to associate the key to the log group -const logGroup = new logs.LogGroup(stack, 'LogGroup', { +const logGroup = new logs.LogGroup(this, 'LogGroup', { encryptionKey: kmsKey, }); // Pass the KMS key in the `encryptionKey` field to associate the key to the S3 bucket -const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { +const execBucket = new s3.Bucket(this, 'EcsExecBucket', { encryptionKey: kmsKey, }); -const cluster = new ecs.Cluster(stack, 'Cluster', { +const cluster = new ecs.Cluster(this, 'Cluster', { vpc, executeCommandConfiguration: { kmsKey, diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index ce5097e9ebccf..1809000064f12 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -570,6 +570,8 @@ export abstract class BaseService extends Resource * * @example * + * declare const listener: elbv2.ApplicationListener; + * declare const service: ecs.BaseService; * listener.addTargets('ECS', { * port: 80, * targets: [service.loadBalancerTarget({ @@ -605,6 +607,8 @@ export abstract class BaseService extends Resource * * @example * + * declare const listener: elbv2.ApplicationListener; + * declare const service: ecs.BaseService; * service.registerLoadBalancerTargets( * { * containerName: 'web', @@ -746,7 +750,7 @@ export abstract class BaseService extends Resource return new cloudwatch.Metric({ namespace: 'AWS/ECS', metricName, - dimensions: { ClusterName: this.cluster.clusterName, ServiceName: this.serviceName }, + dimensionsMap: { ClusterName: this.cluster.clusterName, ServiceName: this.serviceName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts index 80ec042626153..8dfc272300d41 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts @@ -1,4 +1,4 @@ -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IBaseService } from '../base/base-service'; import { ICluster } from '../cluster'; @@ -47,12 +47,14 @@ export function fromServiceAtrributes(scope: Construct, id: string, attrs: Servi }); } else { arn = attrs.serviceArn as string; - name = stack.parseArn(arn).resourceName as string; + name = stack.splitArn(arn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } class Import extends Resource implements IBaseService { public readonly serviceArn = arn; public readonly serviceName = name; public readonly cluster = attrs.cluster; } - return new Import(scope, id); -} \ No newline at end of file + return new Import(scope, id, { + environmentFromArn: arn, + }); +} diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 49d99bf68925d..cf9e67960e10e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -324,15 +324,15 @@ export class Cluster extends Resource implements ICluster { * * @param provider the capacity provider to add to this cluster. */ - public addAsgCapacityProvider(provider: AsgCapacityProvider, options: AddAutoScalingGroupCapacityOptions = {}) { + public addAsgCapacityProvider(provider: AsgCapacityProvider, options: AddAutoScalingGroupCapacityOptions= {}) { // Don't add the same capacity provider more than once. if (this._capacityProviderNames.includes(provider.capacityProviderName)) { return; } - this._hasEc2Capacity = true; this.configureAutoScalingGroup(provider.autoScalingGroup, { ...options, + machineImageType: provider.machineImageType, // Don't enable the instance-draining lifecycle hook if managed termination protection is enabled taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime, }); @@ -555,7 +555,7 @@ export class Cluster extends Resource implements ICluster { return new cloudwatch.Metric({ namespace: 'AWS/ECS', metricName, - dimensions: { ClusterName: this.clusterName }, + dimensionsMap: { ClusterName: this.clusterName }, ...props, }).attachTo(this); } @@ -821,11 +821,9 @@ export interface AddCapacityOptions extends AddAutoScalingGroupCapacityOptions, * To use an image that does not update on every deployment, pass: * * ```ts - * { - * machineImage: EcsOptimizedImage.amazonLinux2(AmiHardwareType.STANDARD, { - * cachedInContext: true, - * }), - * } + * const machineImage = ecs.EcsOptimizedImage.amazonLinux2(ecs.AmiHardwareType.STANDARD, { + * cachedInContext: true, + * }); * ``` * * For more information, see [Amazon ECS-optimized @@ -1064,6 +1062,11 @@ export class AsgCapacityProvider extends CoreConstruct { */ readonly autoScalingGroup: autoscaling.AutoScalingGroup; + /** + * Auto Scaling Group machineImageType. + */ + readonly machineImageType: MachineImageType; + /** * Whether managed termination protection is enabled */ @@ -1074,6 +1077,8 @@ export class AsgCapacityProvider extends CoreConstruct { this.autoScalingGroup = props.autoScalingGroup as autoscaling.AutoScalingGroup; + this.machineImageType = props.machineImageType ?? MachineImageType.AMAZON_LINUX_2; + this.enableManagedTerminationProtection = props.enableManagedTerminationProtection === undefined ? true : props.enableManagedTerminationProtection; @@ -1132,4 +1137,4 @@ class MaybeCreateCapacityProviderAssociations implements IAspect { function isBottleRocketImage(image: ec2.IMachineImage) { return image instanceof BottleRocketImage; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index f817a5280315e..625041468a0a4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -307,6 +307,15 @@ export interface ContainerDefinitionOptions { * @default - No inference accelerators assigned. */ readonly inferenceAcceleratorResources?: string[]; + + /** + * A list of namespaced kernel parameters to set in the container. + * + * @default - No system controls are set. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-systemcontrol.html + * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_systemcontrols + */ + readonly systemControls?: SystemControl[]; } /** @@ -669,6 +678,7 @@ export class ContainerDefinition extends CoreConstruct { linuxParameters: this.linuxParameters && this.linuxParameters.renderLinuxParameters(), resourceRequirements: (!this.props.gpuCount && this.inferenceAcceleratorResources.length == 0 ) ? undefined : renderResourceRequirements(this.props.gpuCount, this.inferenceAcceleratorResources), + systemControls: this.props.systemControls && renderSystemControls(this.props.systemControls), }; } } @@ -1040,3 +1050,25 @@ function renderVolumeFrom(vf: VolumeFrom): CfnTaskDefinition.VolumeFromProperty readOnly: vf.readOnly, }; } + +/** + * Kernel parameters to set in the container + */ +export interface SystemControl { + /** + * The namespaced kernel parameter for which to set a value. + */ + readonly namespace: string; + + /** + * The value for the namespaced kernel parameter specified in namespace. + */ + readonly value: string; +} + +function renderSystemControls(systemControls: SystemControl[]): CfnTaskDefinition.SystemControlProperty[] { + return systemControls.map(sc => ({ + namespace: sc.namespace, + value: sc.value, + })); +} diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index df2ad7819543c..eca66a4db6ff3 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -1,5 +1,5 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -39,7 +39,7 @@ export interface Ec2ServiceProps extends BaseServiceOptions { readonly vpcSubnets?: ec2.SubnetSelection; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * This property is only used for tasks that use the awsvpc network mode. * @@ -49,7 +49,7 @@ export interface Ec2ServiceProps extends BaseServiceOptions { readonly securityGroup?: ec2.ISecurityGroup; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * This property is only used for tasks that use the awsvpc network mode. * @@ -128,7 +128,7 @@ export class Ec2Service extends BaseService implements IEc2Service { public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service { class Import extends Resource implements IEc2Service { public readonly serviceArn = ec2ServiceArn; - public readonly serviceName = Stack.of(scope).parseArn(ec2ServiceArn).resourceName as string; + public readonly serviceName = Stack.of(scope).splitArn(ec2ServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts index 741ba0a7b2b29..9bb1eaf0b8cef 100644 --- a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts @@ -2,7 +2,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssociateCloudMapServiceOptions, BaseService, BaseServiceOptions, CloudMapOptions, DeploymentControllerType, EcsTarget, IBaseService, IEcsLoadBalancerTarget, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -21,7 +21,7 @@ export interface ExternalServiceProps extends BaseServiceOptions { readonly taskDefinition: TaskDefinition; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * * @default - A new security group is created. @@ -73,7 +73,7 @@ export class ExternalService extends BaseService implements IExternalService { public static fromExternalServiceArn(scope: Construct, id: string, externalServiceArn: string): IExternalService { class Import extends Resource implements IExternalService { public readonly serviceArn = externalServiceArn; - public readonly serviceName = Stack.of(scope).parseArn(externalServiceArn).resourceName as string; + public readonly serviceName = Stack.of(scope).splitArn(externalServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } @@ -187,4 +187,4 @@ export class ExternalService extends BaseService implements IExternalService { public associateCloudMapService(_options: AssociateCloudMapServiceOptions): void { throw new Error ('Cloud map service association is not supported for an external service'); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 7979b98f0b0fa..b654c87887dda 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -34,7 +35,7 @@ export interface FargateServiceProps extends BaseServiceOptions { readonly vpcSubnets?: ec2.SubnetSelection; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * @default - A new security group is created. * @deprecated use securityGroups instead. @@ -42,7 +43,7 @@ export interface FargateServiceProps extends BaseServiceOptions { readonly securityGroup?: ec2.ISecurityGroup; /** - * The security groups to associate with the service. If you do not specify a security group, the default security group for the VPC is used. + * The security groups to associate with the service. If you do not specify a security group, a new security group is created. * * @default - A new security group is created. */ @@ -104,7 +105,7 @@ export class FargateService extends BaseService implements IFargateService { public static fromFargateServiceArn(scope: Construct, id: string, fargateServiceArn: string): IFargateService { class Import extends cdk.Resource implements IFargateService { public readonly serviceArn = fargateServiceArn; - public readonly serviceName = cdk.Stack.of(scope).parseArn(fargateServiceArn).resourceName as string; + public readonly serviceName = cdk.Stack.of(scope).splitArn(fargateServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index bd076ccfd05f7..09d355dd18b38 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -36,6 +36,7 @@ export * from './log-drivers/json-file-log-driver'; export * from './log-drivers/splunk-log-driver'; export * from './log-drivers/syslog-log-driver'; export * from './log-drivers/log-driver'; +export * from './log-drivers/generic-log-driver'; export * from './log-drivers/log-drivers'; export * from './proxy-configuration/app-mesh-proxy-configuration'; diff --git a/packages/@aws-cdk/aws-ecs/lib/log-drivers/generic-log-driver.ts b/packages/@aws-cdk/aws-ecs/lib/log-drivers/generic-log-driver.ts index 9e356a8cd3c19..8181b7d689d44 100644 --- a/packages/@aws-cdk/aws-ecs/lib/log-drivers/generic-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/lib/log-drivers/generic-log-driver.ts @@ -1,5 +1,5 @@ import { ContainerDefinition, Secret } from '../container-definition'; -import { LogDriver, LogDriverConfig } from '../index'; +import { LogDriver, LogDriverConfig } from './log-driver'; import { removeEmpty, renderLogDriverSecretOptions } from './utils'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index d1839630a6eff..dc6c57ffe9285 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,9 +86,9 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/proxyquire": "^1.3.28", - "jest": "^26.6.3", + "jest": "^27.3.1", "proxyquire": "^2.1.3" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-ecs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ecs/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..2cc599faf6c3f --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/rosetta/default.ts-fixture @@ -0,0 +1,24 @@ +// Fixture with packages imported, but nothing else +import { Construct, SecretValue, Stack } from '@aws-cdk/core'; +import autoscaling = require('@aws-cdk/aws-autoscaling'); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); +import ecs = require('@aws-cdk/aws-ecs'); +import ec2 = require('@aws-cdk/aws-ec2'); +import elb = require('@aws-cdk/aws-elasticloadbalancing'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); +import events = require('@aws-cdk/aws-events'); +import kms = require('@aws-cdk/aws-kms'); +import logs = require('@aws-cdk/aws-logs'); +import s3 = require('@aws-cdk/aws-s3'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import ssm = require('@aws-cdk/aws-ssm'); +import targets = require('@aws-cdk/aws-events-targets'); +import path = require('path'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index 52de16b353137..51edbb3aca17d 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -6,12 +6,13 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../lib'; describe('cluster', () => { describe('When creating an ECS Cluster', () => { - test('with no properties set, it correctly sets default properties', () => { + testDeprecated('with no properties set, it correctly sets default properties', () => { // GIVEN const stack = new cdk.Stack(); const cluster = new ecs.Cluster(stack, 'EcsCluster'); @@ -179,7 +180,7 @@ describe('cluster', () => { }); - test('with only vpc set, it correctly sets default properties', () => { + testDeprecated('with only vpc set, it correctly sets default properties', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -350,7 +351,7 @@ describe('cluster', () => { }); - test('multiple clusters with default capacity', () => { + testDeprecated('multiple clusters with default capacity', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -366,7 +367,7 @@ describe('cluster', () => { }); - test('lifecycle hook is automatically added', () => { + testDeprecated('lifecycle hook is automatically added', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -505,7 +506,7 @@ describe('cluster', () => { }); - test('lifecycle hook with encrypted SNS is added correctly', () => { + testDeprecated('lifecycle hook with encrypted SNS is added correctly', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -533,7 +534,7 @@ describe('cluster', () => { }); - test('with capacity and cloudmap namespace properties set', () => { + testDeprecated('with capacity and cloudmap namespace properties set', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -715,7 +716,7 @@ describe('cluster', () => { }); }); - test('allows specifying instance type', () => { + testDeprecated('allows specifying instance type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -733,7 +734,7 @@ describe('cluster', () => { }); - test('allows specifying cluster size', () => { + testDeprecated('allows specifying cluster size', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -752,7 +753,7 @@ describe('cluster', () => { }); - test('configures userdata with powershell if windows machine image is specified', () => { + testDeprecated('configures userdata with powershell if windows machine image is specified', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -808,7 +809,7 @@ describe('cluster', () => { /* * TODO:v2.0.0 BEGINNING OF OBSOLETE BLOCK */ - test('allows specifying special HW AMI Type', () => { + testDeprecated('allows specifying special HW AMI Type', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -841,7 +842,7 @@ describe('cluster', () => { }); - test('errors if amazon linux given with special HW type', () => { + testDeprecated('errors if amazon linux given with special HW type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -862,7 +863,7 @@ describe('cluster', () => { }); - test('allows specifying windows image', () => { + testDeprecated('allows specifying windows image', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -889,7 +890,7 @@ describe('cluster', () => { }); - test('errors if windows given with special HW type', () => { + testDeprecated('errors if windows given with special HW type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -910,7 +911,7 @@ describe('cluster', () => { }); - test('errors if windowsVersion and linux generation are set', () => { + testDeprecated('errors if windowsVersion and linux generation are set', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -931,7 +932,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for windows for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for windows for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -943,7 +944,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for linux for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for linux for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -955,7 +956,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for linux 2 for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for linux 2 for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -1012,7 +1013,7 @@ describe('cluster', () => { * TODO:v2.0.0 END OF OBSOLETE BLOCK */ - test('allows specifying special HW AMI Type v2', () => { + testDeprecated('allows specifying special HW AMI Type v2', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1043,7 +1044,7 @@ describe('cluster', () => { }); - test('allows specifying Amazon Linux v1 AMI', () => { + testDeprecated('allows specifying Amazon Linux v1 AMI', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1074,7 +1075,7 @@ describe('cluster', () => { }); - test('allows specifying windows image v2', () => { + testDeprecated('allows specifying windows image v2', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1099,7 +1100,7 @@ describe('cluster', () => { }); - test('allows specifying spot fleet', () => { + testDeprecated('allows specifying spot fleet', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1118,7 +1119,7 @@ describe('cluster', () => { }); - test('allows specifying drain time', () => { + testDeprecated('allows specifying drain time', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1137,7 +1138,7 @@ describe('cluster', () => { }); - test('allows specifying automated spot draining', () => { + testDeprecated('allows specifying automated spot draining', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1170,7 +1171,7 @@ describe('cluster', () => { }); - test('allows containers access to instance metadata service', () => { + testDeprecated('allows containers access to instance metadata service', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1202,7 +1203,7 @@ describe('cluster', () => { }); - test('allows adding default service discovery namespace', () => { + testDeprecated('allows adding default service discovery namespace', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1228,7 +1229,7 @@ describe('cluster', () => { }); - test('allows adding public service discovery namespace', () => { + testDeprecated('allows adding public service discovery namespace', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1254,7 +1255,7 @@ describe('cluster', () => { }); - test('throws if default service discovery namespace added more than once', () => { + testDeprecated('throws if default service discovery namespace added more than once', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1380,7 +1381,7 @@ describe('cluster', () => { }); - test('ASG with a public VPC without NAT Gateways', () => { + testDeprecated('ASG with a public VPC without NAT Gateways', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyPublicVpc', { @@ -1580,7 +1581,7 @@ describe('cluster', () => { }); - test('cluster capacity with bottlerocket AMI, by setting machineImageType', () => { + testDeprecated('cluster capacity with bottlerocket AMI, by setting machineImageType', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1672,7 +1673,7 @@ describe('cluster', () => { }); - test('correct bottlerocket AMI for ARM64 architecture', () => { + testDeprecated('correct bottlerocket AMI for ARM64 architecture', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1701,7 +1702,7 @@ describe('cluster', () => { }); - test('throws when machineImage and machineImageType both specified', () => { + testDeprecated('throws when machineImage and machineImageType both specified', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1732,7 +1733,7 @@ describe('cluster', () => { }); - test('allows specifying capacityProviders (deprecated)', () => { + testDeprecated('allows specifying capacityProviders (deprecated)', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1795,7 +1796,7 @@ describe('cluster', () => { }); - test('allows adding capacityProviders post-construction (deprecated)', () => { + testDeprecated('allows adding capacityProviders post-construction (deprecated)', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1817,7 +1818,7 @@ describe('cluster', () => { }); - test('allows adding capacityProviders post-construction', () => { + testDeprecated('allows adding capacityProviders post-construction', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1839,7 +1840,7 @@ describe('cluster', () => { }); - test('throws for unsupported capacity providers', () => { + testDeprecated('throws for unsupported capacity providers', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -2142,3 +2143,107 @@ describe('cluster', () => { }); }); + +test('can add ASG capacity via Capacity Provider by not specifying machineImageType', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + const autoScalingGroupAl2 = new autoscaling.AutoScalingGroup(stack, 'asgal2', { + vpc, + instanceType: new ec2.InstanceType('bogus'), + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), + }); + + const autoScalingGroupBottlerocket = new autoscaling.AutoScalingGroup(stack, 'asgBottlerocket', { + vpc, + instanceType: new ec2.InstanceType('bogus'), + machineImage: new ecs.BottleRocketImage(), + }); + + // WHEN + const capacityProviderAl2 = new ecs.AsgCapacityProvider(stack, 'provideral2', { + autoScalingGroup: autoScalingGroupAl2, + enableManagedTerminationProtection: false, + }); + + const capacityProviderBottlerocket = new ecs.AsgCapacityProvider(stack, 'providerBottlerocket', { + autoScalingGroup: autoScalingGroupBottlerocket, + enableManagedTerminationProtection: false, + machineImageType: ecs.MachineImageType.BOTTLEROCKET, + }); + + cluster.enableFargateCapacityProviders(); + + // Ensure not added twice + cluster.addAsgCapacityProvider(capacityProviderAl2); + cluster.addAsgCapacityProvider(capacityProviderAl2); + + // Add Bottlerocket ASG Capacity Provider + cluster.addAsgCapacityProvider(capacityProviderBottlerocket); + + + // THEN Bottlerocket LaunchConfiguration + expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + ImageId: { + Ref: 'SsmParameterValueawsservicebottlerocketawsecs1x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter', + + }, + UserData: { + 'Fn::Base64': { + 'Fn::Join': [ + '', + [ + '\n[settings.ecs]\ncluster = \"', + { + Ref: 'EcsCluster97242B84', + }, + '\"', + ], + ], + }, + }, + }); + + // THEN AmazonLinux2 LaunchConfiguration + expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + ImageId: { + Ref: 'SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter', + }, + UserData: { + 'Fn::Base64': { + 'Fn::Join': [ + '', + [ + '#!/bin/bash\necho ECS_CLUSTER=', + { + Ref: 'EcsCluster97242B84', + + }, + ' >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config', + ], + ], + }, + }, + }); + + expect(stack).toHaveResource('AWS::ECS::ClusterCapacityProviderAssociations', { + CapacityProviders: [ + 'FARGATE', + 'FARGATE_SPOT', + { + Ref: 'provideral2A427CBC0', + }, + { + Ref: 'providerBottlerocket90C039FA', + }, + ], + Cluster: { + Ref: 'EcsCluster97242B84', + }, + DefaultCapacityProviderStrategy: [], + }); + +}); diff --git a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts index a5153b82d331a..a384294900342 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -85,6 +85,9 @@ describe('container definition', () => { secrets: { SECRET: ecs.Secret.fromSecretsManager(secret), }, + systemControls: [ + { namespace: 'SomeNamespace', value: 'SomeValue' }, + ], }); // THEN @@ -218,6 +221,12 @@ describe('container definition', () => { ], StartTimeout: 2, StopTimeout: 5, + SystemControls: [ + { + Namespace: 'SomeNamespace', + Value: 'SomeValue', + }, + ], User: 'rootUser', WorkingDirectory: 'a/b/c', }, @@ -753,6 +762,40 @@ describe('container definition', () => { }); }); + test('can specify system controls', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + systemControls: [ + { namespace: 'SomeNamespace1', value: 'SomeValue1' }, + { namespace: 'SomeNamespace2', value: 'SomeValue2' }, + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + SystemControls: [ + { + Namespace: 'SomeNamespace1', + Value: 'SomeValue1', + }, + { + Namespace: 'SomeNamespace2', + Value: 'SomeValue2', + }, + ], + }, + ], + }); + }); + describe('Environment Files', () => { describe('with EC2 task definitions', () => { test('can add asset environment file to the container definition', () => { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts index 7decd0047975e..aebf98820d885 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts @@ -3,6 +3,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { App, Stack } from '@aws-cdk/core'; import * as ecs from '../../lib'; +import { addDefaultCapacityProvider } from '../util'; // Test various cross-stack Cluster/Service/ALB scenario's @@ -20,8 +21,8 @@ describe('cross stack', () => { const vpc = new ec2.Vpc(stack1, 'Vpc'); cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, - capacity: { instanceType: new ec2.InstanceType('t2.micro') }, }); + addDefaultCapacityProvider(cluster, stack1, vpc); stack2 = new Stack(app, 'Stack2'); const taskDefinition = new ecs.Ec2TaskDefinition(stack2, 'TD'); @@ -94,6 +95,6 @@ function expectIngress(stack: Stack) { expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { FromPort: 32768, ToPort: 65535, - GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttClusterDefaultAutoScalingGroupInstanceSecurityGroup1D15236AGroupIdEAB9C5E1' }, + GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttDefaultAutoScalingGroupInstanceSecurityGroupFBA881D0GroupId2F7C804A' }, }); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 48da04142ff3c..7e1f5532fc207 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -8,10 +8,12 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { DeploymentControllerType, LaunchType, PropagatedTagSource } from '../../lib/base/base-service'; import { PlacementConstraint, PlacementStrategy } from '../../lib/placement'; +import { addDefaultCapacityProvider } from '../util'; describe('ec2 service', () => { describe('When creating an EC2 Service', () => { @@ -20,7 +22,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -60,7 +62,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -145,7 +147,7 @@ describe('ec2 service', () => { logging: ecs.ExecuteCommandLogging.NONE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const logGroup = new logs.LogGroup(stack, 'LogGroup'); @@ -214,7 +216,7 @@ describe('ec2 service', () => { logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -334,7 +336,7 @@ describe('ec2 service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -540,7 +542,7 @@ describe('ec2 service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -780,7 +782,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -848,7 +850,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -882,12 +884,12 @@ describe('ec2 service', () => { deploymentController: { type: ecs.DeploymentControllerType.CODE_DEPLOY, }, - securityGroup: new ec2.SecurityGroup(stack, 'SecurityGroup1', { + securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', securityGroupName: 'Bob', vpc, - }), + })], serviceName: 'bonjour', vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); @@ -1019,7 +1021,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1128,12 +1130,12 @@ describe('ec2 service', () => { }); - test('throws when both securityGroup and securityGroups are supplied', () => { + testDeprecated('throws when both securityGroup and securityGroups are supplied', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1203,7 +1205,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1220,7 +1222,7 @@ describe('ec2 service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadataEntry[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).toHaveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -1241,7 +1243,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1266,7 +1268,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1291,7 +1293,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1338,7 +1340,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); new ecs.Ec2Service(stack, 'FargateService', { @@ -1369,7 +1371,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1401,7 +1403,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1431,7 +1433,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1464,7 +1466,7 @@ describe('ec2 service', () => { cidrBlock: '10.10.0.0/20', }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1494,7 +1496,7 @@ describe('ec2 service', () => { const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const securityGroup = new ec2.SecurityGroup(stack, 'MySG', { vpc }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1508,7 +1510,7 @@ describe('ec2 service', () => { new ecs.Ec2Service(stack, 'Ec2Service', { cluster, taskDefinition, - securityGroup, + securityGroups: [securityGroup], }); }).toThrow(/vpcSubnets, securityGroup\(s\) and assignPublicIp can only be used in AwsVpc networking mode/); @@ -1525,7 +1527,7 @@ describe('ec2 service', () => { new ec2.SecurityGroup(stack, 'MySecondSG', { vpc }), ]; const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1554,7 +1556,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1602,7 +1604,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1630,7 +1632,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1659,7 +1661,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1690,7 +1692,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1722,7 +1724,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1772,7 +1774,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1798,7 +1800,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1825,7 +1827,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1846,12 +1848,12 @@ describe('ec2 service', () => { }); - test('with both propagateTags and propagateTaskTagsFrom defined', () => { + testDeprecated('with both propagateTags and propagateTaskTagsFrom defined', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1875,7 +1877,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1902,7 +1904,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1932,7 +1934,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1959,7 +1961,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1990,7 +1992,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2021,7 +2023,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2052,7 +2054,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2081,7 +2083,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2105,7 +2107,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.BRIDGE }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2129,7 +2131,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.AWS_VPC }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2155,7 +2157,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.NONE }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2243,7 +2245,8 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); + cluster.connections.addSecurityGroup(); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2291,7 +2294,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2338,7 +2341,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2384,7 +2387,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2493,7 +2496,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2534,7 +2537,7 @@ describe('ec2 service', () => { 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') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2575,7 +2578,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2604,7 +2607,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.NONE, }); @@ -2635,7 +2638,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2711,7 +2714,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.HOST, @@ -2788,7 +2791,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2822,7 +2825,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, @@ -2897,7 +2900,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, @@ -2975,7 +2978,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3026,7 +3029,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3068,7 +3071,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3112,7 +3115,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3159,7 +3162,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3201,7 +3204,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3238,7 +3241,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('Container', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -3292,6 +3295,8 @@ describe('ec2 service', () => { expect(service.serviceArn).toEqual('arn:aws:ecs:us-west-2:123456789012:service/my-http-service'); expect(service.serviceName).toEqual('my-http-service'); + expect(service.env.account).toEqual('123456789012'); + expect(service.env.region).toEqual('us-west-2'); }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts index 966473e5cc6cb..9217e68a412c5 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -702,7 +702,7 @@ describe('ec2 task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); @@ -721,7 +721,7 @@ describe('ec2 task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index 1fef2d83e2784..fb1143317da2c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -95,15 +95,15 @@ "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VpcPublicSubnet2NATGateway9182C01D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet2EIP3C605A87", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, "Tags": [ { "Key": "Name", @@ -1070,6 +1070,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index 1bcb9d34f8dbd..662773111278c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -95,15 +95,15 @@ "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VpcPublicSubnet2NATGateway9182C01D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet2EIP3C605A87", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, "Tags": [ { "Key": "Name", @@ -1034,6 +1034,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "instance", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.expected.json index 6fe13e7abc27e..b14c1d7855e8c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.expected.json @@ -7,7 +7,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"user\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "TaskDefTaskRole1EDB4A67": { "Type": "AWS::IAM::Role", diff --git a/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts b/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts index 94384a464bb47..b4a3edf73ea0d 100644 --- a/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts @@ -6,6 +6,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { LaunchType } from '../../lib/base/base-service'; +import { addDefaultCapacityProvider } from '../util'; describe('external service', () => { describe('When creating an External Service', () => { @@ -14,7 +15,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { @@ -44,8 +45,6 @@ describe('external service', () => { }); expect(service.node.defaultChild).toBeDefined(); - - }); }); @@ -54,7 +53,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { @@ -104,7 +103,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); cluster.addDefaultCloudMapNamespace({ @@ -137,7 +136,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), @@ -234,7 +233,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -257,7 +256,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -283,7 +282,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -307,7 +306,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -345,7 +344,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -376,7 +375,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -403,7 +402,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -437,7 +436,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -465,7 +464,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -490,7 +489,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); const container = taskDefinition.addContainer('web', { diff --git a/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts index c4e296b2c6831..3b692f73fe8cf 100644 --- a/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts @@ -602,7 +602,7 @@ describe('external task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); @@ -634,4 +634,4 @@ describe('external task definition', () => { }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index ef4ceae4ccedc..a8dbb29c98b10 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -9,9 +9,11 @@ import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { DeploymentControllerType, LaunchType, PropagatedTagSource } from '../../lib/base/base-service'; +import { addDefaultCapacityProvider } from '../util'; describe('fargate service', () => { describe('When creating a Fargate Service', () => { @@ -115,7 +117,7 @@ describe('fargate service', () => { }); - test('does not set launchType when capacity provider strategies specified (deprecated)', () => { + testDeprecated('does not set launchType when capacity provider strategies specified (deprecated)', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -492,12 +494,12 @@ describe('fargate service', () => { type: ecs.DeploymentControllerType.ECS, }, circuitBreaker: { rollback: true }, - securityGroup: new ec2.SecurityGroup(stack, 'SecurityGroup1', { + securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', securityGroupName: 'Bob', vpc, - }), + })], serviceName: 'bonjour', vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); @@ -631,7 +633,7 @@ describe('fargate service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadataEntry[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).toHaveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -776,7 +778,7 @@ describe('fargate service', () => { }); - test('throws when securityGroup and securityGroups are supplied', () => { + testDeprecated('throws when securityGroup and securityGroups are supplied', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1912,7 +1914,7 @@ describe('fargate service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const container = taskDefinition.addContainer('MainContainer', { @@ -1973,7 +1975,7 @@ describe('fargate service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const container = taskDefinition.addContainer('MainContainer', { @@ -2124,6 +2126,8 @@ describe('fargate service', () => { expect(service.serviceArn).toEqual('arn:aws:ecs:us-west-2:123456789012:service/my-http-service'); expect(service.serviceName).toEqual('my-http-service'); + expect(service.env.account).toEqual('123456789012'); + expect(service.env.region).toEqual('us-west-2'); }); @@ -2319,7 +2323,7 @@ describe('fargate service', () => { logging: ecs.ExecuteCommandLogging.NONE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const logGroup = new logs.LogGroup(stack, 'LogGroup'); @@ -2388,7 +2392,7 @@ describe('fargate service', () => { logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2508,7 +2512,7 @@ describe('fargate service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2714,7 +2718,7 @@ describe('fargate service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2949,7 +2953,7 @@ describe('fargate service', () => { }); - test('with both propagateTags and propagateTaskTagsFrom defined', () => { + testDeprecated('with both propagateTags and propagateTaskTagsFrom defined', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json index b0e138464af2a..de47fd2260762 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json @@ -95,15 +95,15 @@ "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VpcPublicSubnet2NATGateway9182C01D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet2EIP3C605A87", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, "Tags": [ { "Key": "Name", @@ -649,6 +649,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.expected.json index 2ef36fdc4d94a..f86ae24841bbf 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.expected.json @@ -7,7 +7,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"user\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "TaskDefTaskRole1EDB4A67": { "Type": "AWS::IAM::Role", diff --git a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts index d9902cb9728e3..cb7678d9bc6e4 100644 --- a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts @@ -6,14 +6,14 @@ import * as ecs from '../lib'; let stack: cdk.Stack; let td: ecs.TaskDefinition; +let secret: secretsmanager.ISecret; const image = ecs.ContainerImage.fromRegistry('test-image'); describe('splunk log driver', () => { beforeEach(() => { stack = new cdk.Stack(); td = new ecs.Ec2TaskDefinition(stack, 'TaskDefinition'); - - + secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'my-splunk-token'); }); test('create a splunk log driver with minimum options', () => { @@ -21,7 +21,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: new ecs.SplunkLogDriver({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -34,9 +34,16 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -50,7 +57,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -63,9 +70,16 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -79,7 +93,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', sourceType: 'my-source-type', }), @@ -93,10 +107,17 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', 'splunk-sourcetype': 'my-source-type', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -105,13 +126,13 @@ describe('splunk log driver', () => { }); - test('create a splunk log driver using secret splunk token from secrets manager', () => { - const secret = new secretsmanager.Secret(stack, 'Secret'); + test('create a splunk log driver using secret splunk token from a new secret', () => { + const secret2 = new secretsmanager.Secret(stack, 'Secret2'); // WHEN td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - secretToken: ecs.Secret.fromSecretsManager(secret), + secretToken: ecs.Secret.fromSecretsManager(secret2), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -130,7 +151,7 @@ describe('splunk log driver', () => { { Name: 'splunk-token', ValueFrom: { - Ref: 'SecretA720EF05', + Ref: 'Secret244EA3BB5', }, }, ], diff --git a/packages/@aws-cdk/aws-ecs/test/util.ts b/packages/@aws-cdk/aws-ecs/test/util.ts new file mode 100644 index 0000000000000..cdfe38403422e --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/util.ts @@ -0,0 +1,21 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as ecs from '../lib'; + +export function addDefaultCapacityProvider(cluster: ecs.Cluster, + stack: cdk.Stack, + vpc: ec2.Vpc, + props?: Omit) { + const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), + instanceType: new ec2.InstanceType('t2.micro'), + }); + const provider = new ecs.AsgCapacityProvider(stack, 'AsgCapacityProvider', { + ...props, + autoScalingGroup, + }); + cluster.addAsgCapacityProvider(provider); + cluster.connections.addSecurityGroup(...autoScalingGroup.connections.securityGroups); +} diff --git a/packages/@aws-cdk/aws-efs/lib/access-point.ts b/packages/@aws-cdk/aws-efs/lib/access-point.ts index 6a0e6973cc6c2..07039df8d6a6e 100644 --- a/packages/@aws-cdk/aws-efs/lib/access-point.ts +++ b/packages/@aws-cdk/aws-efs/lib/access-point.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IFileSystem } from './efs-file-system'; import { CfnAccessPoint } from './efs.generated'; @@ -242,7 +242,7 @@ class ImportedAccessPoint extends AccessPointBase { } this.accessPointArn = attrs.accessPointArn; - let maybeApId = Stack.of(scope).parseArn(attrs.accessPointArn).resourceName; + let maybeApId = Stack.of(scope).splitArn(attrs.accessPointArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!maybeApId) { throw new Error('ARN for AccessPoint must provide the resource name.'); 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 aac4636ed9fa9..e5f92e6e7bca0 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,7 +1,7 @@ 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 { ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core'; +import { ArnFormat, ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports import { FeatureFlags } from '@aws-cdk/core'; @@ -409,7 +409,7 @@ class ImportedFileSystem extends FileSystemBase { resourceName: attrs.fileSystemId, }); - const parsedArn = Stack.of(scope).parseArn(this.fileSystemArn); + const parsedArn = Stack.of(scope).splitArn(this.fileSystemArn, ArnFormat.SLASH_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error(`Invalid FileSystem Arn ${this.fileSystemArn}`); diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index e97a3d99e8fd6..d7ab7e62a5dcb 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks-legacy/README.md b/packages/@aws-cdk/aws-eks-legacy/README.md index 35db7f23efb1e..2260236537399 100644 --- a/packages/@aws-cdk/aws-eks-legacy/README.md +++ b/packages/@aws-cdk/aws-eks-legacy/README.md @@ -42,10 +42,10 @@ cluster.addResource('mypod', { { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', - ports: [ { containerPort: 8080 } ] - } - ] - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, }); ``` @@ -65,7 +65,7 @@ the `defaultCapacity` and `defaultCapacityInstance` props: ```ts new eks.Cluster(this, 'cluster', { defaultCapacity: 10, - defaultCapacityInstance: new ec2.InstanceType('m2.xlarge') + defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), }); ``` @@ -82,7 +82,7 @@ is set to `0`: ```ts const cluster = new eks.Cluster(this, 'my-cluster'); cluster.defaultCapacity!.scaleOnCpuUtilization('up', { - targetUtilizationPercent: 80 + targetUtilizationPercent: 80, }); ``` @@ -90,10 +90,11 @@ You can add customized capacity through `cluster.addCapacity()` or `cluster.addAutoScalingGroup()`: ```ts +declare const cluster: eks.Cluster; cluster.addCapacity('frontend-nodes', { instanceType: new ec2.InstanceType('t2.medium'), desiredCapacity: 3, - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); ``` @@ -102,10 +103,11 @@ cluster.addCapacity('frontend-nodes', { If `spotPrice` is specified, the capacity will be purchased from spot instances: ```ts +declare const cluster: eks.Cluster; cluster.addCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), - maxCapacity: 10 + maxCapacity: 10, }); ``` @@ -126,13 +128,14 @@ you can use `kubeletExtraArgs` to add custom node labels or taints. ```ts // up to ten spot instances +declare const cluster: eks.Cluster; cluster.addCapacity('spot', { instanceType: new ec2.InstanceType('t3.large'), desiredCapacity: 2, bootstrapOptions: { kubeletExtraArgs: '--node-labels foo=bar,goo=far', - awsApiRetryAttempts: 5 - } + awsApiRetryAttempts: 5, + }, }); ``` @@ -154,12 +157,12 @@ role to the Kubernetes `system:masters` group: ```ts // first define the role const clusterAdmin = new iam.Role(this, 'AdminRole', { - assumedBy: new iam.AccountRootPrincipal() + assumedBy: new iam.AccountRootPrincipal(), }); // now define the cluster and map role to "masters" RBAC group new eks.Cluster(this, 'Cluster', { - mastersRole: clusterAdmin + mastersRole: clusterAdmin, }); ``` @@ -247,12 +250,12 @@ const deployment = { { name: "hello-kubernetes", image: "paulbouwer/hello-kubernetes:1.5", - ports: [ { containerPort: 8080 } ] - } - ] - } - } - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, + }, + }, }; const service = { @@ -262,14 +265,15 @@ const service = { spec: { type: "LoadBalancer", ports: [ { port: 80, targetPort: 8080 } ], - selector: appLabel - } + selector: appLabel, + }, }; +declare const cluster: eks.Cluster; // option 1: use a construct -new KubernetesResource(this, 'hello-kub', { +new eks.KubernetesResource(this, 'hello-kub', { cluster, - manifest: [ deployment, service ] + manifest: [ deployment, service ], }); // or, option2: use `addResource` @@ -301,6 +305,7 @@ For example, let's say you want to grant an IAM user administrative privileges on your cluster: ```ts +declare const cluster: eks.Cluster; const adminUser = new iam.User(this, 'Admin'); cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); ``` @@ -308,7 +313,9 @@ cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); A convenience method for mapping a role to the `system:masters` group is also available: ```ts -cluster.awsAuth.addMastersRole(role) +declare const cluster: eks.Cluster; +declare const role: iam.Role; +cluster.awsAuth.addMastersRole(role); ``` ### Node ssh Access @@ -369,7 +376,7 @@ the cluster: ```ts new eks.Cluster(this, 'cluster', { - kubectlEnabled: false + kubectlEnabled: false, }); ``` @@ -395,19 +402,20 @@ The following example will install the [NGINX Ingress Controller](https://kubern to you cluster using Helm. ```ts +declare const cluster: eks.Cluster; // option 1: use a construct -new HelmChart(this, 'NginxIngress', { +new eks.HelmChart(this, 'NginxIngress', { cluster, chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); // or, option2: use `addChart` cluster.addChart('NginxIngress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); ``` diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index 1f8c699180aad..8c364f7268b3a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -109,8 +109,8 @@ export interface ClusterProps { * For example, to only select private subnets, supply the following: * * ```ts - * vpcSubnets: [ - * { subnetType: ec2.SubnetType.Private } + * const vpcSubnets = [ + * { subnetType: ec2.SubnetType.PRIVATE } * ] * ``` * @@ -261,7 +261,7 @@ export class Cluster extends Resource implements ICluster { /** * The AWS generated ARN for the Cluster resource * - * @example arn:aws:eks:us-west-2:666666666666:cluster/prod + * For example, `arn:aws:eks:us-west-2:666666666666:cluster/prod` */ public readonly clusterArn: string; @@ -270,7 +270,7 @@ export class Cluster extends Resource implements ICluster { * * This is the URL inside the kubeconfig file to use with kubectl * - * @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com + * For example, `https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com` */ public readonly clusterEndpoint: string; @@ -726,7 +726,7 @@ export interface BootstrapOptions { /** * Extra arguments to add to the kubelet. Useful for adding labels or taints. * - * @example --node-labels foo=bar,goo=far + * For example, `--node-labels foo=bar,goo=far` * @default - none */ readonly kubeletExtraArgs?: string; diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts index f28901d125cc9..1760c2cf5bb92 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts @@ -22,9 +22,8 @@ export interface KubernetesResourceProps { * cluster through `kubectl apply` and when the resource or the stack is * deleted, the manifest will be deleted through `kubectl delete`. * - * @example - * - * { + * ``` + * const manifest = { * apiVersion: 'v1', * kind: 'Pod', * metadata: { name: 'mypod' }, @@ -32,7 +31,7 @@ export interface KubernetesResourceProps { * containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ] } ] * } * } - * + * ``` */ readonly manifest: any[]; } diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index 66251dac2e6ee..919122fed89d2 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -75,8 +82,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-autoscaling": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..9cac2b0852e12 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as eks from '@aws-cdk/aws-eks-legacy'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts index d9eacb3f183d1..3a5f28f648441 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as iam from '@aws-cdk/aws-iam'; import { Cluster, KubernetesResource } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; @@ -6,7 +7,7 @@ import { testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('awsauth', () => { +describeDeprecated('awsauth', () => { test('empty aws-auth', () => { // GIVEN const { stack } = testFixtureNoVpc(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts index f395348cc6f1e..b7f1666fb79b0 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; import { spotInterruptHandler } from '../lib/spot-interrupt-handler'; @@ -8,7 +9,7 @@ import { testFixture, testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('cluster', () => { +describeDeprecated('cluster', () => { test('a default cluster spans all subnets', () => { // GIVEN const { stack, vpc } = testFixture(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts index 4101ce02168d9..9606f892d5f1c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts @@ -1,10 +1,11 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; /* eslint-disable max-len */ -describe('helm chart', () => { +describeDeprecated('helm chart', () => { describe('add Helm chart', () => { test('should have default namespace', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts index 962e0c0129821..9a3de8f587f4c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts @@ -1,10 +1,11 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('manifest', () => { +describeDeprecated('manifest', () => { test('basic usage', () => { // GIVEN const { stack } = testFixtureNoVpc(); @@ -73,4 +74,4 @@ describe('manifest', () => { }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts index 4189e720d2f97..748acf2b4198e 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts @@ -1,11 +1,12 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import { renderUserData } from '../lib/user-data'; /* eslint-disable max-len */ -describe('user data', () => { +describeDeprecated('user data', () => { test('default user data', () => { // GIVEN const { asg, stack } = newFixtures(); diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 3b04f5d9e7f61..ee082134b51f0 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -62,10 +62,10 @@ cluster.addManifest('mypod', { { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', - ports: [ { containerPort: 8080 } ] - } - ] - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, }); ``` @@ -195,23 +195,22 @@ cluster.addNodegroupCapacity('custom-node-group', { minSize: 4, diskSize: 100, amiType: eks.NodegroupAmiType.AL2_X86_64_GPU, - ... }); ``` To set node taints, you can set `taints` option. ```ts +declare const cluster: eks.Cluster; cluster.addNodegroupCapacity('custom-node-group', { instanceTypes: [new ec2.InstanceType('m5.large')], taints: [ { - effect: TaintEffect.NO_SCHEDULE, + effect: eks.TaintEffect.NO_SCHEDULE, key: 'foo', value: 'bar', - } - ] - ... + }, + ], }); ``` @@ -224,6 +223,7 @@ Spot Instances, we recommend that you configure a Spot managed node group to use ```ts +declare const cluster: eks.Cluster; cluster.addNodegroupCapacity('extra-ng-spot', { instanceTypes: [ new ec2.InstanceType('c5.large'), @@ -245,6 +245,8 @@ When supplying a custom user data script, it must be encoded in the MIME multi-p for mode details. ```ts +declare const cluster: eks.Cluster; + const userData = `MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="==MYBOUNDARY==" @@ -262,6 +264,7 @@ const lt = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { userData: Fn.base64(userData), }, }); + cluster.addNodegroupCapacity('extra-ng', { launchTemplateSpec: { id: lt.ref, @@ -275,6 +278,7 @@ Note that when using a custom AMI, Amazon EKS doesn't merge any user data. Which In the following example, `/ect/eks/bootstrap.sh` from the AMI will be used to bootstrap the node. ```ts +declare const cluster: eks.Cluster; const userData = ec2.UserData.forLinux(); userData.addCommands( 'set -o xtrace', @@ -319,17 +323,19 @@ through the `addFargateProfile()` method. The following example adds a profile that will match all pods from the "default" namespace: ```ts +declare const cluster: eks.Cluster; cluster.addFargateProfile('MyProfile', { - selectors: [ { namespace: 'default' } ] + selectors: [ { namespace: 'default' } ], }); ``` You can also directly use the `FargateProfile` construct to create profiles under different scopes: ```ts -new eks.FargateProfile(scope, 'MyProfile', { +declare const cluster: eks.Cluster; +new eks.FargateProfile(this, 'MyProfile', { cluster, - ... + selectors: [ { namespace: 'default' } ], }); ``` @@ -342,6 +348,8 @@ const cluster = new eks.FargateCluster(this, 'MyCluster', { }); ``` +`FargateCluster` will create a default `FargateProfile` which can be accessed via the cluster's `defaultProfile` property. The created profile can also be customized by passing options as with `addFargateProfile`. + **NOTE**: Classic Load Balancers and Network Load Balancers are not supported on pods running on Fargate. For ingress, we recommend that you use the [ALB Ingress Controller](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) @@ -358,30 +366,33 @@ For a detailed overview please visit [Self Managed Nodes](https://docs.aws.amazo Creating an auto-scaling group and connecting it to the cluster is done using the `cluster.addAutoScalingGroupCapacity` method: ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('frontend-nodes', { instanceType: new ec2.InstanceType('t2.medium'), minCapacity: 3, - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); ``` To connect an already initialized auto-scaling group, use the `cluster.connectAutoScalingGroupCapacity()` method: ```ts -const asg = new ec2.AutoScalingGroup(...); -cluster.connectAutoScalingGroupCapacity(asg); +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +cluster.connectAutoScalingGroupCapacity(asg, {}); ``` To connect a self-managed node group to an imported cluster, use the `cluster.connectAutoScalingGroupCapacity()` method: ```ts -const importedCluster = eks.Cluster.fromClusterAttributes(stack, 'ImportedCluster', { +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +const importedCluster = eks.Cluster.fromClusterAttributes(this, 'ImportedCluster', { clusterName: cluster.clusterName, clusterSecurityGroupId: cluster.clusterSecurityGroupId, }); -const asg = new ec2.AutoScalingGroup(...); -importedCluster.connectAutoScalingGroupCapacity(asg); +importedCluster.connectAutoScalingGroupCapacity(asg, {}); ``` In both cases, the [cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html#cluster-sg) will be automatically attached to @@ -394,13 +405,14 @@ You can customize the [/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-e for bootstrapping the node to the EKS cluster. For example, you can use `kubeletExtraArgs` to add custom node labels or taints. ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('spot', { instanceType: new ec2.InstanceType('t3.large'), minCapacity: 2, bootstrapOptions: { kubeletExtraArgs: '--node-labels foo=bar,goo=far', - awsApiRetryAttempts: 5 - } + awsApiRetryAttempts: 5, + }, }); ``` @@ -408,7 +420,7 @@ To disable bootstrapping altogether (i.e. to fully customize user-data), set `bo You can also configure the cluster to use an auto-scaling group as the default capacity: ```ts -cluster = new eks.Cluster(this, 'HelloEKS', { +const cluster = new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, defaultCapacityType: eks.DefaultCapacityType.EC2, }); @@ -419,8 +431,9 @@ To access the `AutoScalingGroup` that was created on your behalf, you can use `c You can also independently create an `AutoScalingGroup` and connect it to the cluster using the `cluster.connectAutoScalingGroupCapacity` method: ```ts -const asg = new ec2.AutoScalingGroup(...) -cluster.connectAutoScalingGroupCapacity(asg); +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +cluster.connectAutoScalingGroupCapacity(asg, {}); ``` This will add the necessary user-data to access the apiserver and configure all connections, roles, and tags needed for the instances in the auto-scaling group to properly join the cluster. @@ -431,10 +444,11 @@ When using self-managed nodes, you can configure the capacity to use spot instan To enable spot capacity, use the `spotPrice` property: ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), - maxCapacity: 10 + maxCapacity: 10, }); ``` @@ -456,17 +470,26 @@ To disable the installation of the termination handler, set the `spotInterruptHa #### Bottlerocket [Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open-source operating system that is purpose-built by Amazon Web Services for running containers on virtual machines or bare metal hosts. -At this moment, `Bottlerocket` is only supported when using self-managed auto-scaling groups. -> **NOTICE**: Bottlerocket is only available in [some supported AWS regions](https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-EKS.md#finding-an-ami). +`Bottlerocket` is supported when using managed nodegroups or self-managed auto-scaling groups. + +To create a Bottlerocket managed nodegroup: + +```ts +declare const cluster: eks.Cluster; +cluster.addNodegroupCapacity('BottlerocketNG', { + amiType: eks.NodegroupAmiType.BOTTLEROCKET_X86_64, +}); +``` The following example will create an auto-scaling group of 2 `t3.small` Linux instances running with the `Bottlerocket` AMI. ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('BottlerocketNodes', { instanceType: new ec2.InstanceType('t3.small'), minCapacity: 2, - machineImageType: eks.MachineImageType.BOTTLEROCKET + machineImageType: eks.MachineImageType.BOTTLEROCKET, }); ``` @@ -478,6 +501,8 @@ For example, if the Amazon EKS cluster version is `1.17`, the Bottlerocket AMI v Please note Bottlerocket does not allow to customize bootstrap options and `bootstrapOptions` properties is not supported when you create the `Bottlerocket` capacity. +For more details about Bottlerocket, see [Bottlerocket FAQs](https://aws.amazon.com/bottlerocket/faqs/) and [Bottlerocket Open Source Blog](https://aws.amazon.com/blogs/opensource/announcing-the-general-availability-of-bottlerocket-an-open-source-linux-distribution-purpose-built-to-run-containers/). + ### Endpoint Access When you create a new cluster, Amazon EKS creates an endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using Kubernetes management tools such as `kubectl`) @@ -490,7 +515,7 @@ You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/ ```ts const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, - endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC. + endpointAccess: eks.EndpointAccess.PRIVATE, // No access outside of your VPC. }); ``` @@ -501,12 +526,12 @@ The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cl You can specify the VPC of the cluster using the `vpc` and `vpcSubnets` properties: ```ts -const vpc = new ec2.Vpc(this, 'Vpc'); +declare const vpc: ec2.Vpc; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }] + vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }], }); ``` @@ -514,12 +539,17 @@ new eks.Cluster(this, 'HelloEKS', { If you do not specify a VPC, one will be created on your behalf, which you can then access via `cluster.vpc`. The cluster VPC will be associated to any EKS managed capacity (i.e Managed Node Groups and Fargate Profiles). +Please note that the `vpcSubnets` property defines the subnets where EKS will place the _control plane_ ENIs. To choose +the subnets where EKS will place the worker nodes, please refer to the **Provisioning clusters** section above. + If you allocate self managed capacity, you can specify which subnets should the auto-scaling group use: ```ts -const vpc = new ec2.Vpc(this, 'Vpc'); +declare const vpc: ec2.Vpc; +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('nodes', { - vpcSubnets: { subnets: vpc.privateSubnets } + vpcSubnets: { subnets: vpc.privateSubnets }, + instanceType: new ec2.InstanceType('t2.medium'), }); ``` @@ -535,18 +565,26 @@ Breaking this down, it means that if the endpoint exposes private access (via `E If the endpoint does not expose private access (via `EndpointAccess.PUBLIC`) **or** the VPC does not contain private subnets, the function will not be provisioned within the VPC. +If your use-case requires control over the IAM role that the KubeCtl Handler assumes, a custom role can be passed through the ClusterProps (as `kubectlLambdaRole`) of the EKS Cluster construct. + #### Cluster Handler -The `ClusterHandler` is a Lambda function responsible to interact with the EKS API in order to control the cluster lifecycle. To provision this function inside the VPC, set the `placeClusterHandlerInVpc` property to `true`. This will place the function inside the private subnets of the VPC based on the selection strategy specified in the [`vpcSubnets`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-eks.Cluster.html#vpcsubnetsspan-classapi-icon-api-icon-experimental-titlethis-api-element-is-experimental-it-may-change-without-noticespan) property. +The `ClusterHandler` is a set of Lambda functions (`onEventHandler`, `isCompleteHandler`) responsible for interacting with the EKS API in order to control the cluster lifecycle. To provision these functions inside the VPC, set the `placeClusterHandlerInVpc` property to `true`. This will place the functions inside the private subnets of the VPC based on the selection strategy specified in the [`vpcSubnets`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-eks.Cluster.html#vpcsubnetsspan-classapi-icon-api-icon-experimental-titlethis-api-element-is-experimental-it-may-change-without-noticespan) property. -You can configure the environment of this function by specifying it at cluster instantiation. For example, this can be useful in order to configure an http proxy: +You can configure the environment of the Cluster Handler functions by specifying it at cluster instantiation. For example, this can be useful in order to configure an http proxy: ```ts +declare const proxyInstanceSecurityGroup: ec2.SecurityGroup; const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, clusterHandlerEnvironment: { - 'http_proxy': 'http://proxy.myproxy.com' - } + https_proxy: 'http://proxy.myproxy.com', + }, + /** + * If the proxy is not open publicly, you can pass a security group to the + * Cluster Handler Lambdas so that it can reach the proxy. + */ + clusterHandlerSecurityGroup: proxyInstanceSecurityGroup, }); ``` @@ -562,8 +600,8 @@ You can configure the environment of this function by specifying it at cluster i const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, kubectlEnvironment: { - 'http_proxy': 'http://proxy.myproxy.com' - } + 'http_proxy': 'http://proxy.myproxy.com', + }, }); ``` @@ -597,13 +635,21 @@ const layer = new lambda.LayerVersion(this, 'KubectlLayer', { Now specify when the cluster is defined: ```ts -const cluster = new eks.Cluster(this, 'MyCluster', { +declare const layer: lambda.LayerVersion; +declare const vpc: ec2.Vpc; + +const cluster1 = new eks.Cluster(this, 'MyCluster', { kubectlLayer: layer, + vpc, + clusterName: 'cluster-name', + version: eks.KubernetesVersion.V1_21, }); // or -const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { +const cluster2 = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { kubectlLayer: layer, + vpc, + clusterName: 'cluster-name', }); ``` @@ -612,15 +658,17 @@ const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { By default, the kubectl provider is configured with 1024MiB of memory. You can use the `kubectlMemory` option to specify the memory size for the AWS Lambda function: ```ts -import { Size } from '@aws-cdk/core'; - new eks.Cluster(this, 'MyCluster', { - kubectlMemory: Size.gibibytes(4) + kubectlMemory: Size.gibibytes(4), + version: eks.KubernetesVersion.V1_21, }); // or +declare const vpc: ec2.Vpc; eks.Cluster.fromClusterAttributes(this, 'MyCluster', { - kubectlMemory: Size.gibibytes(4) + kubectlMemory: Size.gibibytes(4), + vpc, + clusterName: 'cluster-name', }); ``` @@ -630,6 +678,7 @@ Instance types with `ARM64` architecture are supported in both managed nodegroup Amazon Linux 2 AMI for ARM64 will be automatically selected. ```ts +declare const cluster: eks.Cluster; // add a managed ARM64 nodegroup cluster.addNodegroupCapacity('extra-ng-arm', { instanceTypes: [new ec2.InstanceType('m6g.medium')], @@ -648,7 +697,7 @@ cluster.addAutoScalingGroupCapacity('self-ng-arm', { When you create a cluster, you can specify a `mastersRole`. The `Cluster` construct will associate this role with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) group, giving it super-user access to the cluster. ```ts -const role = new iam.Role(...); +declare const role: iam.Role; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, mastersRole: role, @@ -678,7 +727,7 @@ You can use the `secretsEncryptionKey` to configure which key the cluster will u const secretsKey = new kms.Key(this, 'SecretsKey'); const cluster = new eks.Cluster(this, 'MyCluster', { secretsEncryptionKey: secretsKey, - // ... + version: eks.KubernetesVersion.V1_21, }); ``` @@ -687,13 +736,15 @@ You can also use a similar configuration for running a cluster built using the F ```ts const secretsKey = new kms.Key(this, 'SecretsKey'); const cluster = new eks.FargateCluster(this, 'MyFargateCluster', { - secretsEncryptionKey: secretsKey + secretsEncryptionKey: secretsKey, + version: eks.KubernetesVersion.V1_21, }); ``` The Amazon Resource Name (ARN) for that CMK can be retrieved. ```ts +declare const cluster: eks.Cluster; const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; ``` @@ -713,6 +764,7 @@ Furthermore, when auto-scaling group capacity is added to the cluster, the IAM i For example, let's say you want to grant an IAM user administrative privileges on your cluster: ```ts +declare const cluster: eks.Cluster; const adminUser = new iam.User(this, 'Admin'); cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); ``` @@ -720,7 +772,9 @@ cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); A convenience method for mapping a role to the `system:masters` group is also available: ```ts -cluster.awsAuth.addMastersRole(role) +declare const cluster: eks.Cluster; +declare const role: iam.Role; +cluster.awsAuth.addMastersRole(role); ``` ### Cluster Security Group @@ -732,6 +786,7 @@ between each other. The ID for that security group can be retrieved after creating the cluster. ```ts +declare const cluster: eks.Cluster; const clusterSecurityGroupId = cluster.clusterSecurityGroupId; ``` @@ -751,10 +806,11 @@ unfortunately beyond the scope of this documentation. With services account you can provide Kubernetes Pods access to AWS resources. ```ts +declare const cluster: eks.Cluster; // add service account const serviceAccount = cluster.addServiceAccount('MyServiceAccount'); -const bucket = new Bucket(this, 'Bucket'); +const bucket = new s3.Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); const mypod = cluster.addManifest('mypod', { @@ -762,23 +818,22 @@ const mypod = cluster.addManifest('mypod', { kind: 'Pod', metadata: { name: 'mypod' }, spec: { - serviceAccountName: serviceAccount.serviceAccountName + serviceAccountName: serviceAccount.serviceAccountName, containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ], - - } - ] - } + }, + ], + }, }); // create the resource after the service account. mypod.node.addDependency(serviceAccount); // print the IAM role arn for this service account -new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: serviceAccount.role.roleArn }) +new CfnOutput(this, 'ServiceAccountIamRole', { value: serviceAccount.role.roleArn }); ``` Note that using `serviceAccount.serviceAccountName` above **does not** translate into a resource dependency. @@ -792,9 +847,12 @@ To do so, pass the `openIdConnectProvider` property when you import the cluster const provider = eks.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, 'Provider', 'arn:aws:iam::123456:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/AB123456ABC'); // or create a new one using an existing issuer url -const provider = new eks.OpenIdConnectProvider(this, 'Provider', issuerUrl); +declare const issuerUrl: string; +const provider2 = new eks.OpenIdConnectProvider(this, 'Provider', { + url: issuerUrl, +}); -const cluster = eks.Cluster.fromClusterAttributes({ +const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { clusterName: 'Cluster', openIdConnectProvider: provider, kubectlRoleArn: 'arn:aws:iam::123456:role/service-role/k8sservicerole', @@ -802,10 +860,8 @@ const cluster = eks.Cluster.fromClusterAttributes({ const serviceAccount = cluster.addServiceAccount('MyServiceAccount'); -const bucket = new Bucket(this, 'Bucket'); +const bucket = new s3.Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); - -// ... ``` Note that adding service accounts requires running `kubectl` commands against the cluster. @@ -829,6 +885,7 @@ The following examples will deploy the [paulbouwer/hello-kubernetes](https://git service on the cluster: ```ts +declare const cluster: eks.Cluster; const appLabel = { app: "hello-kubernetes" }; const deployment = { @@ -845,12 +902,12 @@ const deployment = { { name: "hello-kubernetes", image: "paulbouwer/hello-kubernetes:1.5", - ports: [ { containerPort: 8080 } ] - } - ] - } - } - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, + }, + }, }; const service = { @@ -860,14 +917,14 @@ const service = { spec: { type: "LoadBalancer", ports: [ { port: 80, targetPort: 8080 } ], - selector: appLabel + selector: appLabel, } }; // option 1: use a construct -new KubernetesManifest(this, 'hello-kub', { +new eks.KubernetesManifest(this, 'hello-kub', { cluster, - manifest: [ deployment, service ] + manifest: [ deployment, service ], }); // or, option2: use `addManifest` @@ -878,13 +935,16 @@ cluster.addManifest('hello-kub', service, deployment); The following example will deploy the resource manifest hosting on remote server: -```ts +```text +// This example is only available in TypeScript + import * as yaml from 'js-yaml'; import * as request from 'sync-request'; +declare const cluster: eks.Cluster; const manifestUrl = 'https://url/of/manifest.yaml'; const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); -cluster.addManifest('my-resource', ...manifest); +cluster.addManifest('my-resource', manifest); ``` #### Dependencies @@ -897,18 +957,19 @@ You can represent dependencies between `KubernetesManifest`s using `resource.node.addDependency()`: ```ts +declare const cluster: eks.Cluster; const namespace = cluster.addManifest('my-namespace', { apiVersion: 'v1', kind: 'Namespace', - metadata: { name: 'my-app' } + metadata: { name: 'my-app' }, }); const service = cluster.addManifest('my-service', { metadata: { name: 'myservice', - namespace: 'my-app' + namespace: 'my-app', }, - spec: // ... + spec: { }, // ... }); service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. @@ -938,8 +999,9 @@ Pruning is enabled by default but can be disabled through the `prune` option when a cluster is defined: ```ts -new Cluster(this, 'MyCluster', { - prune: false +new eks.Cluster(this, 'MyCluster', { + version: eks.KubernetesVersion.V1_21, + prune: false, }); ``` @@ -949,9 +1011,10 @@ The `kubectl` CLI supports applying a manifest by skipping the validation. This can be accomplished by setting the `skipValidation` flag to `true` in the `KubernetesManifest` props. ```ts +declare const cluster: eks.Cluster; new eks.KubernetesManifest(this, 'HelloAppWithoutValidation', { - cluster: this.cluster, - manifest: [ deployment, service ], + cluster, + manifest: [{ foo: 'bar' }], skipValidation: true, }); ``` @@ -968,19 +1031,20 @@ to add Kubernetes resources to this cluster using Helm. The following example will install the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/) to your cluster using Helm. ```ts +declare const cluster: eks.Cluster; // option 1: use a construct -new HelmChart(this, 'NginxIngress', { +new eks.HelmChart(this, 'NginxIngress', { cluster, chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); // or, option2: use `addHelmChart` cluster.addHelmChart('NginxIngress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); ``` @@ -1004,8 +1068,13 @@ resource or if Helm charts depend on each other. You can use charts: ```ts -const chart1 = cluster.addHelmChart(...); -const chart2 = cluster.addHelmChart(...); +declare const cluster: eks.Cluster; +const chart1 = cluster.addHelmChart('MyChart', { + chart: 'foo', +}); +const chart2 = cluster.addHelmChart('MyChart', { + chart: 'bar', +}); chart2.node.addDependency(chart1); ``` @@ -1043,7 +1112,7 @@ For this reason, to avoid possible confusion, we will create the chart in a sepa `+ my-chart.ts` -```ts +```ts nofixture import * as s3 from '@aws-cdk/aws-s3'; import * as constructs from 'constructs'; import * as cdk8s from 'cdk8s'; @@ -1058,16 +1127,14 @@ export class MyChart extends cdk8s.Chart { 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), - }, - }), - ], - }, + containers: [ + new kplus.Container({ + image: 'my-image', + env: { + BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), + }, + }), + ], }); } } @@ -1075,10 +1142,8 @@ export class MyChart extends cdk8s.Chart { 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'; +```ts fixture=cdk8schart +declare const cluster: eks.Cluster; // some bucket.. const bucket = new s3.Bucket(this, 'Bucket'); @@ -1096,7 +1161,7 @@ You can also compose a few stock `cdk8s+` constructs into your own custom constr 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 +```ts nofixture import * as constructs from 'constructs'; import * as cdk8s from 'cdk8s'; import * as kplus from 'cdk8s-plus'; @@ -1107,21 +1172,21 @@ export interface LoadBalancedWebService { readonly replicas: number; } +const app = new cdk8s.App(); +const chart = new cdk8s.Chart(app, 'my-chart'); + 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 }) ] - } - }, + replicas: props.replicas, + containers: [ new kplus.Container({ image: props.image }) ], }); - deployment.expose({port: props.port, serviceType: kplus.ServiceType.LOAD_BALANCER}) - + deployment.expose(props.port, { + serviceType: kplus.ServiceType.LOAD_BALANCER, + }); } } ``` @@ -1139,11 +1204,12 @@ resources. The following example can be used to patch the `hello-kubernetes` deployment from the example above with 5 replicas. ```ts -new KubernetesPatch(this, 'hello-kub-deployment-label', { +declare const cluster: eks.Cluster; +new eks.KubernetesPatch(this, 'hello-kub-deployment-label', { cluster, resourceName: "deployment/hello-kubernetes", applyPatch: { spec: { replicas: 5 } }, - restorePatch: { spec: { replicas: 3 } } + restorePatch: { spec: { replicas: 3 } }, }) ``` @@ -1155,8 +1221,9 @@ and use that as part of your CDK application. For example, you can fetch the address of a [`LoadBalancer`](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) type service: ```ts +declare const cluster: eks.Cluster; // query the load balancer address -const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute', { +const myServiceAddress = new eks.KubernetesObjectValue(this, 'LoadBalancerAttribute', { cluster: cluster, objectType: 'service', objectName: 'my-service', @@ -1165,9 +1232,11 @@ const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute' // pass the address to a lambda function const proxyFunction = new lambda.Function(this, 'ProxyFunction', { - ... + handler: 'index.handler', + code: lambda.Code.fromInline('my-code'), + runtime: lambda.Runtime.NODEJS_14_X, environment: { - myServiceAddress: myServiceAddress.value + myServiceAddress: myServiceAddress.value, }, }) ``` @@ -1175,6 +1244,7 @@ const proxyFunction = new lambda.Function(this, 'ProxyFunction', { Specifically, since the above use-case is quite common, there is an easier way to access that information: ```ts +declare const cluster: eks.Cluster; const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('my-service'); ``` @@ -1198,6 +1268,7 @@ Then, you can use `addManifest` or `addHelmChart` to define resources inside your Kubernetes cluster. For example: ```ts +declare const cluster: eks.Cluster; cluster.addManifest('Test', { apiVersion: 'v1', kind: 'ConfigMap', diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts index 8f563de833bf6..21cf958df5a68 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts @@ -41,12 +41,6 @@ export abstract class ResourceHandler { } public onEvent() { - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - const ProxyAgent: any = require('proxy-agent'); - aws.config.update({ - httpOptions: { agent: new ProxyAgent() }, - }); - switch (this.requestType) { case 'Create': return this.onCreate(); case 'Update': return this.onUpdate(); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts index e7fc357846259..258f5d8b04545 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts @@ -8,7 +8,13 @@ import { EksClient } from './common'; import * as consts from './consts'; import { FargateProfileResourceHandler } from './fargate'; +// eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies +const ProxyAgent = require('proxy-agent'); + aws.config.logger = console; +aws.config.update({ + httpOptions: { agent: new ProxyAgent() }, +}); let eks: aws.EKS | undefined; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts index f425b0a1eaba6..9bb65be4f56b2 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts @@ -36,11 +36,18 @@ export interface ClusterResourceProviderProps { readonly environment?: { [key: string]: string }; /** - * An AWS Lambda layer that includes the NPM dependency `proxy-agent`. - * - * If not defined, a default layer will be used. - */ + * An AWS Lambda layer that includes the NPM dependency `proxy-agent`. + * + * If not defined, a default layer will be used. + */ readonly onEventLayer?: lambda.ILayerVersion; + + /** + * The security group to associate with the functions. + * + * @default - No security group. + */ + readonly securityGroup?: ec2.ISecurityGroup; } /** @@ -66,6 +73,9 @@ export class ClusterResourceProvider extends NestedStack { private constructor(scope: Construct, id: string, props: ClusterResourceProviderProps) { super(scope as CoreConstruct, id); + // The NPM dependency proxy-agent is required in order to support proxy routing with the AWS JS SDK. + const nodeProxyAgentLayer = new NodeProxyAgentLayer(this, 'NodeProxyAgentLayer'); + const onEvent = new lambda.Function(this, 'OnEventHandler', { code: lambda.Code.fromAsset(HANDLER_DIR), description: 'onEvent handler for EKS cluster resource provider', @@ -75,24 +85,22 @@ export class ClusterResourceProvider extends NestedStack { timeout: Duration.minutes(1), vpc: props.subnets ? props.vpc : undefined, vpcSubnets: props.subnets ? { subnets: props.subnets } : undefined, + securityGroups: props.securityGroup ? [props.securityGroup] : undefined, + // Allow user to override the layer. + layers: props.onEventLayer ? [props.onEventLayer] : [nodeProxyAgentLayer], }); - // Allow user to customize the layer - if (!props.onEventLayer) { - // `NodeProxyAgentLayer` provides `proxy-agent` which is needed to configure `aws-sdk-js` with a user provided proxy. - onEvent.addLayers(new NodeProxyAgentLayer(this, 'NodeProxyAgentLayer')); - } else { - onEvent.addLayers(props.onEventLayer); - } - const isComplete = new lambda.Function(this, 'IsCompleteHandler', { code: lambda.Code.fromAsset(HANDLER_DIR), description: 'isComplete handler for EKS cluster resource provider', runtime: HANDLER_RUNTIME, + environment: props.environment, handler: 'index.isComplete', timeout: Duration.minutes(1), vpc: props.subnets ? props.vpc : undefined, vpcSubnets: props.subnets ? { subnets: props.subnets } : undefined, + securityGroups: props.securityGroup ? [props.securityGroup] : undefined, + layers: [nodeProxyAgentLayer], }); this.provider = new cr.Provider(this, 'Provider', { @@ -102,6 +110,7 @@ export class ClusterResourceProvider extends NestedStack { queryInterval: Duration.minutes(1), vpc: props.subnets ? props.vpc : undefined, vpcSubnets: props.subnets ? { subnets: props.subnets } : undefined, + securityGroups: props.securityGroup ? [props.securityGroup] : undefined, }); props.adminRole.grant(onEvent.role!, 'sts:AssumeRole'); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index 662f4e345a24c..ed0852338a527 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -16,6 +16,7 @@ export interface ClusterResourceProps { readonly resourcesVpcConfig: CfnCluster.ResourcesVpcConfigProperty; readonly roleArn: string; readonly encryptionConfig?: Array; + readonly kubernetesNetworkConfig?: CfnCluster.KubernetesNetworkConfigProperty; readonly name: string; readonly version?: string; readonly endpointPrivateAccess: boolean; @@ -26,6 +27,7 @@ export interface ClusterResourceProps { readonly subnets?: ec2.ISubnet[]; readonly secretsEncryptionKey?: kms.IKey; readonly onEventLayer?: lambda.ILayerVersion; + readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup; } /** @@ -65,6 +67,7 @@ export class ClusterResource extends CoreConstruct { vpc: props.vpc, environment: props.environment, onEventLayer: props.onEventLayer, + securityGroup: props.clusterHandlerSecurityGroup, }); const resource = new CustomResource(this, 'Resource', { @@ -78,6 +81,7 @@ export class ClusterResource extends CoreConstruct { version: props.version, roleArn: props.roleArn, encryptionConfig: props.encryptionConfig, + kubernetesNetworkConfig: props.kubernetesNetworkConfig, resourcesVpcConfig: { subnetIds: (props.resourcesVpcConfig as CfnCluster.ResourcesVpcConfigProperty).subnetIds, securityGroupIds: (props.resourcesVpcConfig as CfnCluster.ResourcesVpcConfigProperty).securityGroupIds, diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 2db0438537f97..7aa18e7aa5fce 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -117,6 +117,15 @@ export interface ICluster extends IResource, ec2.IConnectable { */ readonly kubectlPrivateSubnets?: ec2.ISubnet[]; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + */ + readonly kubectlLambdaRole?: iam.IRole; + /** * An AWS Lambda layer that includes `kubectl`, `helm` and the `aws` CLI. * @@ -130,10 +139,21 @@ export interface ICluster extends IResource, ec2.IConnectable { readonly kubectlMemory?: Size; /** - * An AWS Lambda layer that includes the NPM dependency `proxy-agent`. - * - * If not defined, a default layer will be used. - */ + * A security group to associate with the Cluster Handler's Lambdas. + * The Cluster Handler's Lambdas are responsible for calling AWS's EKS API. + * + * Requires `placeClusterHandlerInVpc` to be set to true. + * + * @default - No security group. + * @attribute + */ + readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup; + + /** + * An AWS Lambda layer that includes the NPM dependency `proxy-agent`. + * + * If not defined, a default layer will be used. + */ readonly onEventLayer?: lambda.ILayerVersion; /** @@ -260,6 +280,18 @@ export interface ClusterAttributes { */ readonly kubectlRoleArn?: string; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands + * to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + readonly kubectlLambdaRole?: iam.IRole; + /** * Environment variables to use when running `kubectl` against this cluster. * @default - no additional variables @@ -309,6 +341,14 @@ export interface ClusterAttributes { */ readonly kubectlMemory?: Size; + /** + * A security group id to associate with the Cluster Handler's Lambdas. + * The Cluster Handler's Lambdas are responsible for calling AWS's EKS API. + * + * @default - No security group. + */ + readonly clusterHandlerSecurityGroupId?: string; + /** * An AWS Lambda Layer which includes the NPM dependency `proxy-agent`. This layer * is used by the onEvent handler to route AWS SDK requests through a proxy. @@ -350,11 +390,7 @@ export interface CommonClusterOptions { * * For example, to only select private subnets, supply the following: * - * ```ts - * vpcSubnets: [ - * { subnetType: ec2.SubnetType.Private } - * ] - * ``` + * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }]` * * @default - All public and private subnets */ @@ -452,13 +488,6 @@ export interface ClusterOptions extends CommonClusterOptions { */ readonly kubectlEnvironment?: { [key: string]: string }; - /** - * Custom environment variables when interacting with the EKS endpoint to manage the cluster lifecycle. - * - * @default - No environment variables. - */ - readonly clusterHandlerEnvironment?: { [key: string]: string }; - /** * An AWS Lambda Layer which includes `kubectl`, Helm and the AWS CLI. * @@ -473,9 +502,9 @@ export interface ClusterOptions extends CommonClusterOptions { * * ```ts * const layer = new lambda.LayerVersion(this, 'kubectl-layer', { - * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`)), - * compatibleRuntimes: [lambda.Runtime.PROVIDED] - * }) + * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`), + * compatibleRuntimes: [lambda.Runtime.PROVIDED], + * }); * ``` * * @default - the layer provided by the `aws-lambda-layer-kubectl` SAR app. @@ -491,26 +520,40 @@ export interface ClusterOptions extends CommonClusterOptions { readonly kubectlMemory?: Size; /** - * An AWS Lambda Layer which includes the NPM dependency `proxy-agent`. + * Custom environment variables when interacting with the EKS endpoint to manage the cluster lifecycle. + * + * @default - No environment variables. + */ + readonly clusterHandlerEnvironment?: { [key: string]: string }; + + /** + * A security group to associate with the Cluster Handler's Lambdas. + * The Cluster Handler's Lambdas are responsible for calling AWS's EKS API. + * + * Requires `placeClusterHandlerInVpc` to be set to true. + * + * @default - No security group. + */ + readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup; + + /** + * An AWS Lambda Layer which includes the NPM dependency `proxy-agent`. This layer + * is used by the onEvent handler to route AWS SDK requests through a proxy. * * By default, the provider will use the layer included in the * "aws-lambda-layer-node-proxy-agent" SAR application which is available in all * commercial regions. * - * To deploy the layer locally, visit - * https://github.com/aws-samples/aws-lambda-layer-node-proxy-agent/blob/master/cdk/README.md - * for instructions on how to prepare the .zip file and then define it in your - * app as follows: + * To deploy the layer locally define it in your app as follows: * * ```ts - * const layer = new lambda.LayerVersion(this, 'node-proxy-agent-layer', { - * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`)), - * compatibleRuntimes: [lambda.Runtime.NODEJS_14_X] - * }) + * const layer = new lambda.LayerVersion(this, 'proxy-agent-layer', { + * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`), + * compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], + * }); * ``` * - * @default - the layer provided by the `aws-lambda-layer-node-proxy-agent` SAR app. - * @see https://github.com/aws-samples/aws-lambda-layer-node-proxy-agent + * @default - a layer bundled with this module. */ readonly onEventLayer?: lambda.ILayerVersion; @@ -540,6 +583,15 @@ export interface ClusterOptions extends CommonClusterOptions { * using AWS-Managed encryption keys. */ readonly secretsEncryptionKey?: kms.IKey; + + /** + * The CIDR block to assign Kubernetes service IP addresses from. + * + * @default - Kubernetes assigns addresses from either the + * 10.100.0.0/16 or 172.20.0.0/16 CIDR blocks + * @see https://docs.aws.amazon.com/eks/latest/APIReference/API_KubernetesNetworkConfigRequest.html#AmazonEKS-Type-KubernetesNetworkConfigRequest-serviceIpv4Cidr + */ + readonly serviceIpv4Cidr?: string; } /** @@ -667,6 +719,14 @@ export interface ClusterProps extends ClusterOptions { * @default NODEGROUP */ readonly defaultCapacityType?: DefaultCapacityType; + + + /** + * The IAM role to pass to the Kubectl Lambda Handler. + * + * @default - Default Lambda IAM Execution Role + */ + readonly kubectlLambdaRole?: iam.IRole; } /** @@ -736,10 +796,12 @@ abstract class ClusterBase extends Resource implements ICluster { public abstract readonly clusterSecurityGroup: ec2.ISecurityGroup; public abstract readonly clusterEncryptionConfigKeyArn: string; public abstract readonly kubectlRole?: iam.IRole; + public abstract readonly kubectlLambdaRole?: iam.IRole; public abstract readonly kubectlEnvironment?: { [key: string]: string }; public abstract readonly kubectlSecurityGroup?: ec2.ISecurityGroup; public abstract readonly kubectlPrivateSubnets?: ec2.ISubnet[]; public abstract readonly kubectlMemory?: Size; + public abstract readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup; public abstract readonly prune: boolean; public abstract readonly openIdConnectProvider: iam.IOpenIdConnectProvider; public abstract readonly awsAuth: AwsAuth; @@ -893,7 +955,7 @@ abstract class ClusterBase extends Resource implements ICluster { // cluster or if `mapRole` is set to false. By default this should happen. let mapRole = options.mapRole ?? true; if (mapRole && !(this instanceof Cluster)) { - // do the mapping... + // do the mapping... Annotations.of(autoScalingGroup).addWarning('Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); mapRole = false; } @@ -974,7 +1036,7 @@ export class Cluster extends ClusterBase { /** * The AWS generated ARN for the Cluster resource * - * @example arn:aws:eks:us-west-2:666666666666:cluster/prod + * For example, `arn:aws:eks:us-west-2:666666666666:cluster/prod` */ public readonly clusterArn: string; @@ -983,7 +1045,7 @@ export class Cluster extends ClusterBase { * * This is the URL inside the kubeconfig file to use with kubectl * - * @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com + * For example, `https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com` */ public readonly clusterEndpoint: string; @@ -1041,6 +1103,18 @@ export class Cluster extends ClusterBase { */ public readonly kubectlRole?: iam.IRole; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + + public readonly kubectlLambdaRole?: iam.IRole; + /** * Custom environment variables when running `kubectl` against this cluster. */ @@ -1090,11 +1164,21 @@ export class Cluster extends ClusterBase { */ public readonly kubectlMemory?: Size; + /** + * A security group to associate with the Cluster Handler's Lambdas. + * The Cluster Handler's Lambdas are responsible for calling AWS's EKS API. + * + * Requires `placeClusterHandlerInVpc` to be set to true. + * + * @default - No security group. + */ + public readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup; + /** * The AWS Lambda layer that contains the NPM dependency `proxy-agent`. If * undefined, a SAR app that contains this layer will be used. */ - public readonly onEventLayer?: lambda.ILayerVersion; + readonly onEventLayer?: lambda.ILayerVersion; /** * Determines if Kubernetes resources can be pruned automatically. @@ -1149,6 +1233,7 @@ export class Cluster extends ClusterBase { this.prune = props.prune ?? true; this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc'); this.version = props.version; + this.kubectlLambdaRole = props.kubectlLambdaRole ? props.kubectlLambdaRole : undefined; this.tagSubnets(); @@ -1179,10 +1264,12 @@ export class Cluster extends ClusterBase { this.endpointAccess = props.endpointAccess ?? EndpointAccess.PUBLIC_AND_PRIVATE; this.kubectlEnvironment = props.kubectlEnvironment; this.kubectlLayer = props.kubectlLayer; - this.onEventLayer = props.onEventLayer; this.kubectlMemory = props.kubectlMemory; - const privateSubents = this.selectPrivateSubnets().slice(0, 16); + this.onEventLayer = props.onEventLayer; + this.clusterHandlerSecurityGroup = props.clusterHandlerSecurityGroup; + + const privateSubnets = this.selectPrivateSubnets().slice(0, 16); const publicAccessDisabled = !this.endpointAccess._config.publicAccess; const publicAccessRestricted = !publicAccessDisabled && this.endpointAccess._config.publicCidrs @@ -1190,22 +1277,26 @@ export class Cluster extends ClusterBase { // validate endpoint access configuration - if (privateSubents.length === 0 && publicAccessDisabled) { + if (privateSubnets.length === 0 && publicAccessDisabled) { // no private subnets and no public access at all, no good. throw new Error('Vpc must contain private subnets when public endpoint access is disabled'); } - if (privateSubents.length === 0 && publicAccessRestricted) { - // no private subents and public access is restricted, no good. + if (privateSubnets.length === 0 && publicAccessRestricted) { + // no private subnets and public access is restricted, no good. throw new Error('Vpc must contain private subnets when public endpoint access is restricted'); } const placeClusterHandlerInVpc = props.placeClusterHandlerInVpc ?? false; - if (placeClusterHandlerInVpc && privateSubents.length === 0) { + if (placeClusterHandlerInVpc && privateSubnets.length === 0) { throw new Error('Cannot place cluster handler in the VPC since no private subnets could be selected'); } + if (props.clusterHandlerSecurityGroup && !placeClusterHandlerInVpc) { + throw new Error('Cannot specify clusterHandlerSecurityGroup without placeClusterHandlerInVpc set to true'); + } + const resource = this._clusterResource = new ClusterResource(this, 'Resource', { name: this.physicalName, environment: props.clusterHandlerEnvironment, @@ -1223,16 +1314,20 @@ export class Cluster extends ClusterBase { resources: ['secrets'], }], } : {}), + kubernetesNetworkConfig: props.serviceIpv4Cidr ? { + serviceIpv4Cidr: props.serviceIpv4Cidr, + } : undefined, endpointPrivateAccess: this.endpointAccess._config.privateAccess, endpointPublicAccess: this.endpointAccess._config.publicAccess, publicAccessCidrs: this.endpointAccess._config.publicCidrs, secretsEncryptionKey: props.secretsEncryptionKey, vpc: this.vpc, - subnets: placeClusterHandlerInVpc ? privateSubents : undefined, + subnets: placeClusterHandlerInVpc ? privateSubnets : undefined, + clusterHandlerSecurityGroup: this.clusterHandlerSecurityGroup, onEventLayer: this.onEventLayer, }); - if (this.endpointAccess._config.privateAccess && privateSubents.length !== 0) { + if (this.endpointAccess._config.privateAccess && privateSubnets.length !== 0) { // when private access is enabled and the vpc has private subnets, lets connect // the provider to the vpc so that it will work even when restricting public access. @@ -1242,7 +1337,7 @@ export class Cluster extends ClusterBase { throw new Error('Private endpoint access requires the VPC to have DNS support and DNS hostnames enabled. Use `enableDnsHostnames: true` and `enableDnsSupport: true` when creating the VPC.'); } - this.kubectlPrivateSubnets = privateSubents; + this.kubectlPrivateSubnets = privateSubnets; // the vpc must exist in order to properly delete the cluster (since we run `kubectl delete`). // this ensures that. @@ -1385,8 +1480,6 @@ export class Cluster extends ClusterBase { cpuArch: cpuArchForInstanceType(options.instanceType), kubernetesVersion: this.version.version, }), - updateType: options.updateType, - instanceType: options.instanceType, }); this.connectAutoScalingGroupCapacity(asg, { @@ -1740,7 +1833,8 @@ export interface BootstrapOptions { /** * Extra arguments to add to the kubelet. Useful for adding labels or taints. * - * @example --node-labels foo=bar,goo=far + * For example, `--node-labels foo=bar,goo=far`. + * * @default - none */ readonly kubeletExtraArgs?: string; @@ -1811,12 +1905,14 @@ class ImportedCluster extends ClusterBase { public readonly clusterArn: string; public readonly connections = new ec2.Connections(); public readonly kubectlRole?: iam.IRole; + public readonly kubectlLambdaRole?: iam.IRole; public readonly kubectlEnvironment?: { [key: string]: string; } | undefined; public readonly kubectlSecurityGroup?: ec2.ISecurityGroup | undefined; public readonly kubectlPrivateSubnets?: ec2.ISubnet[] | undefined; public readonly kubectlLayer?: lambda.ILayerVersion; - public readonly onEventLayer?: lambda.ILayerVersion; public readonly kubectlMemory?: Size; + public readonly clusterHandlerSecurityGroup?: ec2.ISecurityGroup | undefined; + public readonly onEventLayer?: lambda.ILayerVersion; public readonly prune: boolean; // so that `clusterSecurityGroup` on `ICluster` can be configured without optionality, avoiding users from having @@ -1833,8 +1929,9 @@ class ImportedCluster extends ClusterBase { this.kubectlEnvironment = props.kubectlEnvironment; this.kubectlPrivateSubnets = props.kubectlPrivateSubnetIds ? props.kubectlPrivateSubnetIds.map((subnetid, index) => ec2.Subnet.fromSubnetId(this, `KubectlSubnet${index}`, subnetid)) : undefined; this.kubectlLayer = props.kubectlLayer; - this.onEventLayer = props.onEventLayer; this.kubectlMemory = props.kubectlMemory; + this.clusterHandlerSecurityGroup = props.clusterHandlerSecurityGroupId ? ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterHandlerSecurityGroup', props.clusterHandlerSecurityGroupId) : undefined; + this.onEventLayer = props.onEventLayer; this.prune = props.prune ?? true; let i = 1; diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts b/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts index 11036875a9d30..022d6bc6ecdbf 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { Cluster, ClusterOptions, CoreDnsComputeType } from './cluster'; -import { FargateProfileOptions } from './fargate-profile'; +import { FargateProfile, FargateProfileOptions } from './fargate-profile'; /** * Configuration props for EKS Fargate. @@ -23,6 +23,11 @@ export interface FargateClusterProps extends ClusterOptions { * `addFargateProfile`. */ export class FargateCluster extends Cluster { + /** + * Fargate Profile that was created with the cluster. + */ + public readonly defaultProfile: FargateProfile; + constructor(scope: Construct, id: string, props: FargateClusterProps) { super(scope, id, { ...props, @@ -31,7 +36,7 @@ export class FargateCluster extends Cluster { version: props.version, }); - this.addFargateProfile( + this.defaultProfile = this.addFargateProfile( props.defaultProfile?.fargateProfileName ?? (props.defaultProfile ? 'custom' : 'default'), props.defaultProfile ?? { selectors: [ diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index b5bd8ed51b876..0e5db3c6a51e3 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -77,6 +77,7 @@ export class KubectlProvider extends NestedStack { description: 'onEvent handler for EKS kubectl resource provider', memorySize, environment: cluster.kubectlEnvironment, + role: cluster.kubectlLambdaRole ? cluster.kubectlLambdaRole : undefined, // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, diff --git a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts index 69dd8223edc09..ec91d54abb610 100644 --- a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts @@ -34,7 +34,15 @@ export enum NodegroupAmiType { /** * Amazon Linux 2 (ARM-64) */ - AL2_ARM_64 = 'AL2_ARM_64' + AL2_ARM_64 = 'AL2_ARM_64', + /** + * Bottlerocket Linux(ARM-64) + */ + BOTTLEROCKET_ARM_64 = 'BOTTLEROCKET_ARM_64', + /** + * Bottlerocket(x86-64) + */ + BOTTLEROCKET_X86_64 = 'BOTTLEROCKET_x86_64', } /** diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 00be1a03620c4..48d5dad161a32 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,14 +84,14 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", "@types/yaml": "1.9.6", "aws-sdk": "^2.848.0", "cdk8s": "^0.33.0", "cdk8s-plus": "^0.33.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "sinon": "^9.2.4" }, "dependencies": { @@ -93,7 +100,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", @@ -113,7 +119,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture b/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture new file mode 100644 index 0000000000000..d0e854aa4b57d --- /dev/null +++ b/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture @@ -0,0 +1,35 @@ +import { Construct } from 'constructs'; +import { CfnOutput, Fn, Size, Stack } from '@aws-cdk/core'; +import * as eks from '@aws-cdk/aws-eks'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk8s from 'cdk8s'; +import * as kplus from 'cdk8s-plus'; + +interface MyChartProps { + readonly bucket: s3.Bucket; +} + +class MyChart extends cdk8s.Chart { + constructor(scope: Construct, id: string, props: MyChartProps) { + super(scope, id); + + new kplus.Pod(this, 'Pod', { + containers: [ + new kplus.Container({ + image: 'my-image', + env: { + BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), + }, + }), + ], + }); + } +} + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture index 65a6aabb9e5b4..a18b3f87d0f3d 100644 --- a/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture @@ -1,8 +1,15 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnOutput, Fn, Size, Stack } from '@aws-cdk/core'; import * as eks from '@aws-cdk/aws-eks'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; -class Context extends cdk.Construct { - constructor(scope: cdk.Construct, id: string) { +class Context extends Stack { + constructor(scope: Construct, id: string) { super(scope, id); /// here diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 14e2bfad0746b..5c6050a63682c 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -38,6 +38,25 @@ describe('cluster', () => { expect(template.Resources.OnEventHandler42BEBAE0.Properties.Environment).toEqual({ Variables: { foo: 'bar' } }); }); + test('can specify security group to cluster resource handler', () => { + const { stack, vpc } = testFixture(); + const securityGroup = new ec2.SecurityGroup(stack, 'ProxyInstanceSG', { + vpc, + allowAllOutbound: false, + }); + + new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + placeClusterHandlerInVpc: true, + clusterHandlerSecurityGroup: securityGroup, + }); + + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.ClusterResourceProvider') as cdk.NestedStack; + + const template = SynthUtils.toCloudFormation(nested); + expect(template.Resources.OnEventHandler42BEBAE0.Properties.VpcConfig.SecurityGroupIds).toEqual([{ Ref: 'referencetoStackProxyInstanceSG80B79D87GroupId' }]); + }); + test('throws when trying to place cluster handlers in a vpc with no private subnets', () => { const { stack } = testFixture(); @@ -55,6 +74,21 @@ describe('cluster', () => { }); + test('throws when provided `clusterHandlerSecurityGroup` without `placeClusterHandlerInVpc: true`', () => { + const { stack, vpc } = testFixture(); + const securityGroup = new ec2.SecurityGroup(stack, 'ProxyInstanceSG', { + vpc, + allowAllOutbound: false, + }); + + expect(() => { + new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + clusterHandlerSecurityGroup: securityGroup, + }); + }).toThrow(/Cannot specify clusterHandlerSecurityGroup without placeClusterHandlerInVpc set to true/); + }); + describe('imported Vpc from unparseable list tokens', () => { let stack: cdk.Stack; let vpc: ec2.IVpc; @@ -1537,7 +1571,7 @@ describe('cluster', () => { prune: false, defaultCapacityInstance: new ec2.InstanceType('m6g.medium'), }).addNodegroupCapacity('ng', { - instanceType: new ec2.InstanceType('m6g.medium'), + instanceTypes: [new ec2.InstanceType('m6g.medium')], }); // THEN @@ -1558,7 +1592,7 @@ describe('cluster', () => { prune: false, defaultCapacityInstance: new ec2.InstanceType('t4g.medium'), }).addNodegroupCapacity('ng', { - instanceType: new ec2.InstanceType('t4g.medium'), + instanceTypes: [new ec2.InstanceType('t4g.medium')], }); // THEN @@ -2168,6 +2202,42 @@ describe('cluster', () => { }, }); + }); + + test('kubectl provider passes iam role environment to kube ctl lambda', () => { + + const { stack } = testFixture(); + + const kubectlRole = new iam.Role(stack, 'KubectlIamRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + // using _ syntax to silence warning about _cluster not being used, when it is + const cluster = new eks.Cluster(stack, 'Cluster1', { + version: CLUSTER_VERSION, + prune: false, + endpointAccess: eks.EndpointAccess.PRIVATE, + kubectlLambdaRole: kubectlRole, + }); + + cluster.addManifest('resource', { + kind: 'ConfigMap', + apiVersion: 'v1', + data: { + hello: 'world', + }, + metadata: { + name: 'config-map', + }, + }); + + // the kubectl provider is inside a nested stack. + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + expect(nested).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + Ref: 'referencetoStackKubectlIamRole02F8947EArn', + }, + }); }); @@ -2888,4 +2958,26 @@ describe('cluster', () => { expect(providerNestedStackTemplate?.Resources?.Handler886CB40B?.Properties?.MemorySize).toEqual(4096); }); + + test('create a cluster using custom kubernetes network config', () => { + // GIVEN + const { stack } = testFixture(); + const customCidr = '172.16.0.0/12'; + + // WHEN + new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, + serviceIpv4Cidr: customCidr, + }); + + // THEN + expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Config: { + kubernetesNetworkConfig: { + serviceIpv4Cidr: customCidr, + }, + }, + }); + + }); }); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json new file mode 100644 index 0000000000000..7755a615e42e5 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json @@ -0,0 +1,1432 @@ +{ + "Resources": { + "AdminRole38563C57": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "ClusterRoleFA261979": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + } + ] + } + }, + "ClusterControlPlaneSecurityGroupD274242C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ClusterCreationRole360249B6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + }, + "DependsOn": [ + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ] + }, + "ClusterCreationRoleDefaultPolicyE8BDFC7B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterRoleFA261979", + "Arn" + ] + } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DescribeUpdate", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig", + "eks:CreateFargateProfile", + "eks:TagResource", + "eks:UntagResource" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "eks:DescribeFargateProfile", + "eks:DeleteFargateProfile" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:GetRole", + "iam:listAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables", + "ec2:DescribeDhcpOptions", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "Roles": [ + { + "Ref": "ClusterCreationRole360249B6" + } + ] + }, + "DependsOn": [ + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ] + }, + "Cluster9EE0221C": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksclustertestawscdkawseksClusterResourceProviderframeworkonEvent503C1667Arn" + ] + }, + "Config": { + "version": "1.21", + "roleArn": { + "Fn::GetAtt": [ + "ClusterRoleFA261979", + "Arn" + ] + }, + "resourcesVpcConfig": { + "subnetIds": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + } + ], + "endpointPublicAccess": true, + "endpointPrivateAccess": true + } + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "AttributesRevision": 2 + }, + "DependsOn": [ + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6", + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterKubectlReadyBarrier200052AF": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6", + "Cluster9EE0221C" + ] + }, + "ClusterAwsAuthmanifestFE51F8AE": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\",\"labels\":{\"aws.cdk.eks/prune-c842be348c45337cd97b8759de76d5a68b4910d487\":\"\"}},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "PruneLabel": "aws.cdk.eks/prune-c842be348c45337cd97b8759de76d5a68b4910d487", + "Overwrite": true + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ] + } + }, + "ClusterNodegroupBottlerocketNG1B78D1784": { + "Type": "AWS::EKS::Nodegroup", + "Properties": { + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "NodeRole": { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6", + "Arn" + ] + }, + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "AmiType": "BOTTLEROCKET_x86_64", + "ForceUpdateEnabled": true, + "ScalingConfig": { + "DesiredSize": 2, + "MaxSize": 2, + "MinSize": 1 + } + } + }, + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ] + } + }, + "ClusterNodegroupBottlerocketNG299226DAB": { + "Type": "AWS::EKS::Nodegroup", + "Properties": { + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "NodeRole": { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB", + "Arn" + ] + }, + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "AmiType": "BOTTLEROCKET_ARM_64", + "ForceUpdateEnabled": true, + "ScalingConfig": { + "DesiredSize": 2, + "MaxSize": 2, + "MinSize": 1 + } + } + }, + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3BucketA775E312" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksclustertestAssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1771F046Ref": { + "Ref": "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1B280681" + }, + "referencetoawscdkeksclustertestAssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyDA854AFERef": { + "Ref": "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyB1E02791" + }, + "referencetoawscdkeksclustertestClusterCreationRole95F44854Arn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "referencetoawscdkeksclustertestAssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3BucketDA4E9DCDRef": { + "Ref": "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3Bucket3B443230" + }, + "referencetoawscdkeksclustertestAssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKey6F8004B6Ref": { + "Ref": "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKeyAA4674FB" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3Bucket0782C98E" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksclustertestClusterD76DFF87Arn": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Arn" + ] + }, + "referencetoawscdkeksclustertestClusterCreationRole95F44854Arn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "referencetoawscdkeksclustertestAssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3Bucket3929FA93Ref": { + "Ref": "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3BucketC6FAEEC9" + }, + "referencetoawscdkeksclustertestAssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKey14530D6BRef": { + "Ref": "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKeyA7EE7421" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet1Subnet32A4EC2ARef": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet2Subnet5CC53627Ref": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet3Subnet7F5D6918Ref": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + }, + "referencetoawscdkeksclustertestClusterD76DFF87ClusterSecurityGroupId": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "ClusterSecurityGroupId" + ] + }, + "referencetoawscdkeksclustertestAssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketB4E9C142Ref": { + "Ref": "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketAEADE8C7" + }, + "referencetoawscdkeksclustertestAssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKey1C7C1F5FRef": { + "Ref": "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKeyE415415F" + }, + "referencetoawscdkeksclustertestAssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3Bucket6ADB5CE5Ref": { + "Ref": "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3BucketD3288998" + }, + "referencetoawscdkeksclustertestAssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKey314C5B11Ref": { + "Ref": "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKeyB00C0565" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ClusterConfigCommand43AAE40F": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + } + ] + ] + } + }, + "ClusterGetTokenCommand06AE992E": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + } + ] + ] + } + } + }, + "Parameters": { + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1B280681": { + "Type": "String", + "Description": "S3 bucket for asset \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyB1E02791": { + "Type": "String", + "Description": "S3 key for asset version \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665ArtifactHash9EA5AC29": { + "Type": "String", + "Description": "Artifact hash for asset \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3Bucket3B443230": { + "Type": "String", + "Description": "S3 bucket for asset \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKeyAA4674FB": { + "Type": "String", + "Description": "S3 key for asset version \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720ArtifactHash3D7A279D": { + "Type": "String", + "Description": "Artifact hash for asset \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "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\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3BucketC6FAEEC9": { + "Type": "String", + "Description": "S3 bucket for asset \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKeyA7EE7421": { + "Type": "String", + "Description": "S3 key for asset version \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10ArtifactHash528547CD": { + "Type": "String", + "Description": "Artifact hash for asset \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketAEADE8C7": { + "Type": "String", + "Description": "S3 bucket for asset \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKeyE415415F": { + "Type": "String", + "Description": "S3 key for asset version \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68ArtifactHashD9A515C3": { + "Type": "String", + "Description": "Artifact hash for asset \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3BucketD3288998": { + "Type": "String", + "Description": "S3 bucket for asset \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKeyB00C0565": { + "Type": "String", + "Description": "S3 key for asset version \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eArtifactHash4654D012": { + "Type": "String", + "Description": "Artifact hash for asset \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3BucketA775E312": { + "Type": "String", + "Description": "S3 bucket for asset \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B": { + "Type": "String", + "Description": "S3 key for asset version \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9ArtifactHashBC5BD0D7": { + "Type": "String", + "Description": "Artifact hash for asset \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3Bucket0782C98E": { + "Type": "String", + "Description": "S3 bucket for asset \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC": { + "Type": "String", + "Description": "S3 key for asset version \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22ArtifactHash75F0D468": { + "Type": "String", + "Description": "Artifact hash for asset \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts new file mode 100644 index 0000000000000..d27d92d984d4c --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts @@ -0,0 +1,47 @@ +/// !cdk-integ pragma:ignore-assets +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import { App } from '@aws-cdk/core'; +import * as eks from '../lib'; +import { NodegroupAmiType } from '../lib'; +import { TestStack } from './util'; + + +class EksClusterStack extends TestStack { + + private cluster: eks.Cluster; + private vpc: ec2.IVpc; + + constructor(scope: App, id: string) { + super(scope, id); + + // allow all account users to assume this role in order to admin the cluster + const mastersRole = new iam.Role(this, 'AdminRole', { + assumedBy: new iam.AccountRootPrincipal(), + }); + + // just need one nat gateway to simplify the test + this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); + + // create the cluster with a default nodegroup capacity + this.cluster = new eks.Cluster(this, 'Cluster', { + vpc: this.vpc, + mastersRole, + defaultCapacity: 0, + version: eks.KubernetesVersion.V1_21, + }); + + this.cluster.addNodegroupCapacity('BottlerocketNG1', { + amiType: NodegroupAmiType.BOTTLEROCKET_X86_64, + }); + this.cluster.addNodegroupCapacity('BottlerocketNG2', { + amiType: NodegroupAmiType.BOTTLEROCKET_ARM_64, + }); + } +} + +const app = new App(); + +new EksClusterStack(app, 'aws-cdk-eks-cluster-test'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts b/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts index 47ef518f68032..0711bb5c0bb4c 100644 --- a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts +++ b/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts @@ -1,7 +1,9 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; +import { NodegroupAmiType } from '../lib'; import { testFixture } from './util'; /* eslint-disable max-len */ @@ -99,7 +101,7 @@ describe('node group', () => { }); - test('create nodegroup correctly', () => { + test('create a default nodegroup correctly', () => { // GIVEN const { stack, vpc } = testFixture(); @@ -139,6 +141,97 @@ describe('node group', () => { }); + }); + + test('create a x86_64 bottlerocket nodegroup correctly', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + new eks.Nodegroup(stack, 'Nodegroup', { + cluster, + amiType: NodegroupAmiType.BOTTLEROCKET_X86_64, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + NodeRole: { + 'Fn::GetAtt': [ + 'NodegroupNodeGroupRole038A128B', + 'Arn', + ], + }, + Subnets: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + AmiType: 'BOTTLEROCKET_x86_64', + ForceUpdateEnabled: true, + ScalingConfig: { + DesiredSize: 2, + MaxSize: 2, + MinSize: 1, + }, + }); + + + }); + test('create a ARM_64 bottlerocket nodegroup correctly', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + new eks.Nodegroup(stack, 'Nodegroup', { + cluster, + amiType: NodegroupAmiType.BOTTLEROCKET_ARM_64, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + NodeRole: { + 'Fn::GetAtt': [ + 'NodegroupNodeGroupRole038A128B', + 'Arn', + ], + }, + Subnets: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + AmiType: 'BOTTLEROCKET_ARM_64', + ForceUpdateEnabled: true, + ScalingConfig: { + DesiredSize: 2, + MaxSize: 2, + MinSize: 1, + }, + }); + + }); test('aws-auth will be updated', () => { // GIVEN @@ -250,7 +343,7 @@ describe('node group', () => { }); - test('create nodegroup with instanceType provided', () => { + test('create nodegroup with instanceTypes provided', () => { // GIVEN const { stack, vpc } = testFixture(); @@ -262,7 +355,7 @@ describe('node group', () => { }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, - instanceType: new ec2.InstanceType('m5.large'), + instanceTypes: [new ec2.InstanceType('m5.large')], }); // THEN @@ -286,7 +379,7 @@ describe('node group', () => { }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, - instanceType: new ec2.InstanceType('m5.large'), + instanceTypes: [new ec2.InstanceType('m5.large')], capacityType: eks.CapacityType.ON_DEMAND, }); @@ -362,7 +455,7 @@ describe('node group', () => { }); - test('throws when both instanceTypes and instanceType defined', () => { + testDeprecated('throws when both instanceTypes and instanceType defined', () => { // GIVEN const { stack, vpc } = testFixture(); diff --git a/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts index 1165da1ca90df..3702484a68339 100644 --- a/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts +++ b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts @@ -12,6 +12,7 @@ export interface PingerProps { readonly url: string; readonly securityGroup?: ec2.SecurityGroup; readonly vpc?: ec2.IVpc; + readonly subnets?: ec2.ISubnet[]; } export class Pinger extends CoreConstruct { @@ -25,6 +26,7 @@ export class Pinger extends CoreConstruct { handler: 'index.handler', runtime: lambda.Runtime.PYTHON_3_6, vpc: props.vpc, + vpcSubnets: props.subnets ? { subnets: props.subnets } : undefined, securityGroups: props.securityGroup ? [props.securityGroup] : undefined, timeout: Duration.minutes(10), }); diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index 8176496ecfad9..5cf8e0a657b51 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index a7523e7a9bf58..f41a30fdec4f0 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index 9b73054622979..874f90e6ec065 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index aa6c1c8b88ad0..457742ff5db70 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { Connections, Peer, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack } from '@aws-cdk/core'; import { ILoadBalancerTarget, LoadBalancer, LoadBalancingProtocol } from '../lib'; @@ -177,7 +178,7 @@ describe('tests', () => { }); }); - test('does not fail when deprecated property sslCertificateId is used', () => { + testDeprecated('does not fail when deprecated property sslCertificateId is used', () => { // GIVEN const sslCertificateArn = 'arn:aws:acm:us-east-1:12345:test/12345'; const stack = new Stack(); @@ -231,7 +232,7 @@ describe('tests', () => { }); }); - test('throws error when both sslCertificateId and sslCertificateArn are used', () => { + testDeprecated('throws error when both sslCertificateId and sslCertificateArn are used', () => { // GIVEN const sslCertificateArn = 'arn:aws:acm:us-east-1:12345:test/12345'; const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json index a431ef48ddbd5..249d3a00ae197 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json @@ -69,8 +69,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cognito": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index e9b09e29964ff..6fc1ac8745fcd 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -69,8 +69,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3", + "@types/jest": "^27.0.2", + "jest": "^27.3.1", "@aws-cdk/aws-ecs": "0.0.0", "@aws-cdk/aws-ecs-patterns": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.alb-target.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.alb-target.expected.json index 8832e3edf3726..ff70395ffda8b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.alb-target.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.alb-target.expected.json @@ -462,6 +462,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "TargetType": "ip", "VpcId": { "Ref": "Vpc8378EB38" diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.lambda-target.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.lambda-target.expected.json index cca2c6d366998..75905be0cd5ba 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.lambda-target.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/test/integ.lambda-target.expected.json @@ -95,15 +95,15 @@ "StackPublicSubnet1NATGatewayD2E1ABF7": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "StackPublicSubnet1Subnet0AD81D22" + }, "AllocationId": { "Fn::GetAtt": [ "StackPublicSubnet1EIPBDAAB2A5", "AllocationId" ] }, - "SubnetId": { - "Ref": "StackPublicSubnet1Subnet0AD81D22" - }, "Tags": [ { "Key": "Name", @@ -404,6 +404,12 @@ "LBListenerTargetsGroup76EF81E8": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "Targets": [ { "Id": { @@ -457,13 +463,13 @@ "Code": { "ZipFile": "\ndef handler(event, context):\n return {\n \"isBase64Encoded\": False,\n \"statusCode\": 200,\n \"statusDescription\": \"200 OK\",\n \"headers\": {\n \"Set-cookie\": \"cookies\",\n \"Content-Type\": \"application/json\"\n },\n \"body\": \"Hello from Lambda\"\n }\n " }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FunServiceRole3CC876D7", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index f3d23b5e827cf..34755353fb202 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -26,13 +26,10 @@ You define an application load balancer by creating an instance of and adding Targets to the Listener: ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; +declare const asg: AutoScalingGroup; -// ... - -const vpc = new ec2.Vpc(...); +declare const vpc: ec2.Vpc; // Create the load balancer in a VPC. 'internetFacing' is 'false' // by default, which creates an internal load balancer. @@ -54,7 +51,6 @@ const listener = lb.addListener('Listener', { // Create an AutoScaling group and add it as a load balancing // target to the listener. -const asg = new AutoScalingGroup(...); listener.addTargets('ApplicationFleet', { port: 8080, targets: [asg] @@ -68,14 +64,16 @@ One (or more) security groups can be associated with the load balancer; if a security group isn't provided, one will be automatically created. ```ts -const securityGroup1 = new ec2.SecurityGroup(stack, 'SecurityGroup1', { vpc }); +declare const vpc: ec2.Vpc; + +const securityGroup1 = new ec2.SecurityGroup(this, 'SecurityGroup1', { vpc }); const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true, securityGroup: securityGroup1, // Optional - will be automatically created otherwise }); -const securityGroup2 = new ec2.SecurityGroup(stack, 'SecurityGroup2', { vpc }); +const securityGroup2 = new ec2.SecurityGroup(this, 'SecurityGroup2', { vpc }); lb.addSecurityGroup(securityGroup2); ``` @@ -87,11 +85,14 @@ AutoScalingGroup only if the requested host in the request is either for `example.com/ok` or `example.com/path`: ```ts +declare const listener: elbv2.ApplicationListener; +declare const asg: autoscaling.AutoScalingGroup; + listener.addTargets('Example.Com Fleet', { priority: 10, conditions: [ - ListenerCondition.hostHeaders(['example.com']), - ListenerCondition.pathPatterns(['/ok', '/path']), + elbv2.ListenerCondition.hostHeaders(['example.com']), + elbv2.ListenerCondition.pathPatterns(['/ok', '/path']), ], port: 8080, targets: [asg] @@ -149,12 +150,14 @@ Balancer that the other two convenience methods don't: Here's an example of serving a fixed response at the `/ok` URL: ```ts +declare const listener: elbv2.ApplicationListener; + listener.addAction('Fixed', { priority: 10, conditions: [ - ListenerCondition.pathPatterns(['/ok']), + elbv2.ListenerCondition.pathPatterns(['/ok']), ], - action: ListenerAction.fixedResponse(200, { + action: elbv2.ListenerAction.fixedResponse(200, { contentType: elbv2.ContentType.TEXT_PLAIN, messageBody: 'OK', }) @@ -164,12 +167,21 @@ listener.addAction('Fixed', { Here's an example of using OIDC authentication before forwarding to a TargetGroup: ```ts +declare const listener: elbv2.ApplicationListener; +declare const myTargetGroup: elbv2.ApplicationTargetGroup; + listener.addAction('DefaultAction', { - action: ListenerAction.authenticateOidc({ + action: elbv2.ListenerAction.authenticateOidc({ authorizationEndpoint: 'https://example.com/openid', // Other OIDC properties here - // ... - next: ListenerAction.forward([myTargetGroup]), + clientId: '...', + clientSecret: SecretValue.secretsManager('...'), + issuer: '...', + tokenEndpoint: '...', + userInfoEndpoint: '...', + + // Next + next: elbv2.ListenerAction.forward([myTargetGroup]), }), }); ``` @@ -177,6 +189,8 @@ listener.addAction('DefaultAction', { If you just want to redirect all incoming traffic on one port to another port, you can use the following code: ```ts +declare const lb: elbv2.ApplicationLoadBalancer; + lb.addRedirect({ sourceProtocol: elbv2.ApplicationProtocol.HTTPS, sourcePort: 8443, @@ -196,9 +210,8 @@ Network Load Balancers are defined in a similar way to Application Load Balancers: ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import * as autoscaling from '@aws-cdk/aws-autoscaling'; +declare const vpc: ec2.Vpc; +declare const asg: autoscaling.AutoScalingGroup; // Create the load balancer in a VPC. 'internetFacing' is 'false' // by default, which creates an internal load balancer. @@ -243,6 +256,10 @@ and add it to the listener by calling `addTargetGroups` instead of `addTargets`. `addTargets()` will always return the Target Group it just created for you: ```ts +declare const listener: elbv2.NetworkListener; +declare const asg1: autoscaling.AutoScalingGroup; +declare const asg2: autoscaling.AutoScalingGroup; + const group = listener.addTargets('AppFleet', { port: 443, targets: [asg1], @@ -258,19 +275,21 @@ By default, an Application Load Balancer routes each request independently to a Application Load Balancers support both duration-based cookies (`lb_cookie`) and application-based cookies (`app_cookie`). The key to managing sticky sessions is determining how long your load balancer should consistently route the user's request to the same target. Sticky sessions are enabled at the target group level. You can use a combination of duration-based stickiness, application-based stickiness, and no stickiness across all of your target groups. ```ts +declare const vpc: ec2.Vpc; + // Target group with duration-based stickiness with load-balancer generated cookie -const tg1 = new elbv2.ApplicationTargetGroup(stack, 'TG1', { +const tg1 = new elbv2.ApplicationTargetGroup(this, 'TG1', { targetType: elbv2.TargetType.INSTANCE, port: 80, - stickinessCookieDuration: cdk.Duration.minutes(5), + stickinessCookieDuration: Duration.minutes(5), vpc, }); // Target group with application-based stickiness -const tg2 = new elbv2.ApplicationTargetGroup(stack, 'TG2', { +const tg2 = new elbv2.ApplicationTargetGroup(this, 'TG2', { targetType: elbv2.TargetType.INSTANCE, port: 80, - stickinessCookieDuration: cdk.Duration.minutes(5), + stickinessCookieDuration: Duration.minutes(5), stickinessCookieName: 'MyDeliciousCookie', vpc, }); @@ -283,7 +302,9 @@ For more information see: https://docs.aws.amazon.com/elasticloadbalancing/lates By default, Application Load Balancers send requests to targets using HTTP/1.1. You can use the [protocol version](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-protocol-version) to send requests to targets using HTTP/2 or gRPC. ```ts -const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { +declare const vpc: ec2.Vpc; + +const tg = new elbv2.ApplicationTargetGroup(this, 'TG', { targetType: elbv2.TargetType.IP, port: 50051, protocol: elbv2.ApplicationProtocol.HTTP, @@ -303,11 +324,10 @@ To use a Lambda Function as a target, use the integration class in the ```ts import * as lambda from '@aws-cdk/aws-lambda'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as targets from '@aws-cdk/aws-elasticloadbalancingv2-targets'; -const lambdaFunction = new lambda.Function(...); -const lb = new elbv2.ApplicationLoadBalancer(...); +declare const lambdaFunction: lambda.Function; +declare const lb: elbv2.ApplicationLoadBalancer; const listener = lb.addListener('Listener', { port: 80 }); listener.addTargets('Targets', { @@ -329,11 +349,12 @@ To use a single application load balancer as a target for the network load balan `@aws-cdk/aws-elasticloadbalancingv2-targets` package: ```ts -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as targets from '@aws-cdk/aws-elasticloadbalancingv2-targets'; import * as ecs from '@aws-cdk/aws-ecs'; import * as patterns from '@aws-cdk/aws-ecs-patterns'; +declare const vpc: ec2.Vpc; + const task = new ecs.FargateTaskDefinition(this, 'Task', { cpu: 256, memoryLimitMiB: 512 }); task.addContainer('nginx', { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/nginx/nginx:latest'), @@ -369,12 +390,15 @@ Only the network load balancer is allowed to add the application load balancer a Health checks are configured upon creation of a target group: ```ts +declare const listener: elbv2.ApplicationListener; +declare const asg: autoscaling.AutoScalingGroup; + listener.addTargets('AppFleet', { port: 8080, targets: [asg], healthCheck: { path: '/ping', - interval: cdk.Duration.minutes(1), + interval: Duration.minutes(1), } }); ``` @@ -388,15 +412,19 @@ you're routing traffic to, the security group already allows the traffic. If not, you will have to configure the security groups appropriately: ```ts +declare const lb: elbv2.ApplicationLoadBalancer; +declare const listener: elbv2.ApplicationListener; +declare const asg: autoscaling.AutoScalingGroup; + listener.addTargets('AppFleet', { port: 8080, targets: [asg], healthCheck: { - port: 8088, + port: '8088', } }); -listener.connections.allowFrom(lb, ec2.Port.tcp(8088)); +asg.connections.allowFrom(lb, ec2.Port.tcp(8088)); ``` ## Using a Load Balancer from a different Stack @@ -424,12 +452,15 @@ call functions on the load balancer and should return metadata about the load balancing target: ```ts -public attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps { - targetGroup.registerConnectable(...); - return { - targetType: TargetType.Instance | TargetType.Ip - targetJson: { id: ..., port: ... }, - }; +class MyTarget implements elbv2.IApplicationLoadBalancerTarget { + public attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { + // If we need to add security group rules + // targetGroup.registerConnectable(...); + return { + targetType: elbv2.TargetType.IP, + targetJson: { id: '1.2.3.4', port: 8080 }, + }; + } } ``` @@ -445,12 +476,16 @@ group rules. If your load balancer target requires that the TargetGroup has been associated with a LoadBalancer before registration can happen (such as is the case for ECS Services for example), take a resource dependency on -`targetGroup.loadBalancerDependency()` as follows: +`targetGroup.loadBalancerAttached` as follows: ```ts +declare const resource: Resource; +declare const targetGroup: elbv2.ApplicationTargetGroup; + // Make sure that the listener has been created, and so the TargetGroup // has been associated with the LoadBalancer, before 'resource' is created. -resourced.addDependency(targetGroup.loadBalancerDependency()); + +Node.of(resource).addDependency(targetGroup.loadBalancerAttached); ``` ## Looking up Load Balancers and Listeners @@ -478,15 +513,15 @@ provide more specific criteria. **Look up a Application Load Balancer by ARN** ```ts -const loadBalancer = ApplicationLoadBalancer.fromLookup(stack, 'ALB', { - loadBalancerArn: YOUR_ALB_ARN, +const loadBalancer = elbv2.ApplicationLoadBalancer.fromLookup(this, 'ALB', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456', }); ``` **Look up an Application Load Balancer by tags** ```ts -const loadBalancer = ApplicationLoadBalancer.fromLookup(stack, 'ALB', { +const loadBalancer = elbv2.ApplicationLoadBalancer.fromLookup(this, 'ALB', { loadBalancerTags: { // Finds a load balancer matching all tags. some: 'tag', @@ -512,9 +547,9 @@ criteria. **Look up a Listener by associated Load Balancer, Port, and Protocol** ```ts -const listener = ApplicationListener.fromLookup(stack, 'ALBListener', { - loadBalancerArn: YOUR_ALB_ARN, - listenerProtocol: ApplicationProtocol.HTTPS, +const listener = elbv2.ApplicationListener.fromLookup(this, 'ALBListener', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456', + listenerProtocol: elbv2.ApplicationProtocol.HTTPS, listenerPort: 443, }); ``` @@ -522,11 +557,11 @@ const listener = ApplicationListener.fromLookup(stack, 'ALBListener', { **Look up a Listener by associated Load Balancer Tag, Port, and Protocol** ```ts -const listener = ApplicationListener.fromLookup(stack, 'ALBListener', { +const listener = elbv2.ApplicationListener.fromLookup(this, 'ALBListener', { loadBalancerTags: { Cluster: 'MyClusterName', }, - listenerProtocol: ApplicationProtocol.HTTPS, + listenerProtocol: elbv2.ApplicationProtocol.HTTPS, listenerPort: 443, }); ``` @@ -534,11 +569,11 @@ const listener = ApplicationListener.fromLookup(stack, 'ALBListener', { **Look up a Network Listener by associated Load Balancer Tag, Port, and Protocol** ```ts -const listener = NetworkListener.fromLookup(stack, 'ALBListener', { +const listener = elbv2.NetworkListener.fromLookup(this, 'ALBListener', { loadBalancerTags: { Cluster: 'MyClusterName', }, - listenerProtocol: Protocol.TCP, + listenerProtocol: elbv2.Protocol.TCP, listenerPort: 12345, }); ``` diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index e71ccbd5c74de..7530ad08f8cb1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -258,7 +258,9 @@ export class ApplicationListenerRule extends CoreConstruct { this.configureAction(props.action); } - (props.targetGroups || []).forEach(this.addTargetGroup.bind(this)); + (props.targetGroups || []).forEach((group) => { + this.configureAction(ListenerAction.forward([group])); + }); if (props.fixedResponse) { this.addFixedResponse(props.fixedResponse); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 1a2e6c55ee815..2994a437b3929 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -293,12 +293,8 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // TargetGroup.registerListener is called inside ApplicationListenerRule. new ApplicationListenerRule(this, id + 'Rule', { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - action: props.action, + ...props, }); } else { // New default target with these targetgroups @@ -325,12 +321,8 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // TargetGroup.registerListener is called inside ApplicationListenerRule. new ApplicationListenerRule(this, id + 'Rule', { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - targetGroups: props.targetGroups, + ...props, }); } else { // New default target with these targetgroups @@ -359,27 +351,13 @@ export class ApplicationListener extends BaseListener implements IApplicationLis } const group = new ApplicationTargetGroup(this, id + 'Group', { - deregistrationDelay: props.deregistrationDelay, - healthCheck: props.healthCheck, - port: props.port, - protocol: props.protocol, - protocolVersion: props.protocolVersion, - slowStart: props.slowStart, - stickinessCookieDuration: props.stickinessCookieDuration, - stickinessCookieName: props.stickinessCookieName, - loadBalancingAlgorithmType: props.loadBalancingAlgorithmType, - targetGroupName: props.targetGroupName, - targets: props.targets, vpc: this.loadBalancer.vpc, + ...props, }); this.addTargetGroups(id, { - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, - priority: props.priority, targetGroups: [group], + ...props, }); return group; @@ -607,10 +585,9 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat * Add one or more certificates to this listener. */ public addCertificates(id: string, certificates: IListenerCertificate[]): void { - const arns = certificates.map(c => c.certificateArn); new ApplicationListenerCertificate(this, id, { listener: this, - certificateArns: arns, + certificates, }); } @@ -627,12 +604,8 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat // New rule new ApplicationListenerRule(this, id, { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - targetGroups: props.targetGroups, + ...props, }); } else { throw new Error('Cannot add default Target Groups to imported ApplicationListener'); @@ -700,7 +673,7 @@ class LookedUpApplicationListener extends ExternalApplicationListener { }); for (const securityGroupId of props.securityGroupIds) { - const securityGroup = ec2.SecurityGroup.fromLookup(this, `SecurityGroup-${securityGroupId}`, securityGroupId); + const securityGroup = ec2.SecurityGroup.fromLookupById(this, `SecurityGroup-${securityGroupId}`, securityGroupId); this.connections.addSecurityGroup(securityGroup); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index c3cac3ae37eee..10291f2369849 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -148,7 +148,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic return new cloudwatch.Metric({ namespace: 'AWS/ApplicationELB', metricName, - dimensions: { LoadBalancer: this.loadBalancerFullName }, + dimensionsMap: { LoadBalancer: this.loadBalancerFullName }, ...props, }); } @@ -642,7 +642,7 @@ class LookedUpApplicationLoadBalancer extends Resource implements IApplicationLo this.connections = new ec2.Connections(); for (const securityGroupId of props.securityGroupIds) { - const securityGroup = ec2.SecurityGroup.fromLookup(this, `SecurityGroup-${securityGroupId}`, securityGroupId); + const securityGroup = ec2.SecurityGroup.fromLookupById(this, `SecurityGroup-${securityGroupId}`, securityGroupId); this.connections.addSecurityGroup(securityGroup); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index d1329665ba7d6..487394019ed15 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -144,9 +144,13 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat } this.setAttribute('slow_start.duration_seconds', props.slowStart.toSeconds().toString()); } + if (props.stickinessCookieDuration) { this.enableCookieStickiness(props.stickinessCookieDuration, props.stickinessCookieName); + } else { + this.setAttribute('stickiness.enabled', 'false'); } + if (props.loadBalancingAlgorithmType) { this.setAttribute('load_balancing.algorithm.type', props.loadBalancingAlgorithmType); } @@ -250,7 +254,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat return new cloudwatch.Metric({ namespace: 'AWS/ApplicationELB', metricName, - dimensions: { + dimensionsMap: { TargetGroup: this.targetGroupFullName, LoadBalancer: this.firstLoadBalancerFullName, }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index f4ac146d8209d..35c56e21e29bc 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -213,7 +213,7 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge return new cloudwatch.Metric({ namespace: 'AWS/NetworkELB', metricName, - dimensions: { LoadBalancer: this.firstLoadBalancerFullName, TargetGroup: this.targetGroupFullName }, + dimensionsMap: { LoadBalancer: this.firstLoadBalancerFullName, TargetGroup: this.targetGroupFullName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index bcf51d4d81a78..85ae9d143f0e2 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -52,16 +52,18 @@ export interface ILoadBalancerV2 extends IResource { /** * The canonical hosted zone ID of this load balancer * + * Example value: `Z2P70J7EXAMPLE` + * * @attribute - * @example Z2P70J7EXAMPLE */ readonly loadBalancerCanonicalHostedZoneId: string; /** * The DNS name of this load balancer * + * Example value: `my-load-balancer-424835706.us-west-2.elb.amazonaws.com` + * * @attribute - * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com */ readonly loadBalancerDnsName: string; } @@ -141,40 +143,45 @@ export abstract class BaseLoadBalancer extends Resource { /** * The canonical hosted zone ID of this load balancer * + * Example value: `Z2P70J7EXAMPLE` + * * @attribute - * @example Z2P70J7EXAMPLE */ public readonly loadBalancerCanonicalHostedZoneId: string; /** * The DNS name of this load balancer * + * Example value: `my-load-balancer-424835706.us-west-2.elb.amazonaws.com` + * * @attribute - * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com */ public readonly loadBalancerDnsName: string; /** * The full name of this load balancer * + * Example value: `app/my-load-balancer/50dc6c495c0c9188` + * * @attribute - * @example app/my-load-balancer/50dc6c495c0c9188 */ public readonly loadBalancerFullName: string; /** * The name of this load balancer * + * Example value: `my-load-balancer` + * * @attribute - * @example my-load-balancer */ public readonly loadBalancerName: string; /** * The ARN of this load balancer * + * Example value: `arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188` + * * @attribute - * @example arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 */ public readonly loadBalancerArn: string; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 29a9cb707556e..69d925bc933e9 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -186,7 +186,7 @@ export abstract class TargetGroupBase extends CoreConstruct implements ITargetGr * This identifier is emitted as a dimensions of the metrics of this target * group. * - * @example app/my-load-balancer/123456789 + * Example value: `app/my-load-balancer/123456789` */ public abstract readonly firstLoadBalancerFullName: string; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 2a74598925bbb..d2a9b352b4163 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,7 +84,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-elasticloadbalancingv2/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..8a21c6ec4d9a6 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import { Construct, Node } from 'constructs'; +import { CfnOutput, Stack, Duration, Resource, SecretValue } from '@aws-cdk/core'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + + + diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts index e69f4d241bc04..b3a6397ba6eaa 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts @@ -155,7 +155,7 @@ describe('tests', () => { }); listener.addAction('Action2', { - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], priority: 10, action: elbv2.ListenerAction.forward([group2]), }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 885c872482e9c..14f9bd51bee61 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1,7 +1,9 @@ import { MatchStyle } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import * as acm from '@aws-cdk/aws-certificatemanager'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as elbv2 from '../../lib'; @@ -17,7 +19,7 @@ describe('tests', () => { // WHEN lb.addListener('Listener', { port: 443, - certificateArns: ['bla'], + certificates: [importedCertificate(stack)], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -133,7 +135,7 @@ describe('tests', () => { defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); - listener.addCertificateArns('Arns', ['cert']); + listener.addCertificates('Certs', [importedCertificate(stack, 'cert')]); // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::Listener', { @@ -230,7 +232,7 @@ describe('tests', () => { }); listener.addTargetGroups('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [group], }); @@ -248,7 +250,7 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/hello'], + PathPatternConfig: { Values: ['/hello'] }, }, ], Actions: [ @@ -260,7 +262,7 @@ describe('tests', () => { }); }); - test('Can implicitly create target groups with and without conditions', () => { + testDeprecated('Can implicitly create target groups with and without conditions', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -274,7 +276,7 @@ describe('tests', () => { }); listener.addTargets('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], port: 80, targets: [new elbv2.InstanceTarget('i-5678')], }); @@ -314,7 +316,7 @@ describe('tests', () => { }); }); - test('Add certificate to constructed listener', () => { + testDeprecated('Add certificate to constructed listener', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -322,7 +324,7 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443 }); // WHEN - listener.addCertificateArns('Arns', ['cert']); + listener.addCertificates('Certs', [importedCertificate(stack, 'cert')]); listener.addTargets('Targets', { port: 8080, targets: [new elbv2.IpTarget('1.2.3.4')] }); // THEN @@ -339,11 +341,11 @@ describe('tests', () => { const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'Listener', { listenerArn: 'listener-arn', defaultPort: 443, - securityGroupId: 'security-group-id', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SG', 'security-group-id'), }); // WHEN - listener2.addCertificateArns('Arns', ['cert']); + listener2.addCertificates('Certs', [importedCertificate(stack2, 'cert')]); // THEN expect(stack2).toHaveResource('AWS::ElasticLoadBalancingV2::ListenerCertificate', { @@ -482,7 +484,7 @@ describe('tests', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443, certificateArns: ['arn:someCert'] }); + const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'arn:someCert')] }); // WHEN listener.addTargets('Group', { @@ -503,14 +505,14 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'ieks', - securityGroupId: 'sg-12345', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-12345'), }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); // WHEN listener.addTargetGroups('Gruuup', { priority: 30, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [group], }); @@ -533,7 +535,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'ieks', - securityGroupId: 'sg-12345', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-12345'), }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); @@ -640,7 +642,7 @@ describe('tests', () => { new ResourceWithLBDependency(stack, 'SomeResource', group2); listener.addTargetGroups('SecondGroup', { - pathPattern: '/bla', + conditions: [elbv2.ListenerCondition.pathPatterns(['/bla'])], priority: 10, targetGroups: [group2], }); @@ -668,15 +670,16 @@ describe('tests', () => { }); // WHEN - listener.addFixedResponse('Default', { - contentType: elbv2.ContentType.TEXT_PLAIN, - messageBody: 'Not Found', - statusCode: '404', + listener.addAction('Default', { + action: elbv2.ListenerAction.fixedResponse(404, { + contentType: 'text/plain', + messageBody: 'Not Found', + }), }); - listener.addFixedResponse('Hello', { + listener.addAction('Hello', { + action: elbv2.ListenerAction.fixedResponse(503), + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], priority: 10, - pathPattern: '/hello', - statusCode: '503', }); // THEN @@ -705,7 +708,7 @@ describe('tests', () => { }); }); - test('Can add redirect responses', () => { + testDeprecated('Can add redirect responses', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -724,7 +727,7 @@ describe('tests', () => { }); listener.addRedirectResponse('Hello', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], path: '/new/#{path}', statusCode: 'HTTP_302', }); @@ -824,7 +827,7 @@ describe('tests', () => { targetProtocol: elbv2.ApplicationProtocol.HTTP, targetPort: 8080, }); - listener.addCertificateArns('ListenerCertificateX', ['cert3']); + listener.addCertificates('ListenerCertificateX', [importedCertificate(stack, 'cert3')]); // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::Listener', { @@ -862,6 +865,10 @@ describe('tests', () => { Key: 'deregistration_delay.timeout_seconds', Value: '30', }, + { + Key: 'stickiness.enabled', + Value: 'false', + }, ], }); }); @@ -883,6 +890,10 @@ describe('tests', () => { // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { TargetGroupAttributes: [ + { + Key: 'stickiness.enabled', + Value: 'false', + }, { Key: 'load_balancing.algorithm.type', Value: 'least_outstanding_requests', @@ -891,7 +902,7 @@ describe('tests', () => { }); }); - describe('Throws with bad fixed responses', () => { + describeDeprecated('Throws with bad fixed responses', () => { test('status code', () => { // GIVEN @@ -929,7 +940,7 @@ describe('tests', () => { }); }); - describe('Throws with bad redirect responses', () => { + describeDeprecated('Throws with bad redirect responses', () => { test('status code', () => { // GIVEN @@ -967,7 +978,7 @@ describe('tests', () => { }); }); - test('Throws when specifying both target groups and fixed response', () => { + test('Throws when specifying both target groups and an action', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -980,14 +991,12 @@ describe('tests', () => { // THEN expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { + action: elbv2.ListenerAction.fixedResponse(500), listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 })], - fixedResponse: { - statusCode: '500', - }, - })).toThrow(/'targetGroups,fixedResponse'.*/); + })).toThrow(/'action,targetGroups'.*/); }); test('Throws when specifying priority 0', () => { @@ -1003,12 +1012,10 @@ describe('tests', () => { // THEN expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { + action: elbv2.ListenerAction.fixedResponse(500), listener, priority: 0, - pathPattern: '/hello', - fixedResponse: { - statusCode: '500', - }, + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], })).toThrowError('Priority must have value greater than or equal to 1'); }); @@ -1027,14 +1034,14 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { listener, priority: new cdk.CfnParameter(stack, 'PriorityParam', { type: 'Number' }).valueAsNumber, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], fixedResponse: { statusCode: '500', }, })).not.toThrowError('Priority must have value greater than or equal to 1'); }); - test('Throws when specifying both target groups and redirect response', () => { + testDeprecated('Throws when specifying both target groups and redirect response', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -1049,7 +1056,7 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 })], redirectResponse: { statusCode: 'HTTP_301', @@ -1059,7 +1066,7 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule2', { listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup2', { vpc, port: 80 })], fixedResponse: { statusCode: '500', @@ -1099,7 +1106,10 @@ describe('tests', () => { // WHEN lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [ + importedCertificate(stack, 'cert1'), + importedCertificate(stack, 'cert2'), + ], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -1136,7 +1146,7 @@ describe('tests', () => { }); }); - test('Can add additional certificates via addCertificateArns to application listener', () => { + testDeprecated('Can add additional certificates via addCertificateArns to application listener', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1174,13 +1184,13 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); listener.addTargets('Target1', { priority: 10, - pathPatterns: ['/test/path/1', '/test/path/2'], + conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); // THEN @@ -1189,13 +1199,13 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/test/path/1', '/test/path/2'], + PathPatternConfig: { Values: ['/test/path/1', '/test/path/2'] }, }, ], }); }); - test('Cannot add pathPattern and pathPatterns to listener rule', () => { + testDeprecated('Cannot add pathPattern and pathPatterns to listener rule', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1204,7 +1214,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -1227,7 +1237,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group2], }); listener.addTargetGroups('TargetGroup1', { @@ -1291,7 +1301,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group3], }); listener.addTargetGroups('TargetGroup1', { @@ -1410,7 +1420,7 @@ describe('tests', () => { }); }); - test('Can exist together legacy style conditions and modern style conditions', () => { + testDeprecated('Can exist together legacy style conditions and modern style conditions', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1421,7 +1431,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group2], }); listener.addTargetGroups('TargetGroup1', { @@ -1464,14 +1474,14 @@ describe('tests', () => { const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'listener-arn', defaultPort: 443, - securityGroupId: 'security-group-id', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'security-group-id'), }); // WHEN listener.addTargetGroups('OtherTG', { targetGroups: [group], priority: 1, - pathPatterns: ['/path1', '/path2'], + conditions: [elbv2.ListenerCondition.pathPatterns(['/path1', '/path2'])], }); // THEN @@ -1480,13 +1490,13 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/path1', '/path2'], + PathPatternConfig: { Values: ['/path1', '/path2'] }, }, ], }); }); - test('not allowed to combine action specifiers when instantiating a Rule directly', () => { + testDeprecated('not allowed to combine action specifiers when instantiating a Rule directly', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1604,8 +1614,8 @@ describe('tests', () => { }); // WHEN - listener.addCertificateArns('certs', [ - 'arn:something', + listener.addCertificates('certs', [ + importedCertificate(stack, 'arn:something'), ]); // THEN @@ -1624,3 +1634,8 @@ class ResourceWithLBDependency extends cdk.CfnResource { this.node.addDependency(targetGroup.loadBalancerAttached); } } + +function importedCertificate(stack: cdk.Stack, + certificateArn = 'arn:aws:certificatemanager:123456789012:testregion:certificate/fd0b8392-3c0e-4704-81b6-8edf8612c852') { + return acm.Certificate.fromCertificateArn(stack, certificateArn, certificateArn); +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts index 93dd1c2d4ba33..03d17a207f16a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; @@ -34,7 +35,7 @@ describe('tests', () => { fixture.listener.addTargetGroups('Rule', { priority: 10, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [new elbv2.ApplicationTargetGroup(fixture.stack, 'TargetGroup2', { vpc: fixture.vpc, port: 8008, @@ -63,7 +64,7 @@ describe('tests', () => { }); fixture.listener.addTargetGroups('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [group], }); @@ -117,7 +118,7 @@ describe('tests', () => { listener: fixture.listener, targetGroups: [childGroup], priority: 100, - hostHeader: 'www.foo.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['www.foo.com'])], }); // THEN @@ -164,16 +165,18 @@ describe('tests', () => { fixture.listener.addTargets('default', { port: 80 }); // WHEN + const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SecurityGroup', + fixture.listener.connections.securityGroups[0].securityGroupId, + { allowAllOutbound: false }); const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'YetAnotherListener', { defaultPort: 8008, - securityGroupId: fixture.listener.connections.securityGroups[0].securityGroupId, listenerArn: fixture.listener.listenerArn, - securityGroupAllowsAllOutbound: false, + securityGroup, }); listener2.addTargetGroups('Default', { // Must be a non-default target priority: 10, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [group], }); @@ -181,7 +184,7 @@ describe('tests', () => { expectedImportedSGRules(stack2); }); - test('default port peering works on constructed listener', () => { + testDeprecated('default port peering works on constructed listener', () => { // GIVEN const fixture = new TestFixture(); fixture.listener.addTargets('Default', { port: 8080, targets: [new elbv2.InstanceTarget('i-12345')] }); @@ -206,11 +209,12 @@ describe('tests', () => { test('default port peering works on imported listener', () => { // GIVEN const stack2 = new cdk.Stack(); + const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SecurityGroup', 'imported-security-group-id'); // WHEN const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'YetAnotherListener', { listenerArn: 'listener-arn', - securityGroupId: 'imported-security-group-id', + securityGroup, defaultPort: 8080, }); listener2.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 078be310dd3f8..7eed0a4627011 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; @@ -32,7 +33,7 @@ describe('tests', () => { tg.addTarget(new FakeSelfRegisteringTarget(stack, 'Target', vpc)); }); - test('Cannot add direct target to imported TargetGroup', () => { + testDeprecated('Cannot add direct target to imported TargetGroup', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); @@ -46,7 +47,7 @@ describe('tests', () => { }).toThrow(/Cannot add a non-self registering target to an imported TargetGroup/); }); - test('HealthCheck fields set if provided', () => { + testDeprecated('HealthCheck fields set if provided', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); @@ -171,6 +172,10 @@ describe('tests', () => { // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { TargetGroupAttributes: [ + { + Key: 'stickiness.enabled', + Value: 'false', + }, { Key: 'load_balancing.algorithm.type', Value: 'least_outstanding_requests', diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.expected.json index 4358667cb43ed..780859bd7f314 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.expected.json @@ -120,15 +120,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -254,15 +254,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -529,6 +529,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "Targets": [ { "Id": "10.0.128.4" @@ -545,6 +551,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "Targets": [ { "Id": "10.0.128.5" diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb2.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb2.expected.json index ef8989719a37e..9aa4015d15f18 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb2.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb2.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -438,6 +438,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "Targets": [ { "Id": "10.0.128.4" @@ -454,6 +460,12 @@ "Properties": { "Port": 80, "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "stickiness.enabled", + "Value": "false" + } + ], "Targets": [ { "Id": "10.0.128.5" diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts index e2666d3056c9f..a5b3d5aea1e5c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts @@ -2,6 +2,7 @@ import { MatchStyle } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as elbv2 from '../../lib'; @@ -49,7 +50,7 @@ describe('tests', () => { }); }); - test('Can implicitly create target groups', () => { + testDeprecated('Can implicitly create target groups', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -81,7 +82,7 @@ describe('tests', () => { }); }); - test('implicitly created target group inherits protocol', () => { + testDeprecated('implicitly created target group inherits protocol', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -113,7 +114,7 @@ describe('tests', () => { }); }); - test('implicitly created target group but overrides inherited protocol', () => { + testDeprecated('implicitly created target group but overrides inherited protocol', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); diff --git a/packages/@aws-cdk/aws-elasticsearch/README.md b/packages/@aws-cdk/aws-elasticsearch/README.md index 3de87e175693c..6de168cf70f7b 100644 --- a/packages/@aws-cdk/aws-elasticsearch/README.md +++ b/packages/@aws-cdk/aws-elasticsearch/README.md @@ -29,21 +29,17 @@ Higher level constructs for Domain | ![Stable](https://img.shields.io/badge/stab Create a development cluster by simply specifying the version: ```ts -import * as es from '@aws-cdk/aws-elasticsearch'; - const devDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, + version: es.ElasticsearchVersion.V7_1, }); ``` To perform version upgrades without replacing the entire domain, specify the `enableVersionUpgrade` property. ```ts -import * as es from '@aws-cdk/aws-elasticsearch'; - const devDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_10, - enableVersionUpgrade: true // defaults to false + version: es.ElasticsearchVersion.V7_10, + enableVersionUpgrade: true, // defaults to false }); ``` @@ -51,22 +47,22 @@ Create a production grade cluster by also specifying things like capacity and az ```ts const prodDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - capacity: { - masterNodes: 5, - dataNodes: 20 - }, - ebs: { - volumeSize: 20 - }, - zoneAwareness: { - availabilityZoneCount: 3 - }, - logging: { - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: es.ElasticsearchVersion.V7_1, + capacity: { + masterNodes: 5, + dataNodes: 20, + }, + ebs: { + volumeSize: 20, + }, + zoneAwareness: { + availabilityZoneCount: 3, + }, + logging: { + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -93,7 +89,7 @@ You can also create it using the CDK, **but note that only the first application ```ts const slr = new iam.CfnServiceLinkedRole(this, 'ElasticSLR', { - awsServiceName: 'es.amazonaws.com' + awsServiceName: 'es.amazonaws.com', }); ``` @@ -104,7 +100,7 @@ This method accepts a domain endpoint of an already existing domain: ```ts const domainEndpoint = 'https://my-domain-jcjotrt6f7otem4sqcwbch3c4u.us-east-1.es.amazonaws.com'; -const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); +const domain = es.Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); ``` ## Permissions @@ -114,13 +110,14 @@ const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint) Helper methods also exist for managing access to the domain. ```ts -const lambda = new lambda.Function(this, 'Lambda', { /* ... */ }); +declare const fn: lambda.Function; +declare const domain: es.Domain; // Grant write access to the app-search index -domain.grantIndexWrite('app-search', lambda); +domain.grantIndexWrite('app-search', fn); // Grant read access to the 'app-search/_search' path -domain.grantPathRead('app-search/_search', lambda); +domain.grantPathRead('app-search/_search', fn); ``` ## Encryption @@ -129,15 +126,15 @@ The domain can also be created with encryption enabled: ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_4, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, + version: es.ElasticsearchVersion.V7_4, + ebs: { + volumeSize: 100, + volumeType: ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, }); ``` @@ -177,6 +174,7 @@ which security groups will be attached to the domain. By default, CDK will selec Helper methods exist to access common domain metrics for example: ```ts +declare const domain: es.Domain; const freeStorageSpace = domain.metricFreeStorageSpace(); const masterSysMemoryUtilization = domain.metric('MasterSysMemoryUtilization'); ``` @@ -190,15 +188,15 @@ be supplied or dynamically created if not supplied. ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, + version: es.ElasticsearchVersion.V7_1, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, }); const masterUserPassword = domain.masterUserPassword; @@ -228,8 +226,8 @@ stored in the AWS Secrets Manager as secret. The secret has the prefix ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - useUnsignedBasicAuth: true, + version: es.ElasticsearchVersion.V7_1, + useUnsignedBasicAuth: true, }); const masterUserPassword = domain.masterUserPassword; @@ -243,21 +241,21 @@ Audit logs can be enabled for a domain, but only when fine grained access contro ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, - logging: { - auditLogEnabled: true, - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: es.ElasticsearchVersion.V7_1, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, + logging: { + auditLogEnabled: true, + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -267,12 +265,12 @@ UltraWarm nodes can be enabled to provide a cost-effective way to store large am ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_10, - capacity: { - masterNodes: 2, - warmNodes: 2, - warmInstanceType: 'ultrawarm1.medium.elasticsearch', - }, + version: es.ElasticsearchVersion.V7_10, + capacity: { + masterNodes: 2, + warmNodes: 2, + warmInstanceType: 'ultrawarm1.medium.elasticsearch', + }, }); ``` @@ -281,11 +279,11 @@ const domain = new es.Domain(this, 'Domain', { Custom endpoints can be configured to reach the ES domain under a custom domain name. ```ts -new Domain(stack, 'Domain', { - version: ElasticsearchVersion.V7_7, - customEndpoint: { - domainName: 'search.example.com', - }, +new es.Domain(this, 'Domain', { + version: es.ElasticsearchVersion.V7_7, + customEndpoint: { + domainName: 'search.example.com', + }, }); ``` @@ -298,13 +296,13 @@ Additionally, an automatic CNAME-Record is created if a hosted zone is provided [Advanced options](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html#es-createdomain-configure-advanced-options) can used to configure additional options. ```ts -new Domain(stack, 'Domain', { - version: ElasticsearchVersion.V7_7, - advancedOptions: { - 'rest.action.multi.allow_explicit_index': 'false', - 'indices.fielddata.cache.size': '25', - 'indices.query.bool.max_clause_count': '2048', - }, +new es.Domain(this, 'Domain', { + version: es.ElasticsearchVersion.V7_7, + advancedOptions: { + 'rest.action.multi.allow_explicit_index': 'false', + 'indices.fielddata.cache.size': '25', + 'indices.query.bool.max_clause_count': '2048', + }, }); ``` diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts index 842071fa0ec68..14c41d3447395 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts @@ -952,7 +952,7 @@ abstract class DomainBase extends cdk.Resource implements IDomain { return new Metric({ namespace: 'AWS/ES', metricName, - dimensions: { + dimensionsMap: { DomainName: this.domainName, ClientId: this.stack.account, }, @@ -1212,7 +1212,8 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { */ public static fromDomainAttributes(scope: Construct, id: string, attrs: DomainAttributes): IDomain { const { domainArn, domainEndpoint } = attrs; - const domainName = cdk.Stack.of(scope).parseArn(domainArn).resourceName ?? extractNameFromEndpoint(domainEndpoint); + const domainName = cdk.Stack.of(scope).splitArn(domainArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName + ?? extractNameFromEndpoint(domainEndpoint); return new class extends DomainBase { public readonly domainArn = domainArn; @@ -1273,22 +1274,16 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { const defaultInstanceType = 'r5.large.elasticsearch'; const warmDefaultInstanceType = 'ultrawarm1.medium.elasticsearch'; - const dedicatedMasterType = - props.capacity?.masterNodeInstanceType?.toLowerCase() ?? - defaultInstanceType; + const dedicatedMasterType = initializeInstanceType(defaultInstanceType, props.capacity?.masterNodeInstanceType); const dedicatedMasterCount = props.capacity?.masterNodes ?? 0; - const dedicatedMasterEnabled = dedicatedMasterCount > 0; + const dedicatedMasterEnabled = cdk.Token.isUnresolved(dedicatedMasterCount) ? true : dedicatedMasterCount > 0; - const instanceType = - props.capacity?.dataNodeInstanceType?.toLowerCase() ?? - defaultInstanceType; + const instanceType = initializeInstanceType(defaultInstanceType, props.capacity?.dataNodeInstanceType); const instanceCount = props.capacity?.dataNodes ?? 1; - const warmType = - props.capacity?.warmInstanceType?.toLowerCase() ?? - warmDefaultInstanceType; + const warmType = initializeInstanceType(warmDefaultInstanceType, props.capacity?.warmInstanceType); const warmCount = props.capacity?.warmNodes ?? 0; - const warmEnabled = warmCount > 0; + const warmEnabled = cdk.Token.isUnresolved(warmCount) ? true : warmCount > 0; const availabilityZoneCount = props.zoneAwareness?.availabilityZoneCount ?? 2; @@ -1319,11 +1314,11 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { throw new Error('When providing vpc options you need to provide a subnet for each AZ you are using'); } - if ([dedicatedMasterType, instanceType, warmType].some(t => !t.endsWith('.elasticsearch'))) { + if ([dedicatedMasterType, instanceType, warmType].some(t => (!cdk.Token.isUnresolved(t) && !t.endsWith('.elasticsearch')))) { throw new Error('Master, data and UltraWarm node instance types must end with ".elasticsearch".'); } - if (!warmType.startsWith('ultrawarm')) { + if (!cdk.Token.isUnresolved(warmType) && !warmType.startsWith('ultrawarm')) { throw new Error('UltraWarm node instance type must start with "ultrawarm".'); } @@ -1540,9 +1535,9 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { if (props.logging?.auditLogEnabled) { this.auditLogGroup = props.logging.auditLogGroup ?? - new logs.LogGroup(this, 'AuditLogs', { - retention: logs.RetentionDays.ONE_MONTH, - }); + new logs.LogGroup(this, 'AuditLogs', { + retention: logs.RetentionDays.ONE_MONTH, + }); logGroups.push(this.auditLogGroup); }; @@ -1692,7 +1687,21 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { if (logGroupResourcePolicy) { this.domain.node.addDependency(logGroupResourcePolicy); } - if (props.domainName) { this.node.addMetadata('aws:cdk:hasPhysicalName', props.domainName); } + if (props.domainName) { + if (!cdk.Token.isUnresolved(props.domainName)) { + // https://docs.aws.amazon.com/opensearch-service/latest/developerguide/configuration-api.html#configuration-api-datatypes-domainname + if (!props.domainName.match(/^[a-z0-9\-]+$/)) { + throw new Error(`Invalid domainName '${props.domainName}'. Valid characters are a-z (lowercase only), 0-9, and – (hyphen).`); + } + if (props.domainName.length < 3 || props.domainName.length > 28) { + throw new Error(`Invalid domainName '${props.domainName}'. It must be between 3 and 28 characters`); + } + if (props.domainName[0] < 'a' || props.domainName[0] > 'z') { + throw new Error(`Invalid domainName '${props.domainName}'. It must start with a lowercase letter`); + } + } + this.node.addMetadata('aws:cdk:hasPhysicalName', props.domainName); + } this.domainName = this.getResourceNameAttribute(this.domain.ref); @@ -1808,3 +1817,18 @@ function selectSubnets(vpc: ec2.IVpc, vpcSubnets: ec2.SubnetSelection[]): ec2.IS } return selected; } + +/** + * Initializes an instance type. + * + * @param defaultInstanceType Default instance type which is used if no instance type is provided + * @param instanceType Instance type + * @returns Instance type in lowercase (if provided) or default instance type + */ +function initializeInstanceType(defaultInstanceType: string, instanceType?: string): string { + if (instanceType) { + return cdk.Token.isUnresolved(instanceType) ? instanceType : instanceType.toLowerCase(); + } else { + return defaultInstanceType; + } +} diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts b/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts index bb5a530211719..a79c716831b72 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts @@ -44,7 +44,7 @@ export class ElasticsearchAccessPolicy extends cr.AwsCustomResource { AccessPolicies: JSON.stringify(policyDocument.toJSON()), }, // this is needed to limit the response body, otherwise it exceeds the CFN 4k limit - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: cr.PhysicalResourceId.of(`${props.domainName}AccessPolicy`), }, policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: [props.domainArn] }), diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index bed6ead349ed2..0ddd787d22acb 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,7 +84,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d0c3bec94ce39 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as es from '@aws-cdk/aws-elasticsearch'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts b/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts index 6966882c55549..a6291f0af7685 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts +++ b/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts @@ -8,7 +8,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as route53 from '@aws-cdk/aws-route53'; -import { App, Stack, Duration, SecretValue, CfnParameter } from '@aws-cdk/core'; +import { App, Stack, Duration, SecretValue, CfnParameter, Token } from '@aws-cdk/core'; import { Domain, ElasticsearchVersion } from '../lib'; let app: App; @@ -246,6 +246,45 @@ describe('UltraWarm instances', () => { }); +test('can use tokens in capacity configuration', () => { + new Domain(stack, 'Domain', { + version: ElasticsearchVersion.V7_10, + capacity: { + dataNodeInstanceType: Token.asString({ Ref: 'dataNodeInstanceType' }), + dataNodes: Token.asNumber({ Ref: 'dataNodes' }), + masterNodeInstanceType: Token.asString({ Ref: 'masterNodeInstanceType' }), + masterNodes: Token.asNumber({ Ref: 'masterNodes' }), + warmInstanceType: Token.asString({ Ref: 'warmInstanceType' }), + warmNodes: Token.asNumber({ Ref: 'warmNodes' }), + }, + }); + + expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', { + ElasticsearchClusterConfig: { + InstanceCount: { + Ref: 'dataNodes', + }, + InstanceType: { + Ref: 'dataNodeInstanceType', + }, + DedicatedMasterEnabled: true, + DedicatedMasterCount: { + Ref: 'masterNodes', + }, + DedicatedMasterType: { + Ref: 'masterNodeInstanceType', + }, + WarmEnabled: true, + WarmCount: { + Ref: 'warmNodes', + }, + WarmType: { + Ref: 'warmInstanceType', + }, + }, + }); +}); + describe('log groups', () => { test('slowSearchLogEnabled should create a custom log group', () => { @@ -1317,6 +1356,21 @@ describe('custom error responses', () => { })).toThrow(/Unknown Elasticsearch version: 5\.4/); }); + test('error when invalid domain name is given', () => { + expect(() => new Domain(stack, 'Domain1', { + version: ElasticsearchVersion.V7_4, + domainName: 'InvalidName', + })).toThrow(/Valid characters are a-z/); + expect(() => new Domain(stack, 'Domain2', { + version: ElasticsearchVersion.V7_4, + domainName: 'a'.repeat(29), + })).toThrow(/It must be between 3 and 28 characters/); + expect(() => new Domain(stack, 'Domain3', { + version: ElasticsearchVersion.V7_4, + domainName: '123domain', + })).toThrow(/It must start with a lowercase letter/); + }); + test('error when error log publishing is enabled for elasticsearch version < 5.1', () => { const error = /Error logs publishing requires Elasticsearch version 5.1 or later/; expect(() => new Domain(stack, 'Domain1', { diff --git a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts index ebb83e123ed84..53d6afe3b2cb0 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts +++ b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts @@ -40,7 +40,7 @@ test('minimal example renders correctly', () => { DomainName: 'TestDomain', AccessPolicies: '{"Statement":[{"Action":"es:ESHttp*","Effect":"Allow","Principal":{"AWS":"*"},"Resource":"test:arn"}],"Version":"2012-10-17"}', }, - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: { id: 'TestDomainAccessPolicy' }, }), Update: JSON.stringify({ @@ -50,7 +50,7 @@ test('minimal example renders correctly', () => { DomainName: 'TestDomain', AccessPolicies: '{"Statement":[{"Action":"es:ESHttp*","Effect":"Allow","Principal":{"AWS":"*"},"Resource":"test:arn"}],"Version":"2012-10-17"}', }, - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: { id: 'TestDomainAccessPolicy' }, }), }); diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json index fc046ff8065b2..1883358d70fe7 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json @@ -95,15 +95,15 @@ "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VpcPublicSubnet2NATGateway9182C01D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet2EIP3C605A87", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "VpcPublicSubnet3NATGateway7640CD1D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet3EIP3A666A23", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json index 33066ad9091b6..636acfc906b05 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json @@ -252,7 +252,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -276,7 +276,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -359,7 +359,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924S3BucketA716C641" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -372,7 +372,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924S3VersionKey2B40A946" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -385,7 +385,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924S3VersionKey2B40A946" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -412,17 +412,17 @@ } }, "Parameters": { - "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924S3BucketA716C641": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"f4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924S3VersionKey2B40A946": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"f4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParametersf4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924ArtifactHash2B031F57": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"f4b39c228007db80daf4497318957f3b455415dce70fdbb7aeb6151a0b6da924\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json index e976e19e502a2..73c6624609fd4 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json @@ -219,7 +219,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain19FCBCB91" }, @@ -243,7 +243,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain19FCBCB91" }, @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddS3Bucket292EB571" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddS3VersionKeyCE9A5F79" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddS3VersionKeyCE9A5F79" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -565,7 +565,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain2644FE48C" }, @@ -589,7 +589,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain2644FE48C" }, @@ -608,17 +608,17 @@ } }, "Parameters": { - "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddS3Bucket292EB571": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"f2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3dd\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddS3VersionKeyCE9A5F79": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"f2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3dd\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParametersf2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3ddArtifactHash86854188": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"f2b7671fc0b80f63e3c94441727cbff799bb0f4991339d990d7b4c419e1ab3dd\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json index 7ff999080fed6..2793a8beb231f 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json @@ -8,7 +8,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"admin\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "Domain66AC69E0": { "Type": "AWS::Elasticsearch::Domain", @@ -115,7 +117,7 @@ "Arn" ] }, - "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -138,7 +140,7 @@ "Arn" ] }, - "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -191,7 +193,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cS3BucketD609D0D9" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -204,7 +206,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cS3VersionKey77CF589B" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -217,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cS3VersionKey77CF589B" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -243,17 +245,17 @@ } }, "Parameters": { - "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cS3BucketD609D0D9": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02c\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cS3VersionKey77CF589B": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02c\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02cArtifactHash86CFA15D": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"4600faecd25ab407ff0a9d16f935c93062aaea5d415e97046bb8befe6c8ec02c\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ 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 a4983f2f01503..eecd4c1b8d633 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-emrcontainers/package.json b/packages/@aws-cdk/aws-emrcontainers/package.json index 4efce3b89f65d..22fc0ab192946 100644 --- a/packages/@aws-cdk/aws-emrcontainers/package.json +++ b/packages/@aws-cdk/aws-emrcontainers/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 994c24ab8a0a5..1282cee012c5e 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -28,14 +28,14 @@ Currently supported are: * Put a record to a Kinesis stream * [Log an event into a LogGroup](#log-an-event-into-a-loggroup) * Put a record to a Kinesis Data Firehose stream -* Put an event on an EventBridge bus +* [Put an event on an EventBridge bus](#put-an-event-on-an-eventbridge-bus) See the README of the `@aws-cdk/aws-events` library for more information on EventBridge. ## Event retry policy and using dead-letter queues -The Codebuild, CodePipeline, Lambda, StepFunctions and LogGroup targets support attaching a [dead letter queue and setting retry policies](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html). See the [lambda example](#invoke-a-lambda-function). +The Codebuild, CodePipeline, Lambda, StepFunctions, LogGroup and SQSQueue targets support attaching a [dead letter queue and setting retry policies](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html). See the [lambda example](#invoke-a-lambda-function). Use [escape hatches](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html) for the other target types. ## Invoke a Lambda function @@ -65,7 +65,7 @@ const queue = new sqs.Queue(this, 'Queue'); rule.addTarget(new targets.LambdaFunction(fn, { deadLetterQueue: queue, // Optional: add a dead letter queue - maxEventAge: cdk.Duration.hours(2), // Otional: set the maxEventAge retry policy + maxEventAge: cdk.Duration.hours(2), // Optional: set the maxEventAge retry policy retryAttempts: 2, // Optional: set the max number of retry attempts })); ``` @@ -266,3 +266,23 @@ rule.addTarget( } ), ) ``` + +## Put an event on an EventBridge bus + +Use the `EventBus` target to route event to a different EventBus. + +The code snippet below creates the scheduled event rule that route events to an imported event bus. + +```ts +const rule = new events.Rule(this, 'Rule', { + schedule: events.Schedule.expression('rate(1 minute)'), +}); + +rule.addTarget(new targets.EventBus( + events.EventBus.fromEventBusArn( + this, + 'External', + `arn:aws:events:eu-west-1:999999999999:event-bus/test-bus`, + ), +)); +``` diff --git a/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts b/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts index 8237b8cdd7993..7026273ef5330 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts @@ -1,9 +1,12 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { singletonEventRole } from './util'; +import * as sqs from '@aws-cdk/aws-sqs'; +import { singletonEventRole, addToDeadLetterQueueResourcePolicy } from './util'; /** * Configuration properties of an Event Bus event + * + * Cannot extend TargetBaseProps. Retry policy is not supported for Event bus targets. */ export interface EventBusProps { /** @@ -12,25 +15,39 @@ export interface EventBusProps { * @default a new role is created. */ readonly role?: iam.IRole; + + /** + * The SQS queue to be used as deadLetterQueue. + * Check out the [considerations for using a dead-letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html#dlq-considerations). + * + * The events not successfully delivered are automatically retried for a specified period of time, + * depending on the retry policy of the target. + * If an event is not delivered before all retry attempts are exhausted, it will be sent to the dead letter queue. + * + * @default - no dead-letter queue + */ + readonly deadLetterQueue?: sqs.IQueue; } /** * Notify an existing Event Bus of an event */ export class EventBus implements events.IRuleTarget { - private readonly role?: iam.IRole; - - constructor(private readonly eventBus: events.IEventBus, props: EventBusProps = {}) { - this.role = props.role; - } + constructor(private readonly eventBus: events.IEventBus, private readonly props: EventBusProps = {}) { } bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { - if (this.role) { - this.role.addToPrincipalPolicy(this.putEventStatement()); + if (this.props.role) { + this.props.role.addToPrincipalPolicy(this.putEventStatement()); } - const role = this.role ?? singletonEventRole(rule, [this.putEventStatement()]); + const role = this.props.role ?? singletonEventRole(rule, [this.putEventStatement()]); + + if (this.props.deadLetterQueue) { + addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); + } + return { arn: this.eventBus.eventBusArn, + deadLetterConfig: this.props.deadLetterQueue ? { arn: this.props.deadLetterQueue?.queueArn } : undefined, role, }; } diff --git a/packages/@aws-cdk/aws-events-targets/lib/log-group.ts b/packages/@aws-cdk/aws-events-targets/lib/log-group.ts index 688cfc3773c13..18c11cad862db 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/log-group.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/log-group.ts @@ -2,6 +2,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { LogGroupResourcePolicy } from './log-group-resource-policy'; import { TargetBaseProps, bindBaseTargetConfig } from './util'; @@ -30,7 +31,7 @@ export class CloudWatchLogGroup implements events.IRuleTarget { */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { // Use a custom resource to set the log group resource policy since it is not supported by CDK and cfn. - const resourcePolicyId = `EventsLogGroupPolicy${_rule.node.uniqueId}`; + const resourcePolicyId = `EventsLogGroupPolicy${cdk.Names.nodeUniqueId(_rule.node)}`; const logGroupStack = cdk.Stack.of(this.logGroup); @@ -50,7 +51,7 @@ export class CloudWatchLogGroup implements events.IRuleTarget { arn: logGroupStack.formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.logGroup.logGroupName, }), input: this.props.event, diff --git a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts index 43fb9b8ed15d0..465f0355516cf 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/sqs.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/sqs.ts @@ -1,11 +1,12 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as sqs from '@aws-cdk/aws-sqs'; +import { addToDeadLetterQueueResourcePolicy, TargetBaseProps, bindBaseTargetConfig } from './util'; /** * Customize the SQS Queue Event Target */ -export interface SqsQueueProps { +export interface SqsQueueProps extends TargetBaseProps { /** * Message Group ID for messages sent to this queue @@ -24,7 +25,6 @@ export interface SqsQueueProps { * @default the entire EventBridge event */ readonly message?: events.RuleTargetInput; - } /** @@ -62,7 +62,12 @@ export class SqsQueue implements events.IRuleTarget { // deduplicated automatically this.queue.grantSendMessages(new iam.ServicePrincipal('events.amazonaws.com', principalOpts)); + if (this.props.deadLetterQueue) { + addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); + } + return { + ...bindBaseTargetConfig(this.props), arn: this.queue.queueArn, input: this.props.message, targetResource: this.queue, diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index c2e0d3762085f..1184651b4e9a3 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,13 +86,14 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", @@ -108,6 +116,7 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts index 1b084a0ec35b1..eb17b98622369 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts @@ -1,20 +1,31 @@ import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; -test('Can use EC2 taskdef as EventRule target', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { +let stack: cdk.Stack; +let vpc: ec2.Vpc; +let cluster: ecs.Cluster; + +beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), instanceType: new ec2.InstanceType('t2.micro'), }); + const provider = new ecs.AsgCapacityProvider(stack, 'AsgCapacityProvider', { autoScalingGroup }); + cluster.addAsgCapacityProvider(provider); +}); +test('Can use EC2 taskdef as EventRule target', () => { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -61,13 +72,6 @@ test('Can use EC2 taskdef as EventRule target', () => { test('Throws error for lacking of taskRole ' + 'when importing from an EC2 task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); - const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionArn(stack, 'TaskDef', 'importedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -91,13 +95,6 @@ test('Throws error for lacking of taskRole ' + test('Can import an EC2 task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); - const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.BRIDGE, @@ -146,10 +143,6 @@ test('Can import an EC2 task definition from task definition attributes as Event test('Throws error for lacking of taskRole ' + 'when importing from a Fargate task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -173,10 +166,6 @@ test('Throws error for lacking of taskRole ' + test('Can import a Fargate task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.AWS_VPC, @@ -225,10 +214,6 @@ test('Can import a Fargate task definition from task definition attributes as Ev test('Throws error for lacking of taskRole ' + 'when importing from a task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -252,10 +237,6 @@ test('Throws error for lacking of taskRole ' + test('Can import a Task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.AWS_VPC, @@ -303,10 +284,6 @@ test('Can import a Task definition from task definition attributes as EventRule test('Can use Fargate taskdef as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -329,7 +306,7 @@ test('Can use Fargate taskdef as EventRule target', () => { rule.addTarget(target); // THEN - expect(target.securityGroup).toBeDefined(); // Generated security groups should be accessible. + expect(target.securityGroups?.length).toBeGreaterThan(0); // Generated security groups should be accessible. expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ { @@ -372,10 +349,6 @@ test('Can use Fargate taskdef as EventRule target', () => { test('Can use same fargate taskdef with multiple rules', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -404,10 +377,6 @@ test('Can use same fargate taskdef with multiple rules', () => { test('Can use same fargate taskdef multiple times in a rule', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -438,15 +407,14 @@ test('Can use same fargate taskdef multiple times in a rule', () => { test('Isolated subnet does not have AssignPublicIp=true', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { + vpc = new ec2.Vpc(stack, 'Vpc2', { maxAzs: 1, subnetConfiguration: [{ subnetType: ec2.SubnetType.ISOLATED, name: 'Isolated', }], }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster = new ecs.Cluster(stack, 'EcsCluster2', { vpc }); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { @@ -473,7 +441,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ { - Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + Arn: { 'Fn::GetAtt': ['EcsCluster2F191ADEC', 'Arn'] }, EcsParameters: { TaskCount: 1, TaskDefinitionArn: { Ref: 'TaskDef54694570' }, @@ -482,7 +450,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { AwsVpcConfiguration: { Subnets: [ { - Ref: 'VpcIsolatedSubnet1SubnetE48C5737', + Ref: 'Vpc2IsolatedSubnet1SubnetB1A200D6', }, ], AssignPublicIp: 'DISABLED', @@ -505,12 +473,8 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { }); }); -test('throws an error if both securityGroup and securityGroups is specified', () => { +testDeprecated('throws an error if both securityGroup and securityGroups is specified', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -539,10 +503,6 @@ test('throws an error if both securityGroup and securityGroups is specified', () test('uses multiple security groups', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -600,9 +560,6 @@ test('uses multiple security groups', () => { test('uses existing IAM role', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const role = new iam.Role(stack, 'CustomIamRole', { assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), }); @@ -649,9 +606,6 @@ test('uses existing IAM role', () => { test('uses the specific fargate platform version', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const platformVersion = ecs.FargatePlatformVersion.VERSION1_4; const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); diff --git a/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts index 8d188f1550e8b..a6552a10d7a57 100644 --- a/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import * as sqs from '@aws-cdk/aws-sqs'; import { Stack } from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -90,4 +91,79 @@ test('with supplied role', () => { Ref: 'Role1ABCC5F0', }], }); -}); \ No newline at end of file +}); + +test('with a Dead Letter Queue specified', () => { + const stack = new Stack(); + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + const queue = new sqs.Queue(stack, 'Queue'); + + rule.addTarget(new targets.EventBus( + events.EventBus.fromEventBusArn( + stack, + 'External', + 'arn:aws:events:us-east-1:123456789012:default', + ), + { deadLetterQueue: queue }, + )); + + expect(stack).toHaveResource('AWS::Events::Rule', { + Targets: [{ + Arn: 'arn:aws:events:us-east-1:123456789012:default', + Id: 'Target0', + RoleArn: { + 'Fn::GetAtt': [ + 'RuleEventsRoleC51A4248', + 'Arn', + ], + }, + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': [ + 'Queue4A7E3555', + 'Arn', + ], + }, + }, + }], + }); + + expect(stack).toHaveResource('AWS::SQS::QueuePolicy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Condition: { + ArnEquals: { + 'aws:SourceArn': { + 'Fn::GetAtt': [ + 'Rule4C995B7F', + 'Arn', + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: 'events.amazonaws.com', + }, + Resource: { + 'Fn::GetAtt': [ + 'Queue4A7E3555', + 'Arn', + ], + }, + Sid: 'AllowEventRuleRule', + }, + ], + Version: '2012-10-17', + }, + Queues: [ + { + Ref: 'Queue4A7E3555', + }, + ], + }); +}); diff --git a/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.expected.json b/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.expected.json index 632ddf1767598..a10603f0b03b5 100644 --- a/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.expected.json @@ -19,6 +19,14 @@ ] ] }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "Queue4A7E3555", + "Arn" + ] + } + }, "Id": "Target0", "RoleArn": { "Fn::GetAtt": [ @@ -78,6 +86,50 @@ } ] } + }, + "Queue4A7E3555": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "QueuePolicy25439813": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "Rule4C995B7F", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "Queue4A7E3555", + "Arn" + ] + }, + "Sid": "AllowEventRuleeventsourcestackRuleFCA41174" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "Queue4A7E3555" + } + ] + } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.ts b/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.ts index c0ec2ea421b85..5a102ea3cba6c 100644 --- a/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.ts +++ b/packages/@aws-cdk/aws-events-targets/test/event-bus/integ.event-bus.ts @@ -1,5 +1,6 @@ /// !cdk-integ pragma:ignore-assets import * as events from '@aws-cdk/aws-events'; +import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -12,12 +13,18 @@ class EventSourceStack extends cdk.Stack { const rule = new events.Rule(this, 'Rule', { schedule: events.Schedule.expression('rate(1 minute)'), }); + + const queue = new sqs.Queue(this, 'Queue'); + rule.addTarget(new targets.EventBus( events.EventBus.fromEventBusArn( this, 'External', `arn:aws:events:${this.region}:999999999999:event-bus/test-bus`, ), + { + deadLetterQueue: queue, + }, )); } } diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index b1222b9e178f5..d23d7eb3feae0 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -322,7 +322,7 @@ test('must display a warning when using a Dead Letter Queue from another account expect(stack1).not.toHaveResource('AWS::SQS::QueuePolicy'); let rule = stack1.node.children.find(child => child instanceof events.Rule); - expect(rule?.node.metadata[0].data).toMatch(/Cannot add a resource policy to your dead letter queue associated with rule .* because the queue is in a different account\. You must add the resource policy manually to the dead letter queue in account 222222222222\./); + expect(rule?.node.metadataEntry[0].data).toMatch(/Cannot add a resource policy to your dead letter queue associated with rule .* because the queue is in a different account\. You must add the resource policy manually to the dead letter queue in account 222222222222\./); }); diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json index eb2a7dd26ef5f..f35a7a93b9e42 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.expected.json @@ -61,6 +61,14 @@ "Arn" ] }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + } + }, "Id": "Target0" } ] @@ -110,6 +118,50 @@ } ] } + }, + "MyDeadLetterQueueD997968A": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyDeadLetterQueuePolicyCC35D52C": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyRuleA44AB831", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + }, + "Sid": "AllowEventRuleawscdksqseventtargetMyRule0027A8F4" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "MyDeadLetterQueueD997968A" + } + ] + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts index b2b8fb334bff6..f2375dd7c2a37 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts @@ -24,6 +24,10 @@ const queue = new sqs.Queue(stack, 'MyQueue', { encryptionMasterKey: key, }); -event.addTarget(new targets.SqsQueue(queue)); +const deadLetterQueue = new sqs.Queue(stack, 'MyDeadLetterQueue'); + +event.addTarget(new targets.SqsQueue(queue, { + deadLetterQueue, +})); app.synth(); diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts b/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts index 8893a8820f37e..ad2a5296714e6 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts @@ -180,3 +180,75 @@ test('fifo queues are synthesized correctly', () => { ], })); }); + +test('dead letter queue is configured correctly', () => { + const stack = new Stack(); + const queue = new sqs.Queue(stack, 'MyQueue', { fifo: true }); + const deadLetterQueue = new sqs.Queue(stack, 'MyDeadLetterQueue'); + const rule = new events.Rule(stack, 'MyRule', { + schedule: events.Schedule.rate(Duration.hours(1)), + }); + + // WHEN + rule.addTarget(new targets.SqsQueue(queue, { + deadLetterQueue, + })); + + cdkExpect(stack).to(haveResource('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 hour)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + Id: 'Target0', + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': [ + 'MyDeadLetterQueueD997968A', + 'Arn', + ], + }, + }, + }, + ], + })); +}); + +test('specifying retry policy', () => { + const stack = new Stack(); + const queue = new sqs.Queue(stack, 'MyQueue', { fifo: true }); + const rule = new events.Rule(stack, 'MyRule', { + schedule: events.Schedule.rate(Duration.hours(1)), + }); + + // WHEN + rule.addTarget(new targets.SqsQueue(queue, { + retryAttempts: 2, + maxEventAge: Duration.hours(2), + })); + + cdkExpect(stack).to(haveResource('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 hour)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + Id: 'Target0', + RetryPolicy: { + MaximumEventAgeInSeconds: 7200, + MaximumRetryAttempts: 2, + }, + }, + ], + })); +}); diff --git a/packages/@aws-cdk/aws-events/README.md b/packages/@aws-cdk/aws-events/README.md index 0288407ee094f..13bd483ca54cb 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -59,6 +59,9 @@ For example, to define an rule that triggers a CodeBuild project build when a commit is pushed to the "master" branch of a CodeCommit repository: ```ts +declare const repo: codecommit.Repository; +declare const project: codebuild.Project; + const onCommitRule = repo.onCommit('OnCommit', { target: new targets.CodeBuildProject(project), branches: ['master'] @@ -73,6 +76,9 @@ topic target which formats a human-readable message for the commit. For example, this adds an SNS topic as a target: ```ts +declare const onCommitRule: events.Rule; +declare const topic: sns.Topic; + onCommitRule.addTarget(new targets.SnsTopic(topic, { message: events.RuleTargetInput.fromText( `A commit was pushed to the repository ${codecommit.ReferenceEvent.repositoryName} on branch ${codecommit.ReferenceEvent.referenceName}` @@ -83,6 +89,9 @@ onCommitRule.addTarget(new targets.SnsTopic(topic, { Or using an Object: ```ts +declare const onCommitRule: events.Rule; +declare const topic: sns.Topic; + onCommitRule.addTarget(new targets.SnsTopic(topic, { message: events.RuleTargetInput.fromObject( { @@ -99,10 +108,15 @@ Rate must be specified in minutes, hours or days. The following example runs a task every day at 4am: -```ts +```ts fixture=basic import { Rule, Schedule } from '@aws-cdk/aws-events'; import { EcsTask } from '@aws-cdk/aws-events-targets'; -... +import { Cluster, TaskDefinition } from '@aws-cdk/aws-ecs'; +import { Role } from '@aws-cdk/aws-iam'; + +declare const cluster: Cluster; +declare const taskDefinition: TaskDefinition; +declare const role: Role; const ecsTaskTarget = new EcsTask({ cluster, taskDefinition, role }); @@ -115,8 +129,12 @@ new Rule(this, 'ScheduleRule', { If you want to specify Fargate platform version, set `platformVersion` in EcsTask's props like the following example: ```ts +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +declare const role: iam.Role; + const platformVersion = ecs.FargatePlatformVersion.VERSION1_4; -const ecsTaskTarget = new EcsTask({ cluster, taskDefinition, role, platformVersion }); +const ecsTaskTarget = new targets.EcsTask({ cluster, taskDefinition, role, platformVersion }); ``` ## Event Targets @@ -140,7 +158,7 @@ The following targets are supported: It's possible to have the source of the event and a target in separate AWS accounts and regions: -```ts +```ts nofixture import { App, Stack } from '@aws-cdk/core'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codecommit from '@aws-cdk/aws-codecommit'; @@ -148,9 +166,12 @@ import * as targets from '@aws-cdk/aws-events-targets'; const app = new App(); +const account1 = '11111111111'; +const account2 = '22222222222'; + const stack1 = new Stack(app, 'Stack1', { env: { account: account1, region: 'us-west-1' } }); const repo = new codecommit.Repository(stack1, 'Repository', { - // ... + repositoryName: 'myrepository', }); const stack2 = new Stack(app, 'Stack2', { env: { account: account2, region: 'us-east-1' } }); @@ -179,11 +200,7 @@ For more information, see the It is possible to archive all or some events sent to an event bus. It is then possible to [replay these events](https://aws.amazon.com/blogs/aws/new-archive-and-replay-events-with-amazon-eventbridge/). ```ts -import * as cdk from '@aws-cdk/core'; - -const stack = new stack(); - -const bus = new EventBus(stack, 'bus', { +const bus = new events.EventBus(this, 'bus', { eventBusName: 'MyCustomEventBus' }); @@ -191,9 +208,9 @@ bus.archive('MyArchive', { archiveName: 'MyCustomEventBusArchive', description: 'MyCustomerEventBus Archive', eventPattern: { - account: [stack.account], + account: [Stack.of(this).account], }, - retention: cdk.Duration.days(365), + retention: Duration.days(365), }); ``` @@ -205,7 +222,9 @@ or `EventBus.fromEventBusName` factory method. Then, you can use the `grantPutEventsTo` method to grant `event:PutEvents` to the eventBus. ```ts -const eventBus = EventBus.fromEventBusArn(this, 'ImportedEventBus', 'arn:aws:events:us-east-1:111111111:event-bus/my-event-bus'); +declare const lambdaFunction: lambda.Function; + +const eventBus = events.EventBus.fromEventBusArn(this, 'ImportedEventBus', 'arn:aws:events:us-east-1:111111111:event-bus/my-event-bus'); // now you can just call methods on the eventbus eventBus.grantPutEventsTo(lambdaFunction); diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index df0859b3d3259..3bf8768e54695 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, Names, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Archive, BaseArchiveProps } from './archive'; import { CfnEventBus } from './events.generated'; @@ -169,7 +169,7 @@ export class EventBus extends EventBusBase { * @param eventBusArn ARN of imported event bus */ public static fromEventBusArn(scope: Construct, id: string, eventBusArn: string): IEventBus { - const parts = Stack.of(scope).parseArn(eventBusArn); + const parts = Stack.of(scope).splitArn(eventBusArn, ArnFormat.SLASH_RESOURCE_NAME); return new ImportedEventBus(scope, id, { eventBusArn: eventBusArn, @@ -276,6 +276,8 @@ export class EventBus extends EventBusBase { ); } return { eventBusName: eventSourceName, eventSourceName }; + } else { + return { eventBusName: props.eventBusName }; } } return { eventBusName: defaultEventBusName }; @@ -311,7 +313,7 @@ export class EventBus extends EventBusBase { super(scope, id, { physicalName: eventBusName }); const eventBus = new CfnEventBus(this, 'Resource', { - name: eventBusName, + name: this.physicalName, eventSourceName, }); @@ -333,7 +335,7 @@ class ImportedEventBus extends EventBusBase { public readonly eventBusPolicy: string; public readonly eventSourceName?: string; constructor(scope: Construct, id: string, attrs: EventBusAttributes) { - const arnParts = Stack.of(scope).parseArn(attrs.eventBusArn); + const arnParts = Stack.of(scope).splitArn(attrs.eventBusArn, ArnFormat.SLASH_RESOURCE_NAME); super(scope, id, { account: arnParts.account, region: arnParts.region, diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 47f5d05201a53..19f84e8cc479c 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,5 +1,5 @@ import { IRole, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { App, IResource, Lazy, Names, Resource, Stack, Token, PhysicalName } from '@aws-cdk/core'; +import { App, IResource, Lazy, Names, Resource, Stack, Token, PhysicalName, ArnFormat } from '@aws-cdk/core'; import { Node, Construct } from 'constructs'; import { IEventBus } from './event-bus'; import { EventPattern } from './event-pattern'; @@ -98,7 +98,7 @@ export class Rule extends Resource implements IRule { * @param eventRuleArn Event Rule ARN (i.e. arn:aws:events:::rule/MyScheduledRule). */ public static fromEventRuleArn(scope: Construct, id: string, eventRuleArn: string): IRule { - const parts = Stack.of(scope).parseArn(eventRuleArn); + const parts = Stack.of(scope).splitArn(eventRuleArn, ArnFormat.SLASH_RESOURCE_NAME); class Import extends Resource implements IRule { public ruleArn = eventRuleArn; diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index e2cefb728f130..7159bada50ee2 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,8 +85,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-events/rosetta/basic.ts-fixture b/packages/@aws-cdk/aws-events/rosetta/basic.ts-fixture new file mode 100644 index 0000000000000..0cc6d1104d521 --- /dev/null +++ b/packages/@aws-cdk/aws-events/rosetta/basic.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack, Duration } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + diff --git a/packages/monocdk/rosetta/withRepoAndTopic.ts-fixture b/packages/@aws-cdk/aws-events/rosetta/default.ts-fixture similarity index 60% rename from packages/monocdk/rosetta/withRepoAndTopic.ts-fixture rename to packages/@aws-cdk/aws-events/rosetta/default.ts-fixture index 30c1f29cc331b..7cb7c6f81aec6 100644 --- a/packages/monocdk/rosetta/withRepoAndTopic.ts-fixture +++ b/packages/@aws-cdk/aws-events/rosetta/default.ts-fixture @@ -1,23 +1,20 @@ // Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; +import { Stack, Duration } from '@aws-cdk/core'; import * as events from '@aws-cdk/aws-events'; -import * as sns from '@aws-cdk/aws-sns'; +import * as targets from '@aws-cdk/aws-events-targets'; import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as sns from '@aws-cdk/aws-sns'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const topic = new sns.Topic(this, 'MyTopic'); - /// here } } + diff --git a/packages/@aws-cdk/aws-events/test/event-bus.test.ts b/packages/@aws-cdk/aws-events/test/event-bus.test.ts index b4384255ea7b4..f20aa3e81af74 100644 --- a/packages/@aws-cdk/aws-events/test/event-bus.test.ts +++ b/packages/@aws-cdk/aws-events/test/event-bus.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; -import { Aws, CfnResource, Stack, Arn } from '@aws-cdk/core'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; +import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '@aws-cdk/core'; import { EventBus } from '../lib'; describe('event bus', () => { @@ -287,7 +288,7 @@ describe('event bus', () => { }); - test('can grant PutEvents', () => { + testDeprecated('can grant PutEvents', () => { // GIVEN const stack = new Stack(); const role = new iam.Role(stack, 'Role', { @@ -515,4 +516,32 @@ describe('event bus', () => { }); + test('cross account event bus uses generated physical name', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const stack2 = new Stack(app, 'Stack2', { + env: { + account: '22222222222', + region: 'us-east-1', + }, + }); + + // WHEN + const bus1 = new EventBus(stack1, 'Bus', { + eventBusName: PhysicalName.GENERATE_IF_NEEDED, + }); + + new CfnOutput(stack2, 'BusName', { value: bus1.eventBusName }); + + // THEN + expect(stack1).toHaveResource('AWS::Events::EventBus', { + Name: 'stack1stack1busca19bdf8ab2e51b62a5a', + }); + }); }); diff --git a/packages/@aws-cdk/aws-eventschemas/package.json b/packages/@aws-cdk/aws-eventschemas/package.json index 817e1564b28d1..75146a5702dcf 100644 --- a/packages/@aws-cdk/aws-eventschemas/package.json +++ b/packages/@aws-cdk/aws-eventschemas/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-finspace/package.json b/packages/@aws-cdk/aws-finspace/package.json index bb869ef40b45d..5def58d6b4891 100644 --- a/packages/@aws-cdk/aws-finspace/package.json +++ b/packages/@aws-cdk/aws-finspace/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-fis/package.json b/packages/@aws-cdk/aws-fis/package.json index 3a0c6ab86b87b..eea1d73756d0b 100644 --- a/packages/@aws-cdk/aws-fis/package.json +++ b/packages/@aws-cdk/aws-fis/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-fms/package.json b/packages/@aws-cdk/aws-fms/package.json index f00081ac0ea9a..d1cf232ce73f2 100644 --- a/packages/@aws-cdk/aws-fms/package.json +++ b/packages/@aws-cdk/aws-fms/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-frauddetector/package.json b/packages/@aws-cdk/aws-frauddetector/package.json index 0b8c48451b6b5..2fc9ebd4499dd 100644 --- a/packages/@aws-cdk/aws-frauddetector/package.json +++ b/packages/@aws-cdk/aws-frauddetector/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index 6c5b236e7037a..2f4c3e0ef958f 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -79,7 +79,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index 9e3d586dbab6b..2dcde9763ab6e 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json index 3991e52d8a5c8..e55ed3d2ef079 100644 --- a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json @@ -73,10 +73,10 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index 40f27c8a9d3b8..b73aa3b9537f2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-glue/README.md b/packages/@aws-cdk/aws-glue/README.md index f5e200f0465e7..069b572b6cd71 100644 --- a/packages/@aws-cdk/aws-glue/README.md +++ b/packages/@aws-cdk/aws-glue/README.md @@ -42,7 +42,8 @@ These jobs run in an Apache Spark environment managed by AWS Glue. An ETL job processes data in batches using Apache Spark. ```ts -new glue.Job(stack, 'ScalaSparkEtlJob', { +declare const bucket: s3.Bucket; +new glue.Job(this, 'ScalaSparkEtlJob', { executable: glue.JobExecutable.scalaEtl({ glueVersion: glue.GlueVersion.V2_0, script: glue.Code.fromBucket(bucket, 'src/com/example/HelloWorld.scala'), @@ -58,7 +59,7 @@ new glue.Job(stack, 'ScalaSparkEtlJob', { A Streaming job is similar to an ETL job, except that it performs ETL on data streams. It uses the Apache Spark Structured Streaming framework. Some Spark job features are not available to streaming ETL jobs. ```ts -new glue.Job(stack, 'PythonSparkStreamingJob', { +new glue.Job(this, 'PythonSparkStreamingJob', { executable: glue.JobExecutable.pythonStreaming({ glueVersion: glue.GlueVersion.V2_0, pythonVersion: glue.PythonVersion.THREE, @@ -74,10 +75,11 @@ A Python shell job runs Python scripts as a shell and supports a Python version This can be used to schedule and run tasks that don't require an Apache Spark environment. ```ts -new glue.Job(stack, 'PythonShellJob', { +declare const bucket: s3.Bucket; +new glue.Job(this, 'PythonShellJob', { executable: glue.JobExecutable.pythonShell({ glueVersion: glue.GlueVersion.V1_0, - pythonVersion: PythonVersion.THREE, + pythonVersion: glue.PythonVersion.THREE, script: glue.Code.fromBucket(bucket, 'script.py'), }), description: 'an example Python Shell job', @@ -91,8 +93,10 @@ See [documentation](https://docs.aws.amazon.com/glue/latest/dg/add-job.html) for A `Connection` allows Glue jobs, crawlers and development endpoints to access certain types of data stores. For example, to create a network connection to connect to a data source within a VPC: ```ts -new glue.Connection(stack, 'MyConnection', { - connectionType: glue.ConnectionTypes.NETWORK, +declare const securityGroup: ec2.SecurityGroup; +declare const subnet: ec2.Subnet; +new glue.Connection(this, 'MyConnection', { + type: glue.ConnectionType.NETWORK, // The security groups granting AWS Glue inbound access to the data source within the VPC securityGroups: [securityGroup], // The VPC subnet which contains the data source @@ -109,7 +113,7 @@ See [Adding a Connection to Your Data Store](https://docs.aws.amazon.com/glue/la A `SecurityConfiguration` is a set of security properties that can be used by AWS Glue to encrypt data at rest. ```ts -new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { +new glue.SecurityConfiguration(this, 'MySecurityConfiguration', { securityConfigurationName: 'name', cloudWatchEncryption: { mode: glue.CloudWatchEncryptionMode.KMS, @@ -126,7 +130,8 @@ new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { By default, a shared KMS key is created for use with the encryption configurations that require one. You can also supply your own key for each encryption config, for example, for CloudWatch encryption: ```ts -new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { +declare const key: kms.Key; +new glue.SecurityConfiguration(this, 'MySecurityConfiguration', { securityConfigurationName: 'name', cloudWatchEncryption: { mode: glue.CloudWatchEncryptionMode.KMS, @@ -142,8 +147,8 @@ See [documentation](https://docs.aws.amazon.com/glue/latest/dg/encryption-securi A `Database` is a logical grouping of `Tables` in the Glue Catalog. ```ts -new glue.Database(stack, 'MyDatabase', { - databaseName: 'my_database' +new glue.Database(this, 'MyDatabase', { + databaseName: 'my_database', }); ``` @@ -152,7 +157,8 @@ new glue.Database(stack, 'MyDatabase', { A Glue table describes a table of data in S3: its structure (column names and types), location of data (S3 objects with a common prefix in a S3 bucket), and format for the files (Json, Avro, Parquet, etc.): ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { database: myDatabase, tableName: 'my_table', columns: [{ @@ -160,20 +166,29 @@ new glue.Table(stack, 'MyTable', { type: glue.Schema.STRING, }, { name: 'col2', - type: glue.Schema.array(Schema.STRING), + type: glue.Schema.array(glue.Schema.STRING), comment: 'col2 is an array of strings' // comment is optional }], - dataFormat: glue.DataFormat.JSON + dataFormat: glue.DataFormat.JSON, }); ``` By default, a S3 bucket will be created to store the table's data but you can manually pass the `bucket` and `s3Prefix`: ```ts -new glue.Table(stack, 'MyTable', { +declare const myBucket: s3.Bucket; +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { bucket: myBucket, - s3Prefix: 'my-table/' - ... + s3Prefix: 'my-table/', + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` @@ -184,21 +199,22 @@ By default, an S3 bucket will be created to store the table's data and stored in To improve query performance, a table can specify `partitionKeys` on which data is stored and queried separately. For example, you might partition a table by `year` and `month` to optimize queries based on a time window: ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { database: myDatabase, tableName: 'my_table', columns: [{ name: 'col1', - type: glue.Schema.STRING + type: glue.Schema.STRING, }], partitionKeys: [{ name: 'year', - type: glue.Schema.SMALL_INT + type: glue.Schema.SMALL_INT, }, { name: 'month', - type: glue.Schema.SMALL_INT + type: glue.Schema.SMALL_INT, }], - dataFormat: glue.DataFormat.JSON + dataFormat: glue.DataFormat.JSON, }); ``` @@ -210,52 +226,98 @@ You can enable encryption on a Table's data: * [S3Managed](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html) - Server side encryption (`SSE-S3`) with an Amazon S3-managed key. ```ts -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.S3_MANAGED - ... +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.S3_MANAGED, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [Kms](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html) - Server-side encryption (`SSE-KMS`) with an AWS KMS Key managed by the account owner. ```ts +declare const myDatabase: glue.Database; // KMS key is created automatically -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.KMS - ... +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.KMS, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); // with an explicit KMS key -new glue.Table(stack, 'MyTable', { +new glue.Table(this, 'MyTable', { encryption: glue.TableEncryption.KMS, - encryptionKey: new kms.Key(stack, 'MyKey') - ... + encryptionKey: new kms.Key(this, 'MyKey'), + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [KmsManaged](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html) - Server-side encryption (`SSE-KMS`), like `Kms`, except with an AWS KMS Key managed by the AWS Key Management Service. ```ts -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.KMS_MANAGED - ... +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.KMS_MANAGED, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [ClientSideKms](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingClientSideEncryption.html#client-side-encryption-kms-managed-master-key-intro) - Client-side encryption (`CSE-KMS`) with an AWS KMS Key managed by the account owner. ```ts +declare const myDatabase: glue.Database; // KMS key is created automatically -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.CLIENT_SIDE_KMS - ... +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); // with an explicit KMS key -new glue.Table(stack, 'MyTable', { +new glue.Table(this, 'MyTable', { encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - encryptionKey: new kms.Key(stack, 'MyKey') - ... + encryptionKey: new kms.Key(this, 'MyKey'), + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` @@ -266,30 +328,35 @@ new glue.Table(stack, 'MyTable', { A table's schema is a collection of columns, each of which have a `name` and a `type`. Types are recursive structures, consisting of primitive and complex types: ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { columns: [{ name: 'primitive_column', - type: glue.Schema.STRING + type: glue.Schema.STRING, }, { name: 'array_column', type: glue.Schema.array(glue.Schema.INTEGER), - comment: 'array' + comment: 'array', }, { name: 'map_column', type: glue.Schema.map( glue.Schema.STRING, glue.Schema.TIMESTAMP), - comment: 'map' + comment: 'map', }, { name: 'struct_column', type: glue.Schema.struct([{ name: 'nested_column', type: glue.Schema.DATE, - comment: 'nested comment' + comment: 'nested comment', }]), - comment: "struct" + comment: "struct", }], - ... + // ... + database: myDatabase, + tableName: 'my_table', + dataFormat: glue.DataFormat.JSON, +}); ``` ### Primitives diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index 673b5748537c5..e93afa2a5fc3c 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDatabase } from './glue.generated'; @@ -53,7 +53,7 @@ export class Database extends Resource implements IDatabase { class Import extends Resource implements IDatabase { public databaseArn = databaseArn; - public databaseName = stack.parseArn(databaseArn).resourceName!; + public databaseName = stack.splitArn(databaseArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; public catalogArn = stack.formatArn({ service: 'glue', resource: 'catalog' }); public catalogId = stack.account; } diff --git a/packages/@aws-cdk/aws-glue/lib/job.ts b/packages/@aws-cdk/aws-glue/lib/job.ts index 0233783f94869..4d7f982990bdd 100644 --- a/packages/@aws-cdk/aws-glue/lib/job.ts +++ b/packages/@aws-cdk/aws-glue/lib/job.ts @@ -280,7 +280,7 @@ abstract class JobBase extends cdk.Resource implements IJob { return new cloudwatch.Metric({ metricName, namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: this.jobName, JobRunId: 'ALL', Type: type, @@ -782,7 +782,7 @@ function metricRule(rule: events.IRule, props?: cloudwatch.MetricOptions): cloud return new cloudwatch.Metric({ namespace: 'AWS/Events', metricName: 'TriggeredRules', - dimensions: { RuleName: rule.ruleName }, + dimensionsMap: { RuleName: rule.ruleName }, statistic: cloudwatch.Statistic.SUM, ...props, }).attachTo(rule); diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 635fe67a68d35..0c790c5953ced 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; -import { Fn, IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Fn, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { DataFormat } from './data-format'; import { IDatabase } from './database'; @@ -151,7 +151,7 @@ export interface TableProps { export class Table extends Resource implements ITable { public static fromTableArn(scope: Construct, id: string, tableArn: string): ITable { - const tableName = Fn.select(1, Fn.split('/', Stack.of(scope).parseArn(tableArn).resourceName!)); + const tableName = Fn.select(1, Fn.split('/', Stack.of(scope).splitArn(tableArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); return Table.fromTableAttributes(scope, id, { tableArn, diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index 50a535d9810eb..b4325ec0a648b 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,8 +85,8 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..2054032733bfc --- /dev/null +++ b/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import * as path from 'path'; +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as glue from '@aws-cdk/aws-glue'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-glue/test/code.test.ts b/packages/@aws-cdk/aws-glue/test/code.test.ts index 061f6d26c351f..2cc273f51d47d 100644 --- a/packages/@aws-cdk/aws-glue/test/code.test.ts +++ b/packages/@aws-cdk/aws-glue/test/code.test.ts @@ -17,7 +17,7 @@ describe('Code', () => { let bucket: s3.IBucket; test('with valid bucket name and key and bound by job sets the right path and grants the job permissions to read from it', () => { - bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketName'); + bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketname'); script = glue.Code.fromBucket(bucket, key); new glue.Job(stack, 'Job1', { executable: glue.JobExecutable.pythonShell({ @@ -29,7 +29,7 @@ describe('Code', () => { Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { - ScriptLocation: 's3://bucketName/script', + ScriptLocation: 's3://bucketname/script', }, }); @@ -53,7 +53,7 @@ describe('Code', () => { { Ref: 'AWS::Partition', }, - ':s3:::bucketName', + ':s3:::bucketname', ], ], }, @@ -65,7 +65,7 @@ describe('Code', () => { { Ref: 'AWS::Partition', }, - ':s3:::bucketName/script', + ':s3:::bucketname/script', ], ], }, @@ -99,7 +99,7 @@ describe('Code', () => { }), }); - expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + expect(stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { ScriptLocation: { @@ -256,7 +256,7 @@ describe('Code', () => { ], }; - expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + expect(stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); // Job1 and Job2 use reuse the asset Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { diff --git a/packages/@aws-cdk/aws-glue/test/job-executable.test.ts b/packages/@aws-cdk/aws-glue/test/job-executable.test.ts index 481bd16dc8944..5fcf3b1487764 100644 --- a/packages/@aws-cdk/aws-glue/test/job-executable.test.ts +++ b/packages/@aws-cdk/aws-glue/test/job-executable.test.ts @@ -31,7 +31,7 @@ describe('JobExecutable', () => { beforeEach(() => { stack = new cdk.Stack(); - bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketName'); + bucket = s3.Bucket.fromBucketName(stack, 'Bucket', 'bucketname'); script = glue.Code.fromBucket(bucket, 'script.py'); }); diff --git a/packages/@aws-cdk/aws-glue/test/job.test.ts b/packages/@aws-cdk/aws-glue/test/job.test.ts index 625e4743570fd..c9104daa92190 100644 --- a/packages/@aws-cdk/aws-glue/test/job.test.ts +++ b/packages/@aws-cdk/aws-glue/test/job.test.ts @@ -55,7 +55,7 @@ describe('Job', () => { describe('new', () => { const className = 'com.amazon.test.ClassName'; - const codeBucketName = 'bucketName'; + const codeBucketName = 'bucketname'; const codeBucketAccessStatement = { Action: [ 's3:GetObject*', @@ -166,7 +166,7 @@ describe('Job', () => { Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { Name: 'glueetl', - ScriptLocation: 's3://bucketName/script', + ScriptLocation: 's3://bucketname/script', }, Role: { 'Fn::GetAtt': [ @@ -383,7 +383,7 @@ describe('Job', () => { }); describe('with bucket provided', () => { - const sparkUIBucketName = 'sparkBucketName'; + const sparkUIBucketName = 'sparkbucketname'; let sparkUIBucket: s3.IBucket; beforeEach(() => { @@ -420,7 +420,7 @@ describe('Job', () => { { Ref: 'AWS::Partition', }, - ':s3:::sparkBucketName', + ':s3:::sparkbucketname', ], ], }, @@ -432,7 +432,7 @@ describe('Job', () => { { Ref: 'AWS::Partition', }, - ':s3:::sparkBucketName/*', + ':s3:::sparkbucketname/*', ], ], }, @@ -460,7 +460,7 @@ describe('Job', () => { }); describe('with bucket and path provided', () => { - const sparkUIBucketName = 'sparkBucketName'; + const sparkUIBucketName = 'sparkbucketname'; const prefix = 'some/path/'; let sparkUIBucket: s3.IBucket; @@ -516,7 +516,7 @@ describe('Job', () => { Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { Name: 'glueetl', - ScriptLocation: 's3://bucketName/script', + ScriptLocation: 's3://bucketname/script', }, Role: { 'Fn::GetAtt': [ @@ -614,7 +614,7 @@ describe('Job', () => { GlueVersion: '2.0', Command: { Name: 'glueetl', - ScriptLocation: 's3://bucketName/script', + ScriptLocation: 's3://bucketname/script', PythonVersion: '3', }, Role: { @@ -625,9 +625,9 @@ describe('Job', () => { }, DefaultArguments: { '--job-language': 'python', - '--extra-jars': 's3://bucketName/file1.jar,s3://bucketName/file2.jar', - '--extra-py-files': 's3://bucketName/file1.py,s3://bucketName/file2.py', - '--extra-files': 's3://bucketName/file1.txt,s3://bucketName/file2.txt', + '--extra-jars': 's3://bucketname/file1.jar,s3://bucketname/file2.jar', + '--extra-py-files': 's3://bucketname/file1.py,s3://bucketname/file2.py', + '--extra-files': 's3://bucketname/file1.txt,s3://bucketname/file2.txt', '--user-jars-first': 'true', }, }); @@ -649,7 +649,7 @@ describe('Job', () => { GlueVersion: '2.0', Command: { Name: 'gluestreaming', - ScriptLocation: 's3://bucketName/script', + ScriptLocation: 's3://bucketname/script', }, Role: { 'Fn::GetAtt': [ @@ -660,8 +660,8 @@ describe('Job', () => { DefaultArguments: { '--job-language': 'scala', '--class': 'com.amazon.test.ClassName', - '--extra-jars': 's3://bucketName/file1.jar,s3://bucketName/file2.jar', - '--extra-files': 's3://bucketName/file1.txt,s3://bucketName/file2.txt', + '--extra-jars': 's3://bucketname/file1.jar,s3://bucketname/file2.jar', + '--extra-files': 's3://bucketname/file1.txt,s3://bucketname/file2.txt', '--user-jars-first': 'true', }, }); @@ -757,7 +757,7 @@ describe('Job', () => { testCase.invoke(job); expect(metric).toEqual(new cloudwatch.Metric({ - dimensions: { + dimensionsMap: { RuleName: (job.node.findChild(testCase.ruleId) as events.Rule).ruleName, }, metricName: 'TriggeredRules', @@ -814,7 +814,7 @@ describe('Job', () => { metricName, statistic: 'Sum', namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: job.jobName, JobRunId: 'ALL', Type: 'count', @@ -830,7 +830,7 @@ describe('Job', () => { metricName, statistic: 'Average', namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: job.jobName, JobRunId: 'ALL', Type: 'gauge', diff --git a/packages/@aws-cdk/aws-greengrass/package.json b/packages/@aws-cdk/aws-greengrass/package.json index e360765daf13a..c27f163e140a5 100644 --- a/packages/@aws-cdk/aws-greengrass/package.json +++ b/packages/@aws-cdk/aws-greengrass/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-greengrassv2/package.json b/packages/@aws-cdk/aws-greengrassv2/package.json index b36f81ae49a34..850c26982bab5 100644 --- a/packages/@aws-cdk/aws-greengrassv2/package.json +++ b/packages/@aws-cdk/aws-greengrassv2/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-groundstation/package.json b/packages/@aws-cdk/aws-groundstation/package.json index bc3c56193416e..b5ba37d753aea 100644 --- a/packages/@aws-cdk/aws-groundstation/package.json +++ b/packages/@aws-cdk/aws-groundstation/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index dee1ab10f4c53..4c0f7383bab1d 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-healthlake/package.json b/packages/@aws-cdk/aws-healthlake/package.json index ea2c9d0aac3bf..43a53367da896 100644 --- a/packages/@aws-cdk/aws-healthlake/package.json +++ b/packages/@aws-cdk/aws-healthlake/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index b54a0bff34fc4..a81b25d53e6ba 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -30,8 +30,8 @@ Managed policies can be attached using `xxx.addManagedPolicy(ManagedPolicy.fromA Many of the AWS CDK resources have `grant*` methods that allow you to grant other resources access to that resource. As an example, the following code gives a Lambda function write permissions (Put, Update, Delete) to a DynamoDB table. ```ts -const fn = new lambda.Function(this, 'Function', functionProps); -const table = new dynamodb.Table(this, 'Table', tableProps); +declare const fn: lambda.Function; +declare const table: dynamodb.Table; table.grantWriteData(fn); ``` @@ -39,8 +39,8 @@ table.grantWriteData(fn); The more generic `grant` method allows you to give specific permissions to a resource: ```ts -const fn = new lambda.Function(this, 'Function', functionProps); -const table = new dynamodb.Table(this, 'Table', tableProps); +declare const fn: lambda.Function; +declare const table: dynamodb.Table; table.grant(fn, 'dynamodb:PutItem'); ``` @@ -186,7 +186,7 @@ const role = new iam.Role(this, 'MyRole', { assumedBy: new iam.CompositePrincipal( new iam.ServicePrincipal('ec2.amazonaws.com'), new iam.AccountPrincipal('1818188181818187272') - ) + ), }); ``` @@ -212,7 +212,7 @@ Cognito, Amazon, Google or Facebook, for example: const principal = new iam.WebIdentityPrincipal('cognito-identity.amazonaws.com') .withConditions({ "StringEquals": { "cognito-identity.amazonaws.com:aud": "us-east-2:12345678-abcd-abcd-abcd-123456" }, - "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "unauthenticated"} + "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "unauthenticated" }, }); ``` @@ -256,11 +256,11 @@ const customPolicyDocument = iam.PolicyDocument.fromJson(policyDocument); // You can pass this document as an initial document to a ManagedPolicy // or inline Policy. -const newManagedPolicy = new ManagedPolicy(stack, 'MyNewManagedPolicy', { - document: customPolicyDocument +const newManagedPolicy = new iam.ManagedPolicy(this, 'MyNewManagedPolicy', { + document: customPolicyDocument, }); -const newPolicy = new Policy(stack, 'MyNewPolicy', { - document: customPolicyDocument +const newPolicy = new iam.Policy(this, 'MyNewPolicy', { + document: customPolicyDocument, }); ``` @@ -296,15 +296,18 @@ const boundary2 = new iam.ManagedPolicy(this, 'Boundary2', { }); // Directly apply the boundary to a Role you create +declare const role: iam.Role; iam.PermissionsBoundary.of(role).apply(boundary); // Apply the boundary to an Role that was implicitly created for you -iam.PermissionsBoundary.of(lambdaFunction).apply(boundary); +declare const fn: lambda.Function; +iam.PermissionsBoundary.of(fn).apply(boundary); // Apply the boundary to all Roles in a stack -iam.PermissionsBoundary.of(stack).apply(boundary); +iam.PermissionsBoundary.of(this).apply(boundary); // Remove a Permissions Boundary that is inherited, for example from the Stack level +declare const customResource: CustomResource; iam.PermissionsBoundary.of(customResource).clear(); ``` @@ -347,10 +350,13 @@ pool](https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html) you can reference the provider's ARN as follows: ```ts +import * as cognito from '@aws-cdk/aws-cognito'; + +declare const myProvider: iam.OpenIdConnectProvider; new cognito.CfnIdentityPool(this, 'IdentityPool', { openIdConnectProviderArns: [myProvider.openIdConnectProviderArn], // And the other properties for your identity pool - allowUnauthenticatedIdentities, + allowUnauthenticatedIdentities: false, }); ``` @@ -359,7 +365,7 @@ The `OpenIdConnectPrincipal` class can be used as a principal used with a `OpenI ```ts const provider = new iam.OpenIdConnectProvider(this, 'MyProvider', { url: 'https://openid/connect', - clientIds: [ 'myclient1', 'myclient2' ] + clientIds: [ 'myclient1', 'myclient2' ], }); const principal = new iam.OpenIdConnectPrincipal(provider); ``` @@ -410,25 +416,25 @@ new iam.Role(this, 'Role', { IAM manages users for your AWS account. To create a new user: ```ts -const user = new User(this, 'MyUser'); +const user = new iam.User(this, 'MyUser'); ``` To import an existing user by name [with path](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-friendly-names): ```ts -const user = User.fromUserName(stack, 'MyImportedUserByName', 'johnsmith'); +const user = iam.User.fromUserName(this, 'MyImportedUserByName', 'johnsmith'); ``` To import an existing user by ARN: ```ts -const user = User.fromUserArn(this, 'MyImportedUserByArn', 'arn:aws:iam::123456789012:user/johnsmith'); +const user = iam.User.fromUserArn(this, 'MyImportedUserByArn', 'arn:aws:iam::123456789012:user/johnsmith'); ``` To import an existing user by attributes: ```ts -const user = User.fromUserAttributes(stack, 'MyImportedUserByAttributes', { +const user = iam.User.fromUserAttributes(this, 'MyImportedUserByAttributes', { userArn: 'arn:aws:iam::123456789012:user/johnsmith', }); ``` @@ -436,8 +442,8 @@ const user = User.fromUserAttributes(stack, 'MyImportedUserByAttributes', { To add a user to a group (both for a new and imported user/group): ```ts -const user = new User(this, 'MyUser'); // or User.fromUserName(stack, 'User', 'johnsmith'); -const group = new Group(this, 'MyGroup'); // or Group.fromGroupArn(stack, 'Group', 'arn:aws:iam::account-id:group/group-name'); +const user = new iam.User(this, 'MyUser'); // or User.fromUserName(stack, 'User', 'johnsmith'); +const group = new iam.Group(this, 'MyGroup'); // or Group.fromGroupArn(stack, 'Group', 'arn:aws:iam::account-id:group/group-name'); user.addToGroup(group); // or @@ -447,9 +453,9 @@ group.addUser(user); ## Features - * Policy name uniqueness is enforced. If two policies by the same name are attached to the same - principal, the attachment will fail. - * Policy names are not required - the CDK logical ID will be used and ensured to be unique. - * Policies are validated during synthesis to ensure that they have actions, and that policies - attached to IAM principals specify relevant resources, while policies attached to resources - specify which IAM principals they apply to. + * Policy name uniqueness is enforced. If two policies by the same name are attached to the same + principal, the attachment will fail. + * Policy names are not required - the CDK logical ID will be used and ensured to be unique. + * Policies are validated during synthesis to ensure that they have actions, and that policies + attached to IAM principals specify relevant resources, while policies attached to resources + specify which IAM principals they apply to. diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index eca266f6975dd..1fb4fac6b2fe4 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,4 +1,4 @@ -import { Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnGroup } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -145,7 +145,7 @@ export class Group extends GroupBase { * @param groupArn the ARN of the group to import (e.g. `arn:aws:iam::account-id:group/group-name`) */ public static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { - const arnComponents = Stack.of(scope).parseArn(groupArn); + const arnComponents = Stack.of(scope).splitArn(groupArn, ArnFormat.SLASH_RESOURCE_NAME); const groupName = arnComponents.resourceName!; class Import extends GroupBase { public groupName = groupName; diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index cc8bddc0e8a17..f41ca17820c03 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -1,4 +1,4 @@ -import { IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IGroup } from './group'; import { CfnManagedPolicy } from './iam.generated'; @@ -151,7 +151,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { * For this managed policy, you only need to know the name to be able to use it. * * Some managed policy names start with "service-role/", some start with - * "job-function/", and some don't start with anything. Do include the + * "job-function/", and some don't start with anything. Include the * prefix when constructing this object. */ public static fromAwsManagedPolicyName(managedPolicyName: string): IManagedPolicy { @@ -247,7 +247,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { } // arn:aws:iam::123456789012:policy/teststack-CreateTestDBPolicy-16M23YE3CS700 - this.managedPolicyName = this.getResourceNameAttribute(Stack.of(this).parseArn(resource.ref, '/').resourceName!); + this.managedPolicyName = this.getResourceNameAttribute(Stack.of(this).splitArn(resource.ref, ArnFormat.SLASH_RESOURCE_NAME).resourceName!); this.managedPolicyArn = this.getResourceArnAttribute(resource.ref, { region: '', // IAM is global in each partition service: 'iam', diff --git a/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts b/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts index c1a3dde69a026..5084caf9fe4fc 100644 --- a/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts +++ b/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts @@ -6,10 +6,10 @@ import { IManagedPolicy } from './managed-policy'; /** * Modify the Permissions Boundaries of Users and Roles in a construct tree * - * @example - * - * const policy = ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess'); - * PermissionsBoundary.of(stack).apply(policy); + * ```ts + * const policy = iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess'); + * iam.PermissionsBoundary.of(this).apply(policy); + * ``` */ export class PermissionsBoundary { /** diff --git a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts index ecedbe7c2ded5..08a8353e84b36 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts @@ -1,8 +1,7 @@ import * as cdk from '@aws-cdk/core'; -import { AnyPrincipal } from '.'; import { Group } from './group'; import { - AccountPrincipal, AccountRootPrincipal, ArnPrincipal, CanonicalUserPrincipal, + AccountPrincipal, AccountRootPrincipal, AnyPrincipal, ArnPrincipal, CanonicalUserPrincipal, FederatedPrincipal, IPrincipal, PrincipalBase, PrincipalPolicyFragment, ServicePrincipal, ServicePrincipalOpts, } from './principals'; import { LITERAL_STRING_KEY, mergePrincipal } from './util'; diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index f142940b2b3ef..b103b21231e23 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,4 @@ -import { Duration, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { ArnFormat, Duration, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; @@ -185,7 +185,7 @@ export class Role extends Resource implements IRole { */ public static fromRoleArn(scope: Construct, id: string, roleArn: string, options: FromRoleArnOptions = {}): IRole { const scopeStack = Stack.of(scope); - const parsedArn = scopeStack.parseArn(roleArn); + const parsedArn = scopeStack.splitArn(roleArn, ArnFormat.SLASH_RESOURCE_NAME); const resourceName = parsedArn.resourceName!; const roleAccount = parsedArn.account; // service roles have an ARN like 'arn:aws:iam:::role/service-role/' diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index 1449128836c09..e448eaf64cec0 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,10 +84,10 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", - "jest": "^26.6.3", + "jest": "^27.3.1", "sinon": "^9.2.4" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-iam/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-iam/rosetta/default.ts-fixture index a76493f53c694..a27f557ccf250 100644 --- a/packages/@aws-cdk/aws-iam/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-iam/rosetta/default.ts-fixture @@ -1,17 +1,12 @@ -import { Construct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CustomResource, Stack } from '@aws-cdk/core'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import * as cognito from '@aws-cdk/aws-cognito'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as lambda from '@aws-cdk/aws-lambda'; import * as iam from '@aws-cdk/aws-iam'; -declare const allowUnauthenticatedIdentities: boolean; -declare const functionProps: lambda.FunctionProps; -declare const myProvider: iam.OpenIdConnectProvider; -declare const tableProps: dynamodb.TableProps; - -class fixture$construct extends Construct { - public constructor(scope: Construct, id: string) { +class Fixture extends Stack { + constructor(scope: Construct, id: string) { super(scope, id); /// here diff --git a/packages/@aws-cdk/aws-iam/test/role.test.ts b/packages/@aws-cdk/aws-iam/test/role.test.ts index 1e64f8a9a9369..f251f8388a6d2 100644 --- a/packages/@aws-cdk/aws-iam/test/role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack, App } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, CompositePrincipal, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User, Policy, PolicyDocument } from '../lib'; @@ -58,7 +59,7 @@ describe('IAM role', () => { }); }); - test('can supply externalId', () => { + testDeprecated('can supply externalId', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-imagebuilder/package.json b/packages/@aws-cdk/aws-imagebuilder/package.json index e3a90f5b90246..c359b498792a6 100644 --- a/packages/@aws-cdk/aws-imagebuilder/package.json +++ b/packages/@aws-cdk/aws-imagebuilder/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index aa8e421f9b561..fc4b526784a77 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-iot-actions/.eslintrc.js b/packages/@aws-cdk/aws-iot-actions/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot-actions/.gitignore b/packages/@aws-cdk/aws-iot-actions/.gitignore new file mode 100644 index 0000000000000..d8a8561d50885 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +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-iot-actions/.npmignore b/packages/@aws-cdk/aws-iot-actions/.npmignore new file mode 100644 index 0000000000000..aaabf1df59065 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/.npmignore @@ -0,0 +1,28 @@ +# 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/ +!*.lit.ts \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/LICENSE b/packages/@aws-cdk/aws-iot-actions/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/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-2021 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-iot-actions/NOTICE b/packages/@aws-cdk/aws-iot-actions/NOTICE new file mode 100644 index 0000000000000..39cd25bf899ae --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/NOTICE @@ -0,0 +1,32 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** case - https://www.npmjs.com/package/case +Copyright (c) 2013 Nathan Bubna + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +---------------- diff --git a/packages/@aws-cdk/aws-iot-actions/README.md b/packages/@aws-cdk/aws-iot-actions/README.md new file mode 100644 index 0000000000000..e02f67cee0d45 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/README.md @@ -0,0 +1,153 @@ +# Actions for AWS IoT Rule + + +--- + +![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. + +--- + + + +This library contains integration classes to send data to any number of +supported AWS Services. Instances of these classes should be passed to +`TopicRule` defined in `@aws-cdk/aws-iot`. + +Currently supported are: + +- Invoke a Lambda function +- Put objects to a S3 bucket +- Put logs to CloudWatch Logs +- Put records to Kinesis Data Firehose stream + +## Invoke a Lambda function + +The code snippet below creates an AWS IoT Rule that invoke a Lambda function +when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as lambda from '@aws-cdk/aws-lambda'; + +const func = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline(` + exports.handler = (event) => { + console.log("It is test for lambda action of AWS IoT Rule.", event); + };` + ), +}); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'"), + actions: [new actions.LambdaFunctionAction(func)], +}); +``` + +## Put objects to a S3 bucket + +The code snippet below creates an AWS IoT Rule that put objects to a S3 bucket +when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as s3 from '@aws-cdk/aws-s3'; + +const bucket = new s3.Bucket(this, 'MyBucket'); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [new actions.S3PutObjectAction(bucket)], +}); +``` + +The property `key` of `S3PutObjectAction` is given the value `${topic()}/${timestamp()}` by default. This `${topic()}` +and `${timestamp()}` is called Substitution templates. For more information see +[this documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html). +In above sample, `${topic()}` is replaced by a given MQTT topic as `device/001/data`. And `${timestamp()}` is replaced +by the number of the current timestamp in milliseconds as `1636289461203`. So if the MQTT broker receives an MQTT topic +`device/001/data` on `2021-11-07T00:00:00.000Z`, the S3 bucket object will be put to `device/001/data/1636243200000`. + +You can also set specific `key` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), + actions: [ + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + }), + ], +}); +``` + +If you wanna set access control to the S3 bucket object, you can specify `accessControl` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT * FROM 'device/+/data'"), + actions: [ + new actions.S3PutObjectAction(bucket, { + accessControl: s3.BucketAccessControl.PUBLIC_READ, + }), + ], +}); +``` + +## Put logs to CloudWatch Logs + +The code snippet below creates an AWS IoT Rule that put logs to CloudWatch Logs +when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as logs from '@aws-cdk/aws-logs'; + +const logGroup = new logs.LogGroup(this, 'MyLogGroup'); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [new actions.CloudWatchLogsAction(logGroup)], +}); +``` + + +## Put records to Kinesis Data Firehose stream + +The code snippet below creates an AWS IoT Rule that put records to Put records +to Kinesis Data Firehose stream when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; + +const bucket = new s3.Bucket(this, 'MyBucket'); +const stream = new firehose.DeliveryStream(this, 'MyStream', { + destinations: [new destinations.S3Bucket(bucket)], +}); + +const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT * FROM 'device/+/data'"), + actions: [ + new actions.FirehoseStreamAction(stream, { + batchMode: true, + recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE, + }), + ], +}); +``` diff --git a/packages/@aws-cdk/aws-iot-actions/jest.config.js b/packages/@aws-cdk/aws-iot-actions/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts new file mode 100644 index 0000000000000..fb8f2779f32e7 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts @@ -0,0 +1,44 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as logs from '@aws-cdk/aws-logs'; +import { CommonActionProps } from './common-action-props'; +import { singletonActionRole } from './private/role'; + +/** + * Configuration properties of an action for CloudWatch Logs. + */ +export interface CloudWatchLogsActionProps extends CommonActionProps { +} + +/** + * The action to send data to Amazon CloudWatch Logs + */ +export class CloudWatchLogsAction implements iot.IAction { + private readonly role?: iam.IRole; + + /** + * @param logGroup The CloudWatch log group to which the action sends data + * @param props Optional properties to not use default + */ + constructor( + private readonly logGroup: logs.ILogGroup, + props: CloudWatchLogsActionProps = {}, + ) { + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + this.logGroup.grantWrite(role); + this.logGroup.grant(role, 'logs:DescribeLogStreams'); + + return { + configuration: { + cloudwatchLogs: { + logGroupName: this.logGroup.logGroupName, + roleArn: role.roleArn, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts new file mode 100644 index 0000000000000..5a9b52d8b5f27 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts @@ -0,0 +1,13 @@ +import * as iam from '@aws-cdk/aws-iam'; + +/** + * Common properties shared by Actions it access to AWS service. + */ +export interface CommonActionProps { + /** + * The IAM role that allows access to AWS service. + * + * @default a new role will be created + */ + readonly role?: iam.IRole; +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts new file mode 100644 index 0000000000000..c694bef7cad38 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts @@ -0,0 +1,88 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import { CommonActionProps } from './common-action-props'; +import { singletonActionRole } from './private/role'; + +/** + * Record Separator to be used to separate records. + */ +export enum FirehoseStreamRecordSeparator { + /** + * Separate by a new line + */ + NEWLINE = '\n', + + /** + * Separate by a tab + */ + TAB = '\t', + + /** + * Separate by a windows new line + */ + WINDOWS_NEWLINE = '\r\n', + + /** + * Separate by a commma + */ + COMMA = ',', +} + +/** + * Configuration properties of an action for the Kinesis Data Firehose stream. + */ +export interface FirehoseStreamActionProps extends CommonActionProps { + /** + * Whether to deliver the Kinesis Data Firehose stream as a batch by using `PutRecordBatch`. + * When batchMode is true and the rule's SQL statement evaluates to an Array, each Array + * element forms one record in the PutRecordBatch request. The resulting array can't have + * more than 500 records. + * + * @default false + */ + readonly batchMode?: boolean; + + /** + * A character separator that will be used to separate records written to the Kinesis Data Firehose stream. + * + * @default - none -- the stream does not use a separator + */ + readonly recordSeparator?: FirehoseStreamRecordSeparator; +} + + +/** + * The action to put the record from an MQTT message to the Kinesis Data Firehose stream. + */ +export class FirehoseStreamAction implements iot.IAction { + private readonly batchMode?: boolean; + private readonly recordSeparator?: string; + private readonly role?: iam.IRole; + + /** + * @param stream The Kinesis Data Firehose stream to which to put records. + * @param props Optional properties to not use default + */ + constructor(private readonly stream: firehose.IDeliveryStream, props: FirehoseStreamActionProps = {}) { + this.batchMode = props.batchMode; + this.recordSeparator = props.recordSeparator; + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + this.stream.grantPutRecords(role); + + return { + configuration: { + firehose: { + batchMode: this.batchMode, + deliveryStreamName: this.stream.deliveryStreamName, + roleArn: role.roleArn, + separator: this.recordSeparator, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/index.ts b/packages/@aws-cdk/aws-iot-actions/lib/index.ts new file mode 100644 index 0000000000000..ce74a2ff2b685 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/index.ts @@ -0,0 +1,5 @@ +export * from './cloudwatch-logs-action'; +export * from './common-action-props'; +export * from './firehose-stream-action'; +export * from './lambda-function-action'; +export * from './s3-put-object-action'; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts new file mode 100644 index 0000000000000..60cf056d6e5ba --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts @@ -0,0 +1,31 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Names } from '@aws-cdk/core'; + +/** + * The action to invoke an AWS Lambda function, passing in an MQTT message. + */ +export class LambdaFunctionAction implements iot.IAction { + /** + * @param func The lambda function to be invoked by this action + */ + constructor(private readonly func: lambda.IFunction) {} + + bind(topicRule: iot.ITopicRule): iot.ActionConfig { + this.func.addPermission(`${Names.nodeUniqueId(topicRule.node)}:IotLambdaFunctionAction`, { + action: 'lambda:InvokeFunction', + principal: new iam.ServicePrincipal('iot.amazonaws.com'), + sourceAccount: topicRule.env.account, + sourceArn: topicRule.topicRuleArn, + }); + + return { + configuration: { + lambda: { + functionArn: this.func.functionArn, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/private/role.ts b/packages/@aws-cdk/aws-iot-actions/lib/private/role.ts new file mode 100644 index 0000000000000..78c72b914f56b --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/private/role.ts @@ -0,0 +1,27 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { IConstruct, PhysicalName } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +/** + * Obtain the Role for the TopicRule + * + * If a role already exists, it will be returned. This ensures that if a rule have multiple + * actions, they will share a role. + * @internal + */ +export function singletonActionRole(scope: IConstruct): iam.IRole { + const id = 'TopicRuleActionRole'; + const existing = scope.node.tryFindChild(id) as iam.IRole; + if (existing) { + return existing; + }; + + const role = new iam.Role(scope as Construct, id, { + roleName: PhysicalName.GENERATE_IF_NEEDED, + assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'), + }); + return role; +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts new file mode 100644 index 0000000000000..f690bf813a922 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts @@ -0,0 +1,67 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import { kebab as toKebabCase } from 'case'; +import { CommonActionProps } from './common-action-props'; +import { singletonActionRole } from './private/role'; + +/** + * Configuration properties of an action for s3. + */ +export interface S3PutObjectActionProps extends CommonActionProps { + /** + * The Amazon S3 canned ACL that controls access to the object identified by the object key. + * @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl + * + * @default None + */ + readonly accessControl?: s3.BucketAccessControl; + + /** + * The path to the file where the data is written. + * + * Supports substitution templates. + * @see https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html + * + * @default '${topic()}/${timestamp()}' + */ + readonly key?: string; +} + +/** + * The action to write the data from an MQTT message to an Amazon S3 bucket. + */ +export class S3PutObjectAction implements iot.IAction { + private readonly accessControl?: string; + private readonly key?: string; + private readonly role?: iam.IRole; + + /** + * @param bucket The Amazon S3 bucket to which to write data. + * @param props Optional properties to not use default + */ + constructor(private readonly bucket: s3.IBucket, props: S3PutObjectActionProps = {}) { + this.accessControl = props.accessControl; + this.key = props.key; + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['s3:PutObject'], + resources: [this.bucket.arnForObjects('*')], + })); + + return { + configuration: { + s3: { + bucketName: this.bucket.bucketName, + cannedAcl: this.accessControl && toKebabCase(this.accessControl.toString()), + key: this.key ?? '${topic()}/${timestamp()}', + roleArn: role.roleArn, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/package.json b/packages/@aws-cdk/aws-iot-actions/package.json new file mode 100644 index 0000000000000..b996897b7719d --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/package.json @@ -0,0 +1,118 @@ +{ + "name": "@aws-cdk/aws-iot-actions", + "version": "0.0.0", + "description": "Receipt rule actions for AWS IoT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.iot.actions", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "iot-actions" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.IoT.Actions", + "packageId": "Amazon.CDK.AWS.IoT.Actions", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.aws-iot-actions", + "module": "aws_cdk.aws_iot_actions", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-iot-actions" + }, + "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", + "build+test+package": "yarn build+test && yarn package", + "build+test": "yarn build && yarn test", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "iot", + "actions" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/aws-kinesisfirehose-destinations": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cdk-integ-tools": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^27.0.2", + "constructs": "^3.3.69", + "jest": "^27.3.1" + }, + "dependencies": { + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/core": "0.0.0", + "case": "1.6.3", + "constructs": "^3.3.69" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "bundledDependencies": [ + "case" + ], + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "experimental", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts new file mode 100644 index 0000000000000..4499cdd35d6f1 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts @@ -0,0 +1,151 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as logs from '@aws-cdk/aws-logs'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default cloudwatch logs action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const logGroup = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group'); + + // WHEN + topicRule.addAction( + new actions.CloudWatchLogsAction(logGroup), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + CloudwatchLogs: { + LogGroupName: 'my-log-group', + RoleArn: { + 'Fn::GetAtt': [ + 'MyTopicRuleTopicRuleActionRoleCE2D05DA', + 'Arn', + ], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['logs:CreateLogStream', 'logs:PutLogEvents'], + Effect: 'Allow', + Resource: 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*', + }, + { + Action: 'logs:DescribeLogStreams', + Effect: 'Allow', + Resource: 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const logGroup = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.CloudWatchLogsAction(logGroup, { + role, + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ CloudwatchLogs: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); + +test('When multiple actions are omitted role property, the actions use same one role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const logGroup1 = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group1', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group1'); + const logGroup2 = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group2', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group2'); + + // WHEN + topicRule.addAction(new actions.CloudWatchLogsAction(logGroup1)); + topicRule.addAction(new actions.CloudWatchLogsAction(logGroup2)); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + CloudwatchLogs: { + LogGroupName: 'my-log-group1', + RoleArn: { + 'Fn::GetAtt': [ + 'MyTopicRuleTopicRuleActionRoleCE2D05DA', + 'Arn', + ], + }, + }, + }, + { + CloudwatchLogs: { + LogGroupName: 'my-log-group2', + RoleArn: { + 'Fn::GetAtt': [ + 'MyTopicRuleTopicRuleActionRoleCE2D05DA', + 'Arn', + ], + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.expected.json new file mode 100644 index 0000000000000..7d1748a084c77 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.expected.json @@ -0,0 +1,92 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "CloudwatchLogs": { + "LogGroupName": { + "Ref": "MyLogGroup5C0DAD85" + }, + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + } + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT topic(2) as device_id FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyLogGroup5C0DAD85", + "Arn" + ] + } + }, + { + "Action": "logs:DescribeLogStreams", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyLogGroup5C0DAD85", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyLogGroup5C0DAD85": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.ts new file mode 100644 index 0000000000000..802f485b77e37 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.ts @@ -0,0 +1,25 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as logs from '@aws-cdk/aws-logs'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + + const logGroup = new logs.LogGroup(this, 'MyLogGroup', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + topicRule.addAction(new actions.CloudWatchLogsAction(logGroup)); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts new file mode 100644 index 0000000000000..2941cc1db270c --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts @@ -0,0 +1,143 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default firehose stream action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Firehose: { + DeliveryStreamName: 'my-stream', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['firehose:PutRecord', 'firehose:PutRecordBatch'], + Effect: 'Allow', + Resource: 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set batchMode', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { batchMode: true }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { BatchMode: true } }), + ], + }, + }); +}); + +test('can set separotor', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { Separator: '\n' } }), + ], + }, + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json new file mode 100644 index 0000000000000..d1565669b5c04 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json @@ -0,0 +1,306 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Firehose": { + "BatchMode": true, + "DeliveryStreamName": { + "Ref": "MyStream5C050E93" + }, + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + }, + "Separator": "\n" + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT * FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "firehose:PutRecord", + "firehose:PutRecordBatch" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyStream5C050E93", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyStreamServiceRole8C50608A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStreamS3DestinationRole5E0BA960": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStreamS3DestinationRoleDefaultPolicy401EF6F2": { + "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": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyStreamLogGroupAB67AB09", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyStreamS3DestinationRoleDefaultPolicy401EF6F2", + "Roles": [ + { + "Ref": "MyStreamS3DestinationRole5E0BA960" + } + ] + } + }, + "MyStreamLogGroupAB67AB09": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyStreamLogGroupS3Destination423E82A8": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "MyStreamLogGroupAB67AB09" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyStream5C050E93": { + "Type": "AWS::KinesisFirehose::DeliveryStream", + "Properties": { + "DeliveryStreamType": "DirectPut", + "ExtendedS3DestinationConfiguration": { + "BucketARN": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "CloudWatchLoggingOptions": { + "Enabled": true, + "LogGroupName": { + "Ref": "MyStreamLogGroupAB67AB09" + }, + "LogStreamName": { + "Ref": "MyStreamLogGroupS3Destination423E82A8" + } + }, + "RoleARN": { + "Fn::GetAtt": [ + "MyStreamS3DestinationRole5E0BA960", + "Arn" + ] + } + } + }, + "DependsOn": [ + "MyStreamS3DestinationRoleDefaultPolicy401EF6F2" + ] + } + }, + "Mappings": { + "awscdkawskinesisfirehoseCidrBlocks": { + "af-south-1": { + "FirehoseCidrBlock": "13.244.121.224/27" + }, + "ap-east-1": { + "FirehoseCidrBlock": "18.162.221.32/27" + }, + "ap-northeast-1": { + "FirehoseCidrBlock": "13.113.196.224/27" + }, + "ap-northeast-2": { + "FirehoseCidrBlock": "13.209.1.64/27" + }, + "ap-northeast-3": { + "FirehoseCidrBlock": "13.208.177.192/27" + }, + "ap-south-1": { + "FirehoseCidrBlock": "13.232.67.32/27" + }, + "ap-southeast-1": { + "FirehoseCidrBlock": "13.228.64.192/27" + }, + "ap-southeast-2": { + "FirehoseCidrBlock": "13.210.67.224/27" + }, + "ca-central-1": { + "FirehoseCidrBlock": "35.183.92.128/27" + }, + "cn-north-1": { + "FirehoseCidrBlock": "52.81.151.32/27" + }, + "cn-northwest-1": { + "FirehoseCidrBlock": "161.189.23.64/27" + }, + "eu-central-1": { + "FirehoseCidrBlock": "35.158.127.160/27" + }, + "eu-north-1": { + "FirehoseCidrBlock": "13.53.63.224/27" + }, + "eu-south-1": { + "FirehoseCidrBlock": "15.161.135.128/27" + }, + "eu-west-1": { + "FirehoseCidrBlock": "52.19.239.192/27" + }, + "eu-west-2": { + "FirehoseCidrBlock": "18.130.1.96/27" + }, + "eu-west-3": { + "FirehoseCidrBlock": "35.180.1.96/27" + }, + "me-south-1": { + "FirehoseCidrBlock": "15.185.91.0/27" + }, + "sa-east-1": { + "FirehoseCidrBlock": "18.228.1.128/27" + }, + "us-east-1": { + "FirehoseCidrBlock": "52.70.63.192/27" + }, + "us-east-2": { + "FirehoseCidrBlock": "13.58.135.96/27" + }, + "us-gov-east-1": { + "FirehoseCidrBlock": "18.253.138.96/27" + }, + "us-gov-west-1": { + "FirehoseCidrBlock": "52.61.204.160/27" + }, + "us-west-1": { + "FirehoseCidrBlock": "13.57.135.192/27" + }, + "us-west-2": { + "FirehoseCidrBlock": "52.89.255.224/27" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts new file mode 100644 index 0000000000000..9287f1294b4dd --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts @@ -0,0 +1,38 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT * FROM 'device/+/data'", + ), + }); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const stream = new firehose.DeliveryStream(this, 'MyStream', { + destinations: [new destinations.S3Bucket(bucket)], + }); + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { + batchMode: true, + recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE, + }), + ); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json new file mode 100644 index 0000000000000..4c619dff4cf84 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json @@ -0,0 +1,97 @@ +{ + "Resources": { + "MyFunctionServiceRole3C357FF2": { + "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" + ] + ] + } + ] + } + }, + "MyFunction3BAA72D1": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\n exports.handler = (event) => {\n console.log(\"It is test for lambda action of AWS IoT Rule.\", event);\n };\"" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionServiceRole3C357FF2", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "MyFunctionServiceRole3C357FF2" + ] + }, + "MyFunctionteststackTopicRule1CB8242FIotLambdaFunctionAction37A1A89F": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyFunction3BAA72D1", + "Arn" + ] + }, + "Principal": "iot.amazonaws.com", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TopicRule40A4EA44", + "Arn" + ] + } + } + }, + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Lambda": { + "FunctionArn": { + "Fn::GetAtt": [ + "MyFunction3BAA72D1", + "Arn" + ] + } + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.ts b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.ts new file mode 100644 index 0000000000000..58a7773afec03 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.ts @@ -0,0 +1,31 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const func = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline(` + exports.handler = (event) => { + console.log("It is test for lambda action of AWS IoT Rule.", event); + };"`, + ), + }); + + new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp, temperature FROM 'device/+/data'"), + actions: [new actions.LambdaFunctionAction(func)], + }); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts new file mode 100644 index 0000000000000..88974ae613d44 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts @@ -0,0 +1,81 @@ +import { Template } from '@aws-cdk/assertions'; +import * as iot from '@aws-cdk/aws-iot'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('create a topic rule with lambda action and a lambda permission to be invoked by the topic rule', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const func = new lambda.Function(stack, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline('console.log("foo")'), + }); + + // WHEN + topicRule.addAction(new actions.LambdaFunctionAction(func)); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Lambda: { + FunctionArn: { + 'Fn::GetAtt': [ + 'MyFunction3BAA72D1', + 'Arn', + ], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'MyFunction3BAA72D1', + 'Arn', + ], + }, + Principal: 'iot.amazonaws.com', + SourceAccount: { Ref: 'AWS::AccountId' }, + SourceArn: { + 'Fn::GetAtt': [ + 'MyTopicRule4EC2091C', + 'Arn', + ], + }, + }); +}); + +test('create two different permissions, when two topic rules have the same action', () => { + // GIVEN + const stack = new cdk.Stack(); + const func = new lambda.Function(stack, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline('console.log("foo")'), + }); + const action = new actions.LambdaFunctionAction(func); + + // WHEN + new iot.TopicRule(stack, 'MyTopicRule1', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [action], + }); + new iot.TopicRule(stack, 'MyTopicRule2', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [action], + }); + + // THEN + Template.fromStack(stack).resourceCountIs('AWS::Lambda::Permission', 2); +}); diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json new file mode 100644 index 0000000000000..4e530f04da2c1 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "S3": { + "BucketName": { + "Ref": "MyBucketF68F3FF0" + }, + "CannedAcl": "bucket-owner-full-control", + "Key": "${year}/${month}/${day}/${topic(2)}", + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + } + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts new file mode 100644 index 0000000000000..9e100e0254eaf --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts @@ -0,0 +1,32 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), + }); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + }), + ); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts new file mode 100644 index 0000000000000..567bd59d05083 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts @@ -0,0 +1,148 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default s3 action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: 'test-bucket', + Key: '${topic()}/${timestamp()}', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: 'arn:aws:s3::123456789012:test-bucket/*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set key of bucket', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + key: 'test-key', + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { Key: 'test-key' } }), + ], + }, + }); +}); + +test('can set canned ACL and it convert to kebab case', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { CannedAcl: 'bucket-owner-full-control' } }), + ], + }, + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); diff --git a/packages/@aws-cdk/aws-iot/README.md b/packages/@aws-cdk/aws-iot/README.md index 7334c9d4e108e..402036b531dfc 100644 --- a/packages/@aws-cdk/aws-iot/README.md +++ b/packages/@aws-cdk/aws-iot/README.md @@ -9,8 +9,95 @@ > > [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib +![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. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +AWS IoT Core lets you connect billions of IoT devices and route trillions of +messages to AWS services without managing infrastructure. + +## Installation + +Install the module: + +```console +$ npm i @aws-cdk/aws-iot +``` + +Import it into your code: + +```ts +import * as iot from '@aws-cdk/aws-iot'; +``` + +## `TopicRule` + +Create a topic rule that give your devices the ability to interact with AWS services. +You can create a topic rule with an action that invoke the Lambda action as following: + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as lambda from '@aws-cdk/aws-lambda'; + +const func = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline(` + exports.handler = (event) => { + console.log("It is test for lambda action of AWS IoT Rule.", event); + };` + ), +}); + +new iot.TopicRule(this, 'TopicRule', { + topicRuleName: 'MyTopicRule', // optional + description: 'invokes the lambda finction', // optional + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"), + actions: [new actions.LambdaFunctionAction(func)], +}); +``` + +Or, you can add an action after constructing the `TopicRule` instance as following: + +```ts +const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"), +}); +topicRule.addAction(new actions.LambdaFunctionAction(func)) +``` + +You can also supply `errorAction` as following, +and the IoT Rule will trigger it if a rule's action is unable to perform: + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as logs from '@aws-cdk/aws-logs'; + +const logGroup = new logs.LogGroup(this, 'MyLogGroup'); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"), + errorAction: new actions.CloudWatchLogsAction(logGroup), +}); +``` + +If you wanna make the topic rule disable, add property `enabled: false` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id, timestamp() as timestamp FROM 'device/+/data'"), + enabled: false, +}); +``` + +See also [@aws-cdk/aws-iot-actions](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-iot-actions-readme.html) for other actions. diff --git a/packages/@aws-cdk/aws-iot/lib/action.ts b/packages/@aws-cdk/aws-iot/lib/action.ts new file mode 100644 index 0000000000000..f22daf6194b1c --- /dev/null +++ b/packages/@aws-cdk/aws-iot/lib/action.ts @@ -0,0 +1,24 @@ +import { CfnTopicRule } from './iot.generated'; +import { ITopicRule } from './topic-rule'; + +/** + * An abstract action for TopicRule. + */ +export interface IAction { + /** + * Returns the topic rule action specification. + * + * @param topicRule The TopicRule that would trigger this action. + */ + bind(topicRule: ITopicRule): ActionConfig; +} + +/** + * Properties for an topic rule action + */ +export interface ActionConfig { + /** + * The configuration for this action. + */ + readonly configuration: CfnTopicRule.ActionProperty; +} diff --git a/packages/@aws-cdk/aws-iot/lib/index.ts b/packages/@aws-cdk/aws-iot/lib/index.ts index 4f78a6cf531e3..f2e82a6c755b2 100644 --- a/packages/@aws-cdk/aws-iot/lib/index.ts +++ b/packages/@aws-cdk/aws-iot/lib/index.ts @@ -1,2 +1,6 @@ +export * from './action'; +export * from './iot-sql'; +export * from './topic-rule'; + // AWS::IoT CloudFormation Resources: export * from './iot.generated'; diff --git a/packages/@aws-cdk/aws-iot/lib/iot-sql.ts b/packages/@aws-cdk/aws-iot/lib/iot-sql.ts new file mode 100644 index 0000000000000..7014778cc94ab --- /dev/null +++ b/packages/@aws-cdk/aws-iot/lib/iot-sql.ts @@ -0,0 +1,74 @@ +import { Construct } from 'constructs'; + +/** + * The type returned from the `bind()` method in {@link IotSql}. + */ +export interface IotSqlConfig { + /** + * The version of the SQL rules engine to use when evaluating the rule. + */ + readonly awsIotSqlVersion: string; + + /** + * The SQL statement used to query the topic. + */ + readonly sql: string; +} + +/** + * Defines AWS IoT SQL. + */ +export abstract class IotSql { + /** + * Uses the original SQL version built on 2015-10-08. + * + * @param sql The actual SQL-like syntax query + * @returns Instance of IotSql + */ + public static fromStringAsVer20151008(sql: string): IotSql { + return new IotSqlImpl('2015-10-08', sql); + } + + /** + * Uses the SQL version built on 2016-03-23. + * + * @param sql The actual SQL-like syntax query + * @returns Instance of IotSql + */ + public static fromStringAsVer20160323(sql: string): IotSql { + return new IotSqlImpl('2016-03-23', sql); + } + + /** + * Uses the most recent beta SQL version. If you use this version, it might + * introduce breaking changes to your rules. + * + * @param sql The actual SQL-like syntax query + * @returns Instance of IotSql + */ + public static fromStringAsVerNewestUnstable(sql: string): IotSql { + return new IotSqlImpl('beta', sql); + } + + /** + * Returns the IoT SQL configuration. + */ + public abstract bind(scope: Construct): IotSqlConfig; +} + +class IotSqlImpl extends IotSql { + constructor(private readonly version: string, private readonly sql: string) { + super(); + + if (sql === '') { + throw new Error('IoT SQL string cannot be empty'); + } + } + + bind(_scope: Construct): IotSqlConfig { + return { + awsIotSqlVersion: this.version, + sql: this.sql, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot/lib/topic-rule.ts b/packages/@aws-cdk/aws-iot/lib/topic-rule.ts new file mode 100644 index 0000000000000..89f40c3797d97 --- /dev/null +++ b/packages/@aws-cdk/aws-iot/lib/topic-rule.ts @@ -0,0 +1,163 @@ +import { ArnFormat, Resource, Stack, IResource, Lazy } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { IAction } from './action'; +import { IotSql } from './iot-sql'; +import { CfnTopicRule } from './iot.generated'; + +/** + * Represents an AWS IoT Rule + */ +export interface ITopicRule extends IResource { + /** + * The value of the topic rule Amazon Resource Name (ARN), such as + * arn:aws:iot:us-east-2:123456789012:rule/rule_name + * + * @attribute + */ + readonly topicRuleArn: string; + + /** + * The name topic rule + * + * @attribute + */ + readonly topicRuleName: string; +} + +/** + * Properties for defining an AWS IoT Rule + */ +export interface TopicRuleProps { + /** + * The name of the topic rule. + * @default None + */ + readonly topicRuleName?: string; + + /** + * The actions associated with the topic rule. + * + * @default No actions will be perform + */ + readonly actions?: IAction[]; + + /** + * A textual description of the topic rule. + * + * @default None + */ + readonly description?: string; + + /** + * The action AWS IoT performs when it is unable to perform a rule's action. + * + * @default - no action will be performed + */ + readonly errorAction?: IAction; + + /** + * Specifies whether the rule is enabled. + * + * @default true + */ + readonly enabled?: boolean + + /** + * A simplified SQL syntax to filter messages received on an MQTT topic and push the data elsewhere. + * + * @see https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html + */ + readonly sql: IotSql; +} + +/** + * Defines an AWS IoT Rule in this stack. + */ +export class TopicRule extends Resource implements ITopicRule { + /** + * Import an existing AWS IoT Rule provided an ARN + * + * @param scope The parent creating construct (usually `this`). + * @param id The construct's name. + * @param topicRuleArn AWS IoT Rule ARN (i.e. arn:aws:iot:::rule/MyRule). + */ + public static fromTopicRuleArn(scope: Construct, id: string, topicRuleArn: string): ITopicRule { + const parts = Stack.of(scope).splitArn(topicRuleArn, ArnFormat.SLASH_RESOURCE_NAME); + if (!parts.resourceName) { + throw new Error(`Missing topic rule name in ARN: '${topicRuleArn}'`); + } + const resourceName = parts.resourceName; + + class Import extends Resource implements ITopicRule { + public readonly topicRuleArn = topicRuleArn; + public readonly topicRuleName = resourceName; + } + return new Import(scope, id, { + environmentFromArn: topicRuleArn, + }); + } + + /** + * Arn of this topic rule + * @attribute + */ + public readonly topicRuleArn: string; + + /** + * Name of this topic rule + * @attribute + */ + public readonly topicRuleName: string; + + private readonly actions: CfnTopicRule.ActionProperty[] = []; + + constructor(scope: Construct, id: string, props: TopicRuleProps) { + super(scope, id, { + physicalName: props.topicRuleName, + }); + + const sqlConfig = props.sql.bind(this); + + const resource = new CfnTopicRule(this, 'Resource', { + ruleName: this.physicalName, + topicRulePayload: { + actions: Lazy.any({ produce: () => this.actions }), + awsIotSqlVersion: sqlConfig.awsIotSqlVersion, + description: props.description, + errorAction: props.errorAction?.bind(this).configuration, + ruleDisabled: props.enabled === undefined ? undefined : !props.enabled, + sql: sqlConfig.sql, + }, + }); + + this.topicRuleArn = this.getResourceArnAttribute(resource.attrArn, { + service: 'iot', + resource: 'rule', + resourceName: this.physicalName, + }); + this.topicRuleName = this.getResourceNameAttribute(resource.ref); + + props.actions?.forEach(action => { + this.addAction(action); + }); + } + + /** + * Add a action to the topic rule. + * + * @param action the action to associate with the topic rule. + */ + public addAction(action: IAction): void { + const { configuration } = action.bind(this); + + const keys = Object.keys(configuration); + if (keys.length === 0) { + throw new Error('An action property cannot be an empty object.'); + } + if (keys.length > 1) { + throw new Error(`An action property cannot have multiple keys, received: ${keys}`); + } + + this.actions.push(configuration); + } +} diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index 9960168c78ec1..ffd0b7cca83c7 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -74,9 +74,11 @@ "devDependencies": { "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/core": "0.0.0", @@ -91,7 +93,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-iot/test/integ.topic-rule.expected.json b/packages/@aws-cdk/aws-iot/test/integ.topic-rule.expected.json new file mode 100644 index 0000000000000..9daad98410825 --- /dev/null +++ b/packages/@aws-cdk/aws-iot/test/integ.topic-rule.expected.json @@ -0,0 +1,20 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Http": { + "Url": "https://example.com" + } + } + ], + "AwsIotSqlVersion": "2015-10-08", + "Sql": "SELECT topic(2) as device_id FROM 'device/+/data'" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot/test/integ.topic-rule.ts b/packages/@aws-cdk/aws-iot/test/integ.topic-rule.ts new file mode 100644 index 0000000000000..0f4bab54a9d2a --- /dev/null +++ b/packages/@aws-cdk/aws-iot/test/integ.topic-rule.ts @@ -0,0 +1,27 @@ +/// !cdk-integ pragma:ignore-assets +import * as cdk from '@aws-cdk/core'; +import * as iot from '../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [ + { + bind: () => ({ + configuration: { + http: { url: 'https://example.com' }, + }, + }), + }, + ], + }); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot/test/topic-rule.test.ts b/packages/@aws-cdk/aws-iot/test/topic-rule.test.ts new file mode 100644 index 0000000000000..5f84201a37e5d --- /dev/null +++ b/packages/@aws-cdk/aws-iot/test/topic-rule.test.ts @@ -0,0 +1,282 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import * as iot from '../lib'; + +test('Default property', () => { + const stack = new cdk.Stack(); + + new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [], + Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'", + }, + }); +}); + +test('can get topic rule name', () => { + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + new cdk.CfnResource(stack, 'Res', { + type: 'Test::Resource', + properties: { + TopicRuleName: topicRule.topicRuleName, + }, + }); + + Template.fromStack(stack).hasResourceProperties('Test::Resource', { + TopicRuleName: { Ref: 'MyTopicRule4EC2091C' }, + }); +}); + +test('can get topic rule arn', () => { + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + new cdk.CfnResource(stack, 'Res', { + type: 'Test::Resource', + properties: { + TopicRuleArn: topicRule.topicRuleArn, + }, + }); + + Template.fromStack(stack).hasResourceProperties('Test::Resource', { + TopicRuleArn: { + 'Fn::GetAtt': ['MyTopicRule4EC2091C', 'Arn'], + }, + }); +}); + +test('can set physical name', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new iot.TopicRule(stack, 'MyTopicRule', { + topicRuleName: 'PhysicalName', + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + RuleName: 'PhysicalName', + }); +}); + +test('can set description', () => { + const stack = new cdk.Stack(); + + new iot.TopicRule(stack, 'MyTopicRule', { + description: 'test-description', + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Description: 'test-description', + }, + }); +}); + +test('can set ruleDisabled', () => { + const stack = new cdk.Stack(); + + new iot.TopicRule(stack, 'MyTopicRule', { + enabled: false, + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + RuleDisabled: true, + }, + }); +}); + +test.each([ + ['fromStringAsVer20151008', iot.IotSql.fromStringAsVer20151008, '2015-10-08'], + ['fromStringAsVer20160323', iot.IotSql.fromStringAsVer20160323, '2016-03-23'], + ['fromStringAsVerNewestUnstable', iot.IotSql.fromStringAsVerNewestUnstable, 'beta'], +])('can set sql with using %s', (_, factoryMethod, version) => { + const stack = new cdk.Stack(); + + new iot.TopicRule(stack, 'MyTopicRule', { + sql: factoryMethod("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + AwsIotSqlVersion: version, + Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'", + }, + }); +}); + +test.each([ + ['fromStringAsVer20151008', iot.IotSql.fromStringAsVer20151008], + ['fromStringAsVer20160323', iot.IotSql.fromStringAsVer20160323], + ['fromStringAsVerNewestUnstable', iot.IotSql.fromStringAsVerNewestUnstable], +])('Using %s fails when setting empty sql', (_, factoryMethod) => { + expect(() => { + factoryMethod(''); + }).toThrow('IoT SQL string cannot be empty'); +}); + +test('can set actions', () => { + const stack = new cdk.Stack(); + + const action1: iot.IAction = { + bind: () => ({ + configuration: { + http: { url: 'http://example.com' }, + }, + }), + }; + const action2: iot.IAction = { + bind: () => ({ + configuration: { + lambda: { functionArn: 'test-functionArn' }, + }, + }), + }; + + new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + actions: [action1, action2], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { Http: { Url: 'http://example.com' } }, + { Lambda: { FunctionArn: 'test-functionArn' } }, + ], + Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'", + }, + }); +}); + +test('can add an action', () => { + const stack = new cdk.Stack(); + + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + topicRule.addAction({ + bind: () => ({ + configuration: { + http: { url: 'http://example.com' }, + }, + }), + }); + topicRule.addAction({ + bind: () => ({ + configuration: { + lambda: { functionArn: 'test-functionArn' }, + }, + }), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { Http: { Url: 'http://example.com' } }, + { Lambda: { FunctionArn: 'test-functionArn' } }, + ], + Sql: "SELECT topic(2) as device_id, temperature FROM 'device/+/data'", + }, + }); +}); + +test('cannot add an action as empty object', () => { + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + const emptyKeysAction: iot.IAction = { + bind: () => ({ + configuration: {}, + }), + }; + + expect(() => { + topicRule.addAction(emptyKeysAction); + }).toThrow('An action property cannot be an empty object.'); +}); + +test('cannot add an action that have multiple keys', () => { + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + const multipleKeysAction: iot.IAction = { + bind: () => ({ + configuration: { + http: { url: 'http://example.com' }, + lambda: { functionArn: 'test-functionArn' }, + }, + }), + }; + + expect(() => { + topicRule.addAction(multipleKeysAction); + }).toThrow('An action property cannot have multiple keys, received: http,lambda'); +}); + +test('can set errorAction', () => { + const stack = new cdk.Stack(); + + const action: iot.IAction = { + bind: () => ({ + configuration: { + http: { url: 'http://example.com' }, + }, + }), + }; + + new iot.TopicRule(stack, 'MyTopicRule', { + errorAction: action, + sql: iot.IotSql.fromStringAsVer20151008("SELECT topic(2) as device_id, temperature FROM 'device/+/data'"), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + ErrorAction: { + Http: { Url: 'http://example.com' }, + }, + }, + }); +}); + +test('can import a TopicRule by ARN', () => { + const stack = new cdk.Stack(); + + const topicRuleArn = 'arn:aws:iot:ap-northeast-1:123456789012:rule/my-topic-rule-name'; + + const topicRule = iot.TopicRule.fromTopicRuleArn(stack, 'TopicRuleFromArn', topicRuleArn); + + expect(topicRule).toMatchObject({ + topicRuleArn, + topicRuleName: 'my-topic-rule-name', + }); +}); + +test('fails importing a TopicRule by ARN if the ARN is missing the name of the TopicRule', () => { + const stack = new cdk.Stack(); + + const topicRuleArn = 'arn:aws:iot:ap-northeast-1:123456789012:rule/'; + + expect(() => { + iot.TopicRule.fromTopicRuleArn(stack, 'TopicRuleFromArn', topicRuleArn); + }).toThrow("Missing topic rule name in ARN: 'arn:aws:iot:ap-northeast-1:123456789012:rule/'"); +}); diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index eda66e8a3a082..e08bd2197a70a 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 37630cf29c02f..c1a2fb2404f99 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-iotcoredeviceadvisor/package.json b/packages/@aws-cdk/aws-iotcoredeviceadvisor/package.json index 45422d829b797..da47e538939c6 100644 --- a/packages/@aws-cdk/aws-iotcoredeviceadvisor/package.json +++ b/packages/@aws-cdk/aws-iotcoredeviceadvisor/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-iotevents/package.json b/packages/@aws-cdk/aws-iotevents/package.json index 859cb5169f16a..50b9f8f02b5d8 100644 --- a/packages/@aws-cdk/aws-iotevents/package.json +++ b/packages/@aws-cdk/aws-iotevents/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-iotfleethub/package.json b/packages/@aws-cdk/aws-iotfleethub/package.json index f0c4a45a394a0..c05eb0a4e3463 100644 --- a/packages/@aws-cdk/aws-iotfleethub/package.json +++ b/packages/@aws-cdk/aws-iotfleethub/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-iotsitewise/package.json b/packages/@aws-cdk/aws-iotsitewise/package.json index ccf98918699ad..619858772f08f 100644 --- a/packages/@aws-cdk/aws-iotsitewise/package.json +++ b/packages/@aws-cdk/aws-iotsitewise/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-iotthingsgraph/package.json b/packages/@aws-cdk/aws-iotthingsgraph/package.json index 5e2833e52494b..3f6774f372140 100644 --- a/packages/@aws-cdk/aws-iotthingsgraph/package.json +++ b/packages/@aws-cdk/aws-iotthingsgraph/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-iotwireless/package.json b/packages/@aws-cdk/aws-iotwireless/package.json index 16e1afdc9ae88..e1acaffcb83a8 100644 --- a/packages/@aws-cdk/aws-iotwireless/package.json +++ b/packages/@aws-cdk/aws-iotwireless/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-ivs/lib/channel.ts b/packages/@aws-cdk/aws-ivs/lib/channel.ts index 34d9228bc787c..0768847e4b668 100644 --- a/packages/@aws-cdk/aws-ivs/lib/channel.ts +++ b/packages/@aws-cdk/aws-ivs/lib/channel.ts @@ -117,7 +117,7 @@ export class Channel extends ChannelBase { */ public static fromChannelArn(scope: Construct, id: string, channelArn: string): IChannel { // This will throw an error if the arn cannot be parsed - let arnComponents = core.Arn.parse(channelArn); + let arnComponents = core.Arn.split(channelArn, core.ArnFormat.SLASH_RESOURCE_NAME); if (!core.Token.isUnresolved(arnComponents.service) && arnComponents.service !== 'ivs') { throw new Error(`Invalid service, expected 'ivs', got '${arnComponents.service}'`); diff --git a/packages/@aws-cdk/aws-ivs/package.json b/packages/@aws-cdk/aws-ivs/package.json index 922a5672829a6..a1f452ba38783 100644 --- a/packages/@aws-cdk/aws-ivs/package.json +++ b/packages/@aws-cdk/aws-ivs/package.json @@ -89,7 +89,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-kendra/package.json b/packages/@aws-cdk/aws-kendra/package.json index 8b2a1b20396e1..94a8499035a1c 100644 --- a/packages/@aws-cdk/aws-kendra/package.json +++ b/packages/@aws-cdk/aws-kendra/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-kinesis/README.md b/packages/@aws-cdk/aws-kinesis/README.md index 1e7aaf89a5a88..75831cb52b1ed 100644 --- a/packages/@aws-cdk/aws-kinesis/README.md +++ b/packages/@aws-cdk/aws-kinesis/README.md @@ -34,8 +34,8 @@ Using the CDK, a new Kinesis stream can be created as part of the stack using th your own identifier to the stream. If not, CloudFormation will generate a name. ```ts -new Stream(this, "MyFirstStream", { - streamName: "my-awesome-stream" +new kinesis.Stream(this, 'MyFirstStream', { + streamName: 'my-awesome-stream', }); ``` @@ -44,10 +44,10 @@ to specify how long the data in the shards should remain accessible. Read more at [Creating and Managing Streams](https://docs.aws.amazon.com/streams/latest/dev/working-with-streams.html) ```ts -new Stream(this, "MyFirstStream", { - streamName: "my-awesome-stream", +new kinesis.Stream(this, 'MyFirstStream', { + streamName: 'my-awesome-stream', shardCount: 3, - retentionPeriod: Duration.hours(48) + retentionPeriod: Duration.hours(48), }); ``` @@ -59,28 +59,26 @@ server-side encryption using an AWS KMS key for a specified stream. Encryption is enabled by default on your stream with the master key owned by Kinesis Data Streams in regions where it is supported. ```ts -new Stream(this, 'MyEncryptedStream'); +new kinesis.Stream(this, 'MyEncryptedStream'); ``` You can enable encryption on your stream with a user-managed key by specifying the `encryption` property. A KMS key will be created for you and associated with the stream. ```ts -new Stream(this, "MyEncryptedStream", { - encryption: StreamEncryption.KMS +new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); ``` You can also supply your own external KMS key to use for stream encryption by specifying the `encryptionKey` property. ```ts -import * as kms from "@aws-cdk/aws-kms"; +const key = new kms.Key(this, 'MyKey'); -const key = new kms.Key(this, "MyKey"); - -new Stream(this, "MyEncryptedStream", { - encryption: StreamEncryption.KMS, - encryptionKey: key +new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, + encryptionKey: key, }); ``` @@ -91,32 +89,20 @@ Any Kinesis stream that has been created outside the stack can be imported into Streams can be imported by their ARN via the `Stream.fromStreamArn()` API ```ts -const stack = new Stack(app, "MyStack"); - -const importedStream = Stream.fromStreamArn( - stack, - "ImportedStream", - "arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j" +const importedStream = kinesis.Stream.fromStreamArn(this, 'ImportedStream', + 'arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j', ); ``` Encrypted Streams can also be imported by their attributes via the `Stream.fromStreamAttributes()` API ```ts -import { Key } from "@aws-cdk/aws-kms"; - -const stack = new Stack(app, "MyStack"); - -const importedStream = Stream.fromStreamAttributes( - stack, - "ImportedEncryptedStream", - { - streamArn: "arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j", - encryptionKey: kms.Key.fromKeyArn( - "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" - ) - } -); +const importedStream = kinesis.Stream.fromStreamAttributes(this, 'ImportedEncryptedStream', { + streamArn: 'arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j', + encryptionKey: kms.Key.fromKeyArn(this, 'key', + 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012', + ), +}); ``` ### Permission Grants @@ -138,10 +124,10 @@ If the stream has an encryption key, read permissions will also be granted to th const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), description: 'Example role...', -} +}); -const stream = new Stream(this, 'MyEncryptedStream', { - encryption: StreamEncryption.KMS +const stream = new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); // give lambda permissions to read stream @@ -165,10 +151,10 @@ If the stream has an encryption key, write permissions will also be granted to t const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), description: 'Example role...', -} +}); -const stream = new Stream(this, 'MyEncryptedStream', { - encryption: StreamEncryption.KMS +const stream = new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); // give lambda permissions to write to stream @@ -186,9 +172,9 @@ The following write permissions are provided to a service principal by the `gran You can add any set of permissions to a stream by calling the `grant()` API. ```ts -const user = new iam.User(stack, 'MyUser'); +const user = new iam.User(this, 'MyUser'); -const stream = new Stream(stack, 'MyStream'); +const stream = new kinesis.Stream(this, 'MyStream'); // give my user permissions to list shards stream.grant(user, 'kinesis:ListShards'); @@ -199,7 +185,7 @@ stream.grant(user, 'kinesis:ListShards'); You can use common metrics from your stream to create alarms and/or dashboards. The `stream.metric('MetricName')` method creates a metric with the stream namespace and dimension. You can also use pre-define methods like `stream.metricGetRecordsSuccess()`. To find out more about Kinesis metrics check [Monitoring the Amazon Kinesis Data Streams Service with Amazon CloudWatch](https://docs.aws.amazon.com/streams/latest/dev/monitoring-with-cloudwatch.html). ```ts -const stream = new Stream(stack, 'MyStream'); +const stream = new kinesis.Stream(this, 'MyStream'); // Using base metric method passing the metric name stream.metric('GetRecords.Success'); @@ -210,4 +196,3 @@ stream.metricGetRecordsSuccess(); // using pre-defined and overriding the statistic stream.metricGetRecordsSuccess({ statistic: 'Maximum' }); ``` - diff --git a/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts b/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts index a10434d947bbf..53404627c39ae 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts @@ -13,7 +13,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Bytes', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -21,7 +21,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Success', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -29,7 +29,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Records', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -37,7 +37,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -45,7 +45,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecord.Bytes', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -53,7 +53,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecord.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -70,7 +70,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -78,7 +78,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.Success', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -86,7 +86,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.TotalRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -94,7 +94,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.SuccessfulRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -102,7 +102,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.FailedRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -110,7 +110,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.ThrottledRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index b2fed1eb10329..03d693e8dfa4e 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Aws, CfnCondition, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Aws, CfnCondition, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { KinesisMetrics } from './kinesis-fixed-canned-metrics'; import { CfnStream } from './kinesis.generated'; @@ -12,6 +12,8 @@ const READ_OPERATIONS = [ 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ]; const WRITE_OPERATIONS = [ @@ -392,7 +394,7 @@ abstract class StreamBase extends Resource implements IStream { return new cloudwatch.Metric({ namespace: 'AWS/Kinesis', metricName, - dimensions: { + dimensionsMap: { StreamName: this.streamName, }, ...props, @@ -725,7 +727,7 @@ export class Stream extends StreamBase { public static fromStreamAttributes(scope: Construct, id: string, attrs: StreamAttributes): IStream { class Import extends StreamBase { public readonly streamArn = attrs.streamArn; - public readonly streamName = Stack.of(scope).parseArn(attrs.streamArn).resourceName!; + public readonly streamName = Stack.of(scope).splitArn(attrs.streamArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; public readonly encryptionKey = attrs.encryptionKey; } diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index b1464884fb740..1641e48f910f7 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..742f48088c404 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json b/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json index 15055271413a2..41230acc599a2 100644 --- a/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json +++ b/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json @@ -44,6 +44,8 @@ "kinesis:GetShardIterator", "kinesis:ListShards", "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams", "kinesis:PutRecord", "kinesis:PutRecords" ], diff --git a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts index 089261c6ebdae..7ab2c5eed849e 100644 --- a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts +++ b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts @@ -2,9 +2,9 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import { App, Duration, Stack, CfnParameter } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { Stream, StreamEncryption } from '../lib'; /* eslint-disable quote-props */ @@ -503,6 +503,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], Effect: 'Allow', Resource: { @@ -811,6 +813,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', 'kinesis:PutRecord', 'kinesis:PutRecords', ], @@ -884,6 +888,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], Effect: 'Allow', Resource: { @@ -1050,6 +1056,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', 'kinesis:PutRecord', 'kinesis:PutRecords', ], diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md index 7dc9b1a088b16..2882cc60afc54 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md @@ -37,16 +37,17 @@ aws-kinesisanalytics-runtime library to [retrieve these properties](https://docs.aws.amazon.com/kinesisanalytics/latest/java/how-properties.html#how-properties-access). ```ts -import * as flink from '@aws-cdk/aws-kinesisanalytics-flink'; - +declare const bucket: s3.Bucket; const flinkApp = new flink.Application(this, 'Application', { - // ... propertyGroups: { FlinkApplicationProperties: { inputStreamName: 'my-input-kinesis-stream', outputStreamName: 'my-output-kinesis-stream', }, }, + // ... + runtime: flink.Runtime.FLINK_1_13, + code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), }); ``` @@ -55,14 +56,13 @@ when the Flink job starts. These include parameters for checkpointing, snapshotting, monitoring, and parallelism. ```ts -import * as logs from '@aws-cdk/aws-logs'; - +declare const bucket: s3.Bucket; const flinkApp = new flink.Application(this, 'Application', { code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), - runtime: file.Runtime.FLINK_1_11, + runtime: flink.Runtime.FLINK_1_13, checkpointingEnabled: true, // default is true - checkpointInterval: cdk.Duration.seconds(30), // default is 1 minute - minPausesBetweenCheckpoints: cdk.Duration.seconds(10), // default is 5 seconds + checkpointInterval: Duration.seconds(30), // default is 1 minute + minPauseBetweenCheckpoints: Duration.seconds(10), // default is 5 seconds logLevel: flink.LogLevel.ERROR, // default is INFO metricsLevel: flink.MetricsLevel.PARALLELISM, // default is APPLICATION autoScalingEnabled: false, // default is true diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts index 5091d39c7f959..4a0f3bfc36138 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts @@ -227,7 +227,7 @@ export class Application extends ApplicationBase { * applicationArn. */ public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication { - const applicationName = core.Stack.of(scope).parseArn(applicationArn).resourceName; + const applicationName = core.Stack.of(scope).splitArn(applicationArn, core.ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!applicationName) { throw new Error(`applicationArn for fromApplicationArn (${applicationArn}) must include resource name`); } @@ -296,7 +296,7 @@ export class Application extends ApplicationBase { core.Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: core.ArnFormat.COLON_RESOURCE_NAME, resourceName: '*', }), ], diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts index b92d55cb48518..02e56eee10966 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/types.ts @@ -37,7 +37,7 @@ export enum MetricsLevel { * configuration. */ export interface PropertyGroups { - readonly [propertyId: string]: {[mapKey: string]: string}; + readonly [propertyId: string]: { [mapKey: string]: string }; } /** @@ -53,6 +53,9 @@ export class Runtime { /** Flink Version 1.11 */ public static readonly FLINK_1_11 = Runtime.of('FLINK-1_11'); + /** Flink Version 1.13 */ + public static readonly FLINK_1_13 = Runtime.of('FLINK-1_13'); + /** Create a new Runtime with with an arbitrary Flink version string */ public static of(value: string) { return new Runtime(value); diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json index f54538e7a374f..1f4a026686416 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -70,8 +77,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..a9f46e29f793b --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as flink from '@aws-cdk/aws-kinesisanalytics-flink'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} \ 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 1e345f2fdb47b..36a2b60ede671 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,7 +86,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md b/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md index 03ef4657b3f78..3873a6a493052 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md @@ -20,3 +20,7 @@ delivery stream. Destinations can be added by specifying the `destinations` prop defining a delivery stream. See [Amazon Kinesis Data Firehose module README](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-kinesisfirehose-readme.html) for usage examples. + +```ts nofixture +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +``` diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json b/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json index 36c981f2c54cd..a872462d82cf8 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json @@ -32,7 +32,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": true + "strict": true } } } @@ -78,8 +78,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture index fe46e06908b34..f48bd7e013c59 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture @@ -1,8 +1,9 @@ // Fixture with packages imported, but nothing else -import { Construct } from '@aws-cdk/core'; -import { S3Bucket } from '@aws-cdk/aws-kinesisfirehose-destinations'; +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -class Fixture extends Construct { +class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/aws-kinesisfirehose/README.md b/packages/@aws-cdk/aws-kinesisfirehose/README.md index 936fcdc881a86..2db9f79e6ba30 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/README.md +++ b/packages/@aws-cdk/aws-kinesisfirehose/README.md @@ -44,11 +44,8 @@ In order to define a Delivery Stream, you must specify a destination. An S3 buck used as a destination. More supported destinations are covered [below](#destinations). ```ts -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as s3 from '@aws-cdk/aws-s3'; - const bucket = new s3.Bucket(this, 'Bucket'); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [new destinations.S3Bucket(bucket)], }); ``` @@ -74,11 +71,10 @@ A delivery stream can read directly from a Kinesis data stream as a consumer of stream. Configure this behaviour by providing a data stream in the `sourceStream` property when constructing a delivery stream: -```ts fixture=with-destination -import * as kinesis from '@aws-cdk/aws-kinesis'; - +```ts +declare const destination: firehose.IDestination; const sourceStream = new kinesis.Stream(this, 'Source Stream'); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { sourceStream: sourceStream, destinations: [destination], }); @@ -113,14 +109,10 @@ for the implementations of these destinations. Defining a delivery stream with an S3 bucket destination: ```ts -import * as s3 from '@aws-cdk/aws-s3'; -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; - -const bucket = new s3.Bucket(this, 'Bucket'); - +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [s3Destination], }); ``` @@ -129,7 +121,8 @@ The S3 destination also supports custom dynamic prefixes. `prefix` will be used successfully delivered to S3. `errorOutputPrefix` will be added to failed records before writing them to S3. -```ts fixture=with-bucket +```ts +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { dataOutputPrefix: 'myFirehose/DeliveredYear=!{timestamp:yyyy}/anyMonth/rand=!{firehose:random-string}', errorOutputPrefix: 'myFirehoseFailures/!{firehose:error-output-type}/!{timestamp:yyyy}/anyMonth/!{timestamp:dd}', @@ -158,22 +151,22 @@ access, rotation, aliases, and deletion for these keys, and you are changed for use. See: [Customer master keys](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#master_keys) in the *KMS Developer Guide*. -```ts fixture=with-destination -import * as kms from '@aws-cdk/aws-kms'; +```ts +declare const destination: firehose.IDestination; // SSE with an AWS-owned CMK -new DeliveryStream(this, 'Delivery Stream AWS Owned', { - encryption: StreamEncryption.AWS_OWNED, +new firehose.DeliveryStream(this, 'Delivery Stream AWS Owned', { + encryption: firehose.StreamEncryption.AWS_OWNED, destinations: [destination], }); // SSE with an customer-managed CMK that is created automatically by the CDK -new DeliveryStream(this, 'Delivery Stream Implicit Customer Managed', { - encryption: StreamEncryption.CUSTOMER_MANAGED, +new firehose.DeliveryStream(this, 'Delivery Stream Implicit Customer Managed', { + encryption: firehose.StreamEncryption.CUSTOMER_MANAGED, destinations: [destination], }); // SSE with an customer-managed CMK that is explicitly specified -const key = new kms.Key(this, 'Key'); -new DeliveryStream(this, 'Delivery Stream Explicit Customer Managed', { +declare const key: kms.Key; +new firehose.DeliveryStream(this, 'Delivery Stream Explicit Customer Managed', { encryptionKey: key, destinations: [destination], }); @@ -196,28 +189,29 @@ and LogStream for your Delivery Stream. You can provide a specific log group to specify where the CDK will create the log streams where log events will be sent: -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +```ts import * as logs from '@aws-cdk/aws-logs'; const logGroup = new logs.LogGroup(this, 'Log Group'); +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { logGroup: logGroup, }); -new DeliveryStream(this, 'Delivery Stream', { + +declare const destination: firehose.IDestination; +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` Logging can also be disabled: -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; - +```ts +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { logging: false, }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -242,8 +236,9 @@ for a full list). CDK also provides a generic `metric` method that can be used t metric configurations for any metric provided by Kinesis Data Firehose; the configurations are pre-populated with the correct dimensions for the delivery stream. -```ts fixture=with-delivery-stream +```ts import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +declare const deliveryStream: firehose.DeliveryStream; // Alarm that triggers when the per-second average of incoming bytes exceeds 90% of the current service limit const incomingBytesPercentOfLimit = new cloudwatch.MathExpression({ @@ -253,6 +248,7 @@ const incomingBytesPercentOfLimit = new cloudwatch.MathExpression({ bytePerSecLimit: deliveryStream.metric('BytesPerSecondLimit'), }, }); + new cloudwatch.Alarm(this, 'Alarm', { metric: incomingBytesPercentOfLimit, threshold: 0.9, @@ -271,13 +267,14 @@ Hadoop-compatible Snappy, and ZIP, except for Redshift destinations, where Snapp (regardless of Hadoop-compatibility) and ZIP are not supported. By default, data is delivered to S3 without compression. -```ts fixture=with-bucket +```ts // Compress data delivered to S3 using Snappy +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { - compression: Compression.SNAPPY, + compression: destinations.Compression.SNAPPY, }); -new DeliveryStream(this, 'Delivery Stream', { - destinations: [destination], +new firehose.DeliveryStream(this, 'Delivery Stream', { + destinations: [s3Destination], }); ``` @@ -290,15 +287,14 @@ threshold (the "buffer interval"), whichever happens first. You can configure th thresholds based on the capabilities of the destination and your use-case. By default, the buffer size is 5 MiB and the buffer interval is 5 minutes. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; - +```ts // Increase the buffer interval and size to 10 minutes and 8 MiB, respectively +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { - bufferingInterval: cdk.Duration.minutes(10), - bufferingSize: cdk.Size.mebibytes(8), + bufferingInterval: Duration.minutes(10), + bufferingSize: Size.mebibytes(8), }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -315,14 +311,13 @@ in Amazon S3. You can choose to not encrypt the data or to encrypt with a key fr the list of AWS KMS keys that you own. For more information, see [Protecting Data Using Server-Side Encryption with AWS KMS–Managed Keys (SSE-KMS)](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html). Data is not encrypted by default. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; -import * as kms from '@aws-cdk/aws-kms'; - +```ts +declare const bucket: s3.Bucket; +declare const key: kms.Key; const destination = new destinations.S3Bucket(bucket, { - encryptionKey: new kms.Key(this, 'MyKey'), + encryptionKey: key, }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -337,37 +332,35 @@ you can provide a bucket where data will be backed up. You can also provide a pr which your backed-up data will be placed within the bucket. By default, source data is not backed up to S3. -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as s3 from '@aws-cdk/aws-s3'; - +```ts // Enable backup of all source records (to an S3 bucket created by CDK). -new DeliveryStream(this, 'Delivery Stream Backup All', { +declare const bucket: s3.Bucket; +new firehose.DeliveryStream(this, 'Delivery Stream Backup All', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { - mode: BackupMode.ALL, - } + mode: destinations.BackupMode.ALL, + }, }), ], }); // Explicitly provide an S3 bucket to which all source records will be backed up. -const backupBucket = new s3.Bucket(this, 'Bucket'); -new DeliveryStream(this, 'Delivery Stream Backup All Explicit Bucket', { +declare const backupBucket: s3.Bucket; +new firehose.DeliveryStream(this, 'Delivery Stream Backup All Explicit Bucket', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { bucket: backupBucket, - } + }, }), ], }); // Explicitly provide an S3 prefix under which all source records will be backed up. -new DeliveryStream(this, 'Delivery Stream Backup All Explicit Prefix', { +new firehose.DeliveryStream(this, 'Delivery Stream Backup All Explicit Prefix', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { - mode: BackupMode.ALL, + mode: destinations.BackupMode.ALL, dataOutputPrefix: 'mybackup', }, }), @@ -405,10 +398,7 @@ configuration (see: [Buffering](#buffering)). If the function invocation fails d network timeout or because of hitting an invocation limit, the invocation is retried 3 times by default, but can be configured using `retries` in the processor configuration. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; -import * as lambda from '@aws-cdk/aws-lambda'; - +```ts // Provide a Lambda function that will transform records before delivery, with custom // buffering and retry configuration const lambdaFunction = new lambda.Function(this, 'Processor', { @@ -416,16 +406,17 @@ const lambdaFunction = new lambda.Function(this, 'Processor', { handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'process-records')), }); -const lambdaProcessor = new LambdaFunctionProcessor(lambdaFunction, { - bufferingInterval: cdk.Duration.minutes(5), - bufferingSize: cdk.Size.mebibytes(5), +const lambdaProcessor = new firehose.LambdaFunctionProcessor(lambdaFunction, { + bufferInterval: Duration.minutes(5), + bufferSize: Size.mebibytes(5), retries: 5, }); +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { processor: lambdaProcessor, }); -new DeliveryStream(this, 'Delivery Stream', { - destinations: [destination], +new firehose.DeliveryStream(this, 'Delivery Stream', { + destinations: [s3Destination], }); ``` @@ -449,10 +440,7 @@ allow Kinesis Data Firehose to assume it) or delivery stream creation or data de will fail. Other required permissions to destination resources, encryption keys, etc., will be provided automatically. -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as iam from '@aws-cdk/aws-iam'; - +```ts // Create service roles for the delivery stream and destination. // These can be used for other purposes and granted access to different resources. // They must include the Kinesis Data Firehose service principal in their trust policies. @@ -465,8 +453,9 @@ const destinationRole = new iam.Role(this, 'Destination Role', { }); // Specify the roles created above when defining the destination and delivery stream. +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { role: destinationRole }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], role: deliveryStreamRole, }); @@ -488,14 +477,13 @@ can be granted permissions to a delivery stream by calling: - `grant(principal, ...actions)` - grants the principal permission to a custom set of actions -```ts fixture=with-delivery-stream -import * as iam from '@aws-cdk/aws-iam'; - +```ts const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); // Give the role permissions to write data to the delivery stream +declare const deliveryStream: firehose.DeliveryStream; deliveryStream.grantPutRecords(lambdaRole); ``` @@ -514,15 +502,14 @@ found in the `@aws-cdk/aws-kinesisfirehose-destinations` module, the CDK grants permissions automatically. However, custom or third-party destinations may require custom permissions. In this case, use the delivery stream as an `IGrantable`, as follows: -```ts fixture=with-delivery-stream -import * as lambda from '@aws-cdk/aws-lambda'; - +```ts const fn = new lambda.Function(this, 'Function', { code: lambda.Code.fromInline('exports.handler = (event) => {}'), runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', }); +declare const deliveryStream: firehose.DeliveryStream; fn.grantInvoke(deliveryStream); ``` diff --git a/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts b/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts index 7dfaed8eb384b..c9608904e9373 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts +++ b/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts @@ -124,7 +124,7 @@ abstract class DeliveryStreamBase extends cdk.Resource implements IDeliveryStrea return new cloudwatch.Metric({ namespace: 'AWS/Firehose', metricName: metricName, - dimensions: { + dimensionsMap: { DeliveryStreamName: this.deliveryStreamName, }, ...props, @@ -358,13 +358,6 @@ export class DeliveryStream extends DeliveryStreamBase { roleArn: role.roleArn, } : undefined; const readStreamGrant = props.sourceStream?.grantRead(role); - /* - * Firehose still uses the deprecated DescribeStream API instead of the modern DescribeStreamSummary API. - * kinesis.IStream.grantRead does not provide DescribeStream permissions so we add it manually here. - */ - if (readStreamGrant && readStreamGrant.principalStatement) { - readStreamGrant.principalStatement.addActions('kinesis:DescribeStream'); - } const destinationConfig = props.destinations[0].bind(this, {}); diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index abaabc25fc1c7..64c30be1210e3 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture index 8a68efc25aa8e..9585eb9368d19 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture @@ -1,6 +1,14 @@ // Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; +import { Construct } from 'constructs'; +import { Duration, Size, Stack } from '@aws-cdk/core'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as path from 'path'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture deleted file mode 100644 index d0851cff49639..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture with a bucket already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -import * as s3 from '@aws-cdk/aws-s3'; -declare const bucket: s3.Bucket; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture deleted file mode 100644 index c7b75b20d2c1b..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a delivery stream already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const deliveryStream: DeliveryStream; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture deleted file mode 100644 index 37d78bf7a43d3..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a destination already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const destination: IDestination; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json b/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json index eb46541a1cdf2..896d0487a091c 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json @@ -119,7 +119,8 @@ "kinesis:GetShardIterator", "kinesis:ListShards", "kinesis:SubscribeToShard", - "kinesis:DescribeStream" + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index 098e643440784..201accf122675 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -14,10 +14,8 @@ Define a KMS key: ```ts -import * as kms from '@aws-cdk/aws-kms'; - new kms.Key(this, 'MyKey', { - enableKeyRotation: true + enableKeyRotation: true, }); ``` @@ -27,7 +25,7 @@ Specifies the number of days in the waiting period before AWS KMS deletes a CMK ```ts const key = new kms.Key(this, 'MyKey', { - pendingWindow: 10 // Default to 30 Days + pendingWindow: Duration.days(10), // Default to 30 Days }); ``` @@ -48,7 +46,7 @@ Valid `keySpec` values depends on `keyUsage` value. ```ts const key = new kms.Key(this, 'MyKey', { keySpec: kms.KeySpec.ECC_SECG_P256K1, // Default to SYMMETRIC_DEFAULT - keyUsage: kms.KeyUsage.SIGN_VERIFY // and ENCRYPT_DECRYPT + keyUsage: kms.KeyUsage.SIGN_VERIFY, // and ENCRYPT_DECRYPT }); ``` @@ -84,10 +82,12 @@ If a Key has an associated Alias, the Alias can be imported by name and used in of the Key as a reference. A common scenario for this is in referencing AWS managed keys. ```ts +import * as cloudtrail from '@aws-cdk/aws-cloudtrail'; + const myKeyAlias = kms.Alias.fromAliasName(this, 'myKey', 'alias/aws/s3'); const trail = new cloudtrail.Trail(this, 'myCloudTrail', { - sendToCloudWatchLogs: true, - kmsKey: myKeyAlias + sendToCloudWatchLogs: true, + kmsKey: myKeyAlias, }); ``` @@ -110,7 +110,7 @@ Here's how `Key.fromLookup()` can be used: ```ts const myKeyLookup = kms.Key.fromLookup(this, 'MyKeyLookup', { - aliasName: 'alias/KeyAlias' + aliasName: 'alias/KeyAlias', }); const role = new iam.Role(this, 'MyRole', { @@ -138,7 +138,7 @@ which is set for all new projects; for existing projects, this same behavior can passing the `trustAccountIdentities` property as `true` when creating the key: ```ts -new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); +new kms.Key(this, 'MyKey', { trustAccountIdentities: true }); ``` With either the `@aws-cdk/aws-kms:defaultKeyPolicies` feature flag set, @@ -158,8 +158,8 @@ This enables the root account user -- via IAM policies -- to grant access to oth With the above default policy, future permissions can be added to either the key policy or IAM principal policy. ```ts -const key = new kms.Key(stack, 'MyKey'); -const user = new iam.User(stack, 'MyUser'); +const key = new kms.Key(this, 'MyKey'); +const user = new iam.User(this, 'MyUser'); key.grantEncrypt(user); // Adds encrypt permissions to user policy; key policy is unmodified. ``` @@ -177,12 +177,12 @@ A common addition to the key policy would be to add other key admins that are al via the `grantAdmin` method. ```ts -const myTrustedAdminRole = iam.Role.fromRoleArn(stack, 'TrustedRole', 'arn:aws:iam:....'); -const key = new kms.Key(stack, 'MyKey', { +const myTrustedAdminRole = iam.Role.fromRoleArn(this, 'TrustedRole', 'arn:aws:iam:....'); +const key = new kms.Key(this, 'MyKey', { admins: [myTrustedAdminRole], }); -const secondKey = new kms.Key(stack, 'MyKey2'); +const secondKey = new kms.Key(this, 'MyKey2'); secondKey.grantAdmin(myTrustedAdminRole); ``` @@ -193,7 +193,7 @@ and with `trustedAccountIdentities` set to false (the default), specifying a pol provided policy to the default key policy, rather than _replacing_ the default policy. ```ts -const myTrustedAdminRole = iam.Role.fromRoleArn(stack, 'TrustedRole', 'arn:aws:iam:....'); +const myTrustedAdminRole = iam.Role.fromRoleArn(this, 'TrustedRole', 'arn:aws:iam:....'); // Creates a limited admin policy and assigns to the account root. const myCustomPolicy = new iam.PolicyDocument({ statements: [new iam.PolicyStatement({ @@ -208,7 +208,7 @@ const myCustomPolicy = new iam.PolicyDocument({ resources: ['*'], })], }); -const key = new kms.Key(stack, 'MyKey', { +const key = new kms.Key(this, 'MyKey', { policy: myCustomPolicy, }); ``` diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 8c726958f500e..62f0ad7ddf0a0 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration, Token, ContextProvider, Arn } from '@aws-cdk/core'; +import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration, Token, ContextProvider, Arn, ArnFormat } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Construct } from 'constructs'; import { Alias } from './alias'; @@ -479,7 +479,7 @@ export class Key extends KeyBase { } } - const keyResourceName = Stack.of(scope).parseArn(keyArn).resourceName; + const keyResourceName = Stack.of(scope).splitArn(keyArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!keyResourceName) { throw new Error(`KMS key ARN must be in the format 'arn:aws:kms:::key/', got: '${keyArn}'`); } diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 74fd0a5f586aa..e38c467b3b4f7 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/monocdk/rosetta/with-channel.ts-fixture b/packages/@aws-cdk/aws-kms/rosetta/default.ts-fixture similarity index 55% rename from packages/monocdk/rosetta/with-channel.ts-fixture rename to packages/@aws-cdk/aws-kms/rosetta/default.ts-fixture index 44da118b81afa..3e5ab4d8a6ab9 100644 --- a/packages/monocdk/rosetta/with-channel.ts-fixture +++ b/packages/@aws-cdk/aws-kms/rosetta/default.ts-fixture @@ -1,15 +1,14 @@ // Fixture with packages imported, but nothing else -import { Duration, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import * as ivs from '@aws-cdk/aws-ivs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const myChannelArn = 'arn:aws:ivs:us-west-2:123456789012:channel/abcdABCDefgh'; - const myChannel = ivs.Channel.fromChannelArn(this, 'Channel', myChannelArn); - /// here } -} \ No newline at end of file +} + diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index fae1564223d45..5a42d030bf36a 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -1,9 +1,9 @@ import { arrayWith, countResources, expect as expectCdk, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as kms from '../lib'; const ADMIN_ACTIONS: string[] = [ @@ -450,10 +450,31 @@ testFutureBehavior('setting pendingWindow value to not in allowed range will thr .toThrow('\'pendingWindow\' value must between 7 and 30 days. Received: 6'); }); -testFutureBehavior('setting trustAccountIdentities to false will throw (when the defaultKeyPolicies feature flag is enabled)', flags, cdk.App, (app) => { - const stack = new cdk.Stack(app); - expect(() => new kms.Key(stack, 'MyKey', { trustAccountIdentities: false })) - .toThrow('`trustAccountIdentities` cannot be false if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set'); +describeDeprecated('trustAccountIdentities is deprecated', () => { + testFutureBehavior('setting trustAccountIdentities to false will throw (when the defaultKeyPolicies feature flag is enabled)', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + expect(() => new kms.Key(stack, 'MyKey', { trustAccountIdentities: false })) + .toThrow('`trustAccountIdentities` cannot be false if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set'); + }); + + testLegacyBehavior('trustAccountIdentities changes key policy to allow IAM control', cdk.App, (app) => { + const stack = new cdk.Stack(app); + new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] }, + }, + Resource: '*', + }, + ], + }, + }); + }); }); testFutureBehavior('addAlias creates an alias', flags, cdk.App, (app) => { @@ -989,25 +1010,6 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }); }); - testLegacyBehavior('trustAccountIdentities changes key policy to allow IAM control', cdk.App, (app) => { - const stack = new cdk.Stack(app); - new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); - expect(stack).toHaveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: 'kms:*', - Effect: 'Allow', - Principal: { - AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] }, - }, - Resource: '*', - }, - ], - }, - }); - }); - testLegacyBehavior('additional key admins can be specified (with imported/immutable principal)', cdk.App, (app) => { const stack = new cdk.Stack(app); const adminRole = iam.Role.fromRoleArn(stack, 'Admin', 'arn:aws:iam::123456789012:role/TrustedAdmin'); diff --git a/packages/@aws-cdk/aws-lakeformation/package.json b/packages/@aws-cdk/aws-lakeformation/package.json index 7609498bebed8..df8b5c9f6f22b 100644 --- a/packages/@aws-cdk/aws-lakeformation/package.json +++ b/packages/@aws-cdk/aws-lakeformation/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-destinations/README.md b/packages/@aws-cdk/aws-lambda-destinations/README.md index 404b0b3157adb..675ca0b175757 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/README.md +++ b/packages/@aws-cdk/aws-lambda-destinations/README.md @@ -24,15 +24,17 @@ The following destinations are supported Example with a SNS topic for successful invocations: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as destinations from '@aws-cdk/aws-lambda-destinations'; +// An sns topic for successful invocations of a lambda function import * as sns from '@aws-cdk/aws-sns'; const myTopic = new sns.Topic(this, 'Topic'); const myFn = new lambda.Function(this, 'Fn', { - // other props - onSuccess: new destinations.SnsDestination(myTopic) + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + // sns topic for successful invocations + onSuccess: new destinations.SnsDestination(myTopic), }) ``` @@ -71,27 +73,27 @@ In case of failure, the record contains the reason and error object: ```json { - "version": "1.0", - "timestamp": "2019-11-24T21:52:47.333Z", - "requestContext": { - "requestId": "8ea123e4-1db7-4aca-ad10-d9ca1234c1fd", - "functionArn": "arn:aws:lambda:sa-east-1:123456678912:function:event-destinations:$LATEST", - "condition": "RetriesExhausted", - "approximateInvokeCount": 3 - }, - "requestPayload": { - "Success": false - }, - "responseContext": { - "statusCode": 200, - "executedVersion": "$LATEST", - "functionError": "Handled" - }, - "responsePayload": { - "errorMessage": "Failure from event, Success = false, I am failing!", - "errorType": "Error", - "stackTrace": [ "exports.handler (/var/task/index.js:18:18)" ] - } + "version": "1.0", + "timestamp": "2019-11-24T21:52:47.333Z", + "requestContext": { + "requestId": "8ea123e4-1db7-4aca-ad10-d9ca1234c1fd", + "functionArn": "arn:aws:lambda:sa-east-1:123456678912:function:event-destinations:$LATEST", + "condition": "RetriesExhausted", + "approximateInvokeCount": 3 + }, + "requestPayload": { + "Success": false + }, + "responseContext": { + "statusCode": 200, + "executedVersion": "$LATEST", + "functionError": "Handled" + }, + "responsePayload": { + "errorMessage": "Failure from event, Success = false, I am failing!", + "errorType": "Error", + "stackTrace": [ "exports.handler (/var/task/index.js:18:18)" ] + } } ``` @@ -112,18 +114,17 @@ The `responseOnly` option of `LambdaDestination` allows to auto-extract the resp invocation record: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as destinations from '@aws-cdk/aws-lambda-destinations'; - -const destinationFn = new lambda.Function(this, 'Destination', { - // props -}); +// Auto-extract response payload with a lambda destination +declare const destinationFn: lambda.Function; const sourceFn = new lambda.Function(this, 'Source', { - // other props + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + // auto-extract on success onSuccess: new destinations.LambdaDestination(destinationFn, { - responseOnly: true // auto-extract - }); + responseOnly: true, + }), }) ``` diff --git a/packages/@aws-cdk/aws-lambda-destinations/package.json b/packages/@aws-cdk/aws-lambda-destinations/package.json index 66dea74b84119..12108477a91de 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/package.json +++ b/packages/@aws-cdk/aws-lambda-destinations/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -69,8 +76,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..fb7d525a027aa --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as destinations from '@aws-cdk/aws-lambda-destinations'; +import * as path from 'path'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index e1ba637699efd..9abd6e59d9669 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -24,12 +24,14 @@ sources regardless of the underlying mechanism they use. The following code sets up a lambda function with an SQS queue event source - ```ts -const fn = new lambda.Function(this, 'MyFunction', { /* ... */ }); +import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; +declare const fn: lambda.Function; const queue = new sqs.Queue(this, 'MyQueue'); -const eventSource = fn.addEventSource(new SqsEventSource(queue)); +const eventSource = new SqsEventSource(queue); +fn.addEventSource(eventSource); -const eventSourceId = eventSource.eventSourceId; +const eventSourceId = eventSource.eventSourceMappingId; ``` The `eventSourceId` property contains the event source id. This will be a @@ -58,16 +60,15 @@ behavior: * __enabled__: If the SQS event source mapping should be enabled. The default is true. ```ts -import * as sqs from '@aws-cdk/aws-sqs'; import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; -import { Duration } from '@aws-cdk/core'; const queue = new sqs.Queue(this, 'MyQueue', { - visibilityTimeout: Duration.seconds(30) // default, - receiveMessageWaitTime: Duration.seconds(20) // default + visibilityTimeout: Duration.seconds(30), // default, + receiveMessageWaitTime: Duration.seconds(20), // default }); +declare const fn: lambda.Function; -lambda.addEventSource(new SqsEventSource(queue, { +fn.addEventSource(new SqsEventSource(queue, { batchSize: 10, // default maxBatchingWindow: Duration.minutes(5), })); @@ -88,11 +89,12 @@ Amazon S3 to publish and which Lambda function to invoke. import * as s3 from '@aws-cdk/aws-s3'; import { S3EventSource } from '@aws-cdk/aws-lambda-event-sources'; -const bucket = new s3.Bucket(...); +const bucket = new s3.Bucket(this, 'mybucket'); +declare const fn: lambda.Function; -lambda.addEventSource(new S3EventSource(bucket, { +fn.addEventSource(new S3EventSource(bucket, { events: [ s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED ], - filters: [ { prefix: 'subdir/' } ] // optional + filters: [ { prefix: 'subdir/' } ], // optional })); ``` @@ -118,12 +120,13 @@ Accounts](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html). import * as sns from '@aws-cdk/aws-sns'; import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources'; -const topic = new sns.Topic(...); +declare const topic: sns.Topic; const deadLetterQueue = new sqs.Queue(this, 'deadLetterQueue'); -lambda.addEventSource(new SnsEventSource(topic, { - filterPolicy: { ... }, - deadLetterQueue: deadLetterQueue +declare const fn: lambda.Function; +fn.addEventSource(new SnsEventSource(topic, { + filterPolicy: { }, + deadLetterQueue: deadLetterQueue, })); ``` @@ -157,24 +160,19 @@ and add it to your Lambda function. The following parameters will impact Amazon ```ts import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as sqs from '@aws-cdk/aws-sqs'; import { DynamoEventSource, SqsDlq } from '@aws-cdk/aws-lambda-event-sources'; -const table = new dynamodb.Table(..., { - partitionKey: ..., - stream: dynamodb.StreamViewType.NEW_IMAGE // make sure stream is configured -}); +declare const table: dynamodb.Table; const deadLetterQueue = new sqs.Queue(this, 'deadLetterQueue'); -const function = new lambda.Function(...); -function.addEventSource(new DynamoEventSource(table, { +declare const fn: lambda.Function; +fn.addEventSource(new DynamoEventSource(table, { startingPosition: lambda.StartingPosition.TRIM_HORIZON, batchSize: 5, bisectBatchOnError: true, onFailure: new SqsDlq(deadLetterQueue), - retryAttempts: 10 + retryAttempts: 10, })); ``` @@ -202,15 +200,15 @@ behavior: * __enabled__: If the DynamoDB Streams event source mapping should be enabled. The default is true. ```ts -import * as lambda from '@aws-cdk/aws-lambda'; import * as kinesis from '@aws-cdk/aws-kinesis'; import { KinesisEventSource } from '@aws-cdk/aws-lambda-event-sources'; const stream = new kinesis.Stream(this, 'MyStream'); +declare const myFunction: lambda.Function; myFunction.addEventSource(new KinesisEventSource(stream, { batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` @@ -222,27 +220,26 @@ The following code sets up Amazon MSK as an event source for a lambda function. MSK cluster, as described in [Username/Password authentication](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html). ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as msk from '@aws-cdk/aws-lambda'; -import { Secret } from '@aws-cdk/aws-secretmanager'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; import { ManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; // Your MSK cluster arn -const cluster = 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'; +const clusterArn = 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'; // The Kafka topic you want to subscribe to -const topic = 'some-cool-topic' +const topic = 'some-cool-topic'; // The secret that allows access to your MSK cluster // You still have to make sure that it is associated with your cluster as described in the documentation const secret = new Secret(this, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); +declare const myFunction: lambda.Function; myFunction.addEventSource(new ManagedKafkaEventSource({ clusterArn, topic: topic, secret: secret, batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` @@ -250,25 +247,25 @@ The following code sets up a self managed Kafka cluster as an event source. User will need to be set up as described in [Managing access and permissions](https://docs.aws.amazon.com/lambda/latest/dg/smaa-permissions.html#smaa-permissions-add-secret). ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import { Secret } from '@aws-cdk/aws-secretmanager'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; import { SelfManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; // The list of Kafka brokers -const bootstrapServers = ['kafka-broker:9092'] +const bootstrapServers = ['kafka-broker:9092']; // The Kafka topic you want to subscribe to -const topic = 'some-cool-topic' +const topic = 'some-cool-topic'; // The secret that allows access to your self hosted Kafka cluster -const secret = new Secret(this, 'Secret', { ... }); +declare const secret: Secret; +declare const myFunction: lambda.Function; myFunction.addEventSource(new SelfManagedKafkaEventSource({ bootstrapServers: bootstrapServers, topic: topic, secret: secret, batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts index f316ba69cf9ed..3d0fd78c915fc 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -1,6 +1,6 @@ import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Names } from '@aws-cdk/core'; +import { Names, Token } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; export interface DynamoEventSourceProps extends StreamEventSourceProps { @@ -15,7 +15,9 @@ export class DynamoEventSource extends StreamEventSource { constructor(private readonly table: dynamodb.ITable, props: DynamoEventSourceProps) { super(props); - if (this.props.batchSize !== undefined && (this.props.batchSize < 1 || this.props.batchSize > 1000)) { + if (this.props.batchSize !== undefined + && !Token.isUnresolved(this.props.batchSize) + && (this.props.batchSize < 1 || this.props.batchSize > 1000)) { throw new Error(`Maximum batch size must be between 1 and 1000 inclusive (given ${this.props.batchSize})`); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts index 117aae2b19b80..e31fac89100cb 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -3,7 +3,7 @@ import { ISecurityGroup, IVpc, SubnetSelection } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Stack } from '@aws-cdk/core'; +import { Stack, Names } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -49,6 +49,10 @@ export enum AuthenticationMethod { * SASL_SCRAM_256_AUTH authentication method for your Kafka cluster */ SASL_SCRAM_256_AUTH = 'SASL_SCRAM_256_AUTH', + /** + * BASIC_AUTH (SASL/PLAIN) authentication method for your Kafka cluster + */ + BASIC_AUTH = 'BASIC_AUTH', } /** @@ -97,6 +101,7 @@ export interface SelfManagedKafkaEventSourceProps extends KafkaEventSourceProps export class ManagedKafkaEventSource extends StreamEventSource { // This is to work around JSII inheritance problems private innerProps: ManagedKafkaEventSourceProps; + private _eventSourceMappingId?: string = undefined; constructor(props: ManagedKafkaEventSourceProps) { super(props); @@ -104,8 +109,8 @@ export class ManagedKafkaEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - target.addEventSourceMapping( - `KafkaEventSource:${this.innerProps.clusterArn}${this.innerProps.topic}`, + const eventSourceMapping = target.addEventSourceMapping( + `KafkaEventSource:${Names.nodeUniqueId(target.node)}${this.innerProps.topic}`, this.enrichMappingOptions({ eventSourceArn: this.innerProps.clusterArn, startingPosition: this.innerProps.startingPosition, @@ -114,6 +119,8 @@ export class ManagedKafkaEventSource extends StreamEventSource { }), ); + this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; + if (this.innerProps.secret !== undefined) { this.innerProps.secret.grantRead(target); } @@ -142,6 +149,16 @@ export class ManagedKafkaEventSource extends StreamEventSource { ? undefined : sourceAccessConfigurations; } + + /** + * The identifier for this EventSourceMapping + */ + public get eventSourceMappingId(): string { + if (!this._eventSourceMappingId) { + throw new Error('KafkaEventSource is not yet bound to an event source mapping'); + } + return this._eventSourceMappingId; + } } /** @@ -193,6 +210,9 @@ export class SelfManagedKafkaEventSource extends StreamEventSource { private sourceAccessConfigurations() { let authType; switch (this.innerProps.authenticationMethod) { + case AuthenticationMethod.BASIC_AUTH: + authType = lambda.SourceAccessConfigurationType.BASIC_AUTH; + break; case AuthenticationMethod.SASL_SCRAM_256_AUTH: authType = lambda.SourceAccessConfigurationType.SASL_SCRAM_256_AUTH; break; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index 1660dfe07b24a..3bd4375cc9d24 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -68,8 +75,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d2cdb69c90519 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sqs from '@aws-cdk/aws-sqs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/dynamo.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/dynamo.test.ts index 3e90ab5a4ab3b..97bd42c07145f 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/dynamo.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/dynamo.test.ts @@ -136,6 +136,49 @@ describe('DynamoEventSource', () => { }); + }); + + test('pass validation if batchsize is token', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const table = new dynamodb.Table(stack, 'T', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + stream: dynamodb.StreamViewType.NEW_IMAGE, + }); + const batchSize = new cdk.CfnParameter(stack, 'BatchSize', { + type: 'Number', + default: 100, + minValue: 1, + maxValue: 1000, + }); + // WHEN + fn.addEventSource(new sources.DynamoEventSource(table, { + batchSize: batchSize.valueAsNumber, + startingPosition: lambda.StartingPosition.LATEST, + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::EventSourceMapping', { + 'EventSourceArn': { + 'Fn::GetAtt': [ + 'TD925BC7E', + 'StreamArn', + ], + }, + 'FunctionName': { + 'Ref': 'Fn9270CBC0', + }, + 'BatchSize': { + 'Ref': 'BatchSize', + }, + 'StartingPosition': 'LATEST', + }); + + }); test('fails if streaming not enabled on table', () => { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json index aafb84ca19c72..c1690f2f03aac 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json @@ -42,7 +42,9 @@ "kinesis:GetRecords", "kinesis:GetShardIterator", "kinesis:ListShards", - "kinesis:SubscribeToShard" + "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json index 4d0a6c1a54707..616adaef6a86a 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json @@ -56,7 +56,9 @@ "kinesis:GetRecords", "kinesis:GetShardIterator", "kinesis:ListShards", - "kinesis:SubscribeToShard" + "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts index 1c361ffe7a442..1455931278129 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts @@ -240,7 +240,7 @@ describe('KafkaEventSource', () => { topic: kafkaTopic, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, securityGroup: sg, })); @@ -300,7 +300,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, securityGroup: sg, })); @@ -411,7 +411,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, })); }).toThrow(/securityGroup must be set/); @@ -437,7 +437,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, securityGroup: sg, authenticationMethod: sources.AuthenticationMethod.SASL_SCRAM_256_AUTH, })); @@ -452,9 +452,60 @@ describe('KafkaEventSource', () => { }, ]), }); + }); + test('using BASIC_AUTH', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const kafkaTopic = 'some-topic'; + const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); + const bootstrapServers = ['kafka-broker:9092']; + const sg = SecurityGroup.fromSecurityGroupId(stack, 'SecurityGroup', 'sg-0123456789'); + const vpc = new Vpc(stack, 'Vpc'); + // WHEN + fn.addEventSource(new sources.SelfManagedKafkaEventSource( + { + bootstrapServers: bootstrapServers, + topic: kafkaTopic, + secret: secret, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + vpc: vpc, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + securityGroup: sg, + authenticationMethod: sources.AuthenticationMethod.BASIC_AUTH, + })); + + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::EventSourceMapping', { + SourceAccessConfigurations: Match.arrayWith([ + { + Type: 'BASIC_AUTH', + URI: { + Ref: 'SecretA720EF05', + }, + }, + ]), + }); }); - }); + test('ManagedKafkaEventSource name conforms to construct id rules', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const clusterArn = 'some-arn'; + const kafkaTopic = 'some-topic'; + + const mskEventMapping = new sources.ManagedKafkaEventSource( + { + clusterArn, + topic: kafkaTopic, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + }); + + // WHEN + fn.addEventSource(mskEventMapping); + expect(mskEventMapping.eventSourceMappingId).toBeDefined(); + }); + }); }); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts index 96701d6c83f7a..e77fec71e5079 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts @@ -30,6 +30,8 @@ describe('KinesisEventSource', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], 'Effect': 'Allow', 'Resource': { diff --git a/packages/@aws-cdk/aws-lambda-go/README.md b/packages/@aws-cdk/aws-lambda-go/README.md index 5fb2907b0bf7a..e4de2d1641113 100644 --- a/packages/@aws-cdk/aws-lambda-go/README.md +++ b/packages/@aws-cdk/aws-lambda-go/README.md @@ -29,7 +29,7 @@ Define a `GoFunction`: ```ts new lambda.GoFunction(this, 'handler', { - entry: 'app/cmd/api' + entry: 'app/cmd/api', }); ``` @@ -106,7 +106,7 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd By default the following environment variables are set for you: * `GOOS=linux` -* `GOARCH=amd64` +* `GOARCH`: based on the target architecture of the Lambda function * `GO111MODULE=on` Use the `environment` prop to define additional environment variables when go runs: @@ -124,7 +124,7 @@ new lambda.GoFunction(this, 'handler', { ## Local Bundling -If `Go` is installed locally and the version is >= `go1.11` then it will be used to bundle your code in your environment. Otherwise, bundling will happen in a [Lambda compatible Docker container](https://hub.docker.com/layers/lambci/lambda/build-go1.x/images/sha256-e14dab718ed0bb06b2243825c5993e494a6969de7c01754ad7e80dacfce9b0cf?context=explore). +If `Go` is installed locally and the version is >= `go1.11` then it will be used to bundle your code in your environment. Otherwise, bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-go1.x) with the Docker platform based on the target architecture of the Lambda function. For macOS the recommended approach is to install `Go` as Docker volume performance is really poor. @@ -154,7 +154,7 @@ Use the `bundling.dockerImage` prop to use a custom bundling image: new lambda.GoFunction(this, 'handler', { entry: 'app/cmd/api', bundling: { - dockerImage: cdk.DockerImage.fromBuild('/path/to/Dockerfile'), + dockerImage: DockerImage.fromBuild('/path/to/Dockerfile'), }, }); ``` @@ -174,7 +174,9 @@ new lambda.GoFunction(this, 'handler', { It is possible to run additional commands by specifying the `commandHooks` prop: -```ts +```text +// This example only available in TypeScript +// Run additional commands on a GoFunction via `commandHooks` property new lambda.GoFunction(this, 'handler', { bundling: { commandHooks: { diff --git a/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile index ffa102c84803c..e832110399bea 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-go/lib/Dockerfile @@ -1,13 +1,15 @@ # 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=lambci/lambda:build-go1.x +ARG IMAGE=public.ecr.aws/sam/build-go1.x FROM $IMAGE # set the GOCACHE +ENV GOPATH=/go ENV GOCACHE=$GOPATH/.cache/go-build ENV GOPROXY=direct # Ensure all users can write to GOPATH -RUN chmod -R 777 $GOPATH +RUN mkdir $GOPATH && \ + chmod -R 777 $GOPATH CMD [ "go" ] diff --git a/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts index fd320cce90aed..afc233479ef3b 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts @@ -1,6 +1,6 @@ import * as os from 'os'; import * as path from 'path'; -import { AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; +import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; import { BundlingOptions } from './types'; import { exec, findUp, getGoBuildVersion } from './util'; @@ -55,6 +55,11 @@ export interface BundlingProps extends BundlingOptions { * The runtime of the lambda function */ readonly runtime: Runtime; + + /** + * The system architecture of the lambda function + */ + readonly architecture: Architecture; } /** @@ -104,7 +109,7 @@ export class Bundling implements cdk.BundlingOptions { const environment = { CGO_ENABLED: cgoEnabled, GO111MODULE: 'on', - GOARCH: 'amd64', + GOARCH: props.architecture.dockerPlatform.split('/')[1], GOOS: 'linux', ...props.environment, }; @@ -117,6 +122,7 @@ export class Bundling implements cdk.BundlingOptions { ...props.buildArgs ?? {}, IMAGE: Runtime.GO_1_X.bundlingImage.image, // always use the GO_1_X build image }, + platform: props.architecture.dockerPlatform, }) : cdk.DockerImage.fromRegistry('dummy'); // Do not build if we don't need to diff --git a/packages/@aws-cdk/aws-lambda-go/lib/function.ts b/packages/@aws-cdk/aws-lambda-go/lib/function.ts index 3b915e859cef3..4c7220ee27dce 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/function.ts @@ -104,6 +104,7 @@ export class GoFunction extends lambda.Function { } const runtime = props.runtime ?? lambda.Runtime.PROVIDED_AL2; + const architecture = props.architecture ?? lambda.Architecture.X86_64; super(scope, id, { ...props, @@ -112,6 +113,7 @@ export class GoFunction extends lambda.Function { ...props.bundling ?? {}, entry, runtime, + architecture, moduleDir, }), handler: 'bootstrap', // setting name to bootstrap so that the 'provided' runtime can also be used diff --git a/packages/@aws-cdk/aws-lambda-go/lib/types.ts b/packages/@aws-cdk/aws-lambda-go/lib/types.ts index bcd334809be33..28e058e1a685e 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/types.ts @@ -106,7 +106,7 @@ export interface BundlingOptions { * * Commands are chained with `&&`. * - * @example + * ```text * { * // Run tests prior to bundling * beforeBundling(inputDir: string, outputDir: string): string[] { @@ -114,6 +114,7 @@ export interface BundlingOptions { * } * // ... * } + * ``` */ export interface ICommandHooks { /** diff --git a/packages/@aws-cdk/aws-lambda-go/package.json b/packages/@aws-cdk/aws-lambda-go/package.json index 4fca8e06193ad..5df9aae607538 100644 --- a/packages/@aws-cdk/aws-lambda-go/package.json +++ b/packages/@aws-cdk/aws-lambda-go/package.json @@ -30,7 +30,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -71,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..96f3f6933780c --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { DockerImage, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda-go'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts index 474657dff61b9..73dfec99b45c1 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts @@ -1,26 +1,28 @@ import * as child_process from 'child_process'; import * as os from 'os'; import * as path from 'path'; -import { Code, Runtime } from '@aws-cdk/aws-lambda'; +import { Architecture, Code, Runtime } from '@aws-cdk/aws-lambda'; import { AssetHashType, DockerImage } from '@aws-cdk/core'; import { Bundling } from '../lib/bundling'; import * as util from '../lib/util'; -jest.spyOn(Code, 'fromAsset'); -const fromAssetMock = jest.spyOn(DockerImage, 'fromBuild'); let getGoBuildVersionMock = jest.spyOn(util, 'getGoBuildVersion'); beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); Bundling.clearRunsLocallyCache(); - getGoBuildVersionMock.mockReturnValue(true); - fromAssetMock.mockReturnValue({ + + jest.spyOn(Code, 'fromAsset'); + + jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({ image: 'built-image', cp: () => 'built-image', run: () => {}, toJSON: () => 'build-image', }); + + getGoBuildVersionMock.mockReturnValue(true); }); const moduleDir = '/project/go.mod'; @@ -30,6 +32,7 @@ test('bundling', () => { Bundling.bundle({ entry, runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, forcedDockerBundling: true, environment: { @@ -55,12 +58,20 @@ test('bundling', () => { ], }), }); + + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/aws-lambda-go\/lib$/), expect.objectContaining({ + buildArgs: expect.objectContaining({ + IMAGE: expect.stringMatching(/build-go/), + }), + platform: 'linux/amd64', + })); }); test('bundling with file as entry', () => { Bundling.bundle({ entry: '/project/main.go', runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, }); @@ -81,6 +92,7 @@ test('bundling with file in subdirectory as entry', () => { Bundling.bundle({ entry: '/project/cmd/api/main.go', runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, }); @@ -101,6 +113,7 @@ test('bundling with file other than main.go in subdirectory as entry', () => { Bundling.bundle({ entry: '/project/cmd/api/api.go', runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, }); @@ -122,6 +135,7 @@ test('go with Windows paths', () => { Bundling.bundle({ entry: 'C:\\my-project\\cmd\\api', runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir: 'C:\\my-project\\go.mod', forcedDockerBundling: true, }); @@ -141,13 +155,14 @@ test('with Docker build args', () => { Bundling.bundle({ entry, runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, forcedDockerBundling: true, buildArgs: { HELLO: 'WORLD', }, }); - expect(fromAssetMock).toHaveBeenCalledWith(expect.stringMatching(/lib$/), expect.objectContaining({ + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/aws-lambda-go\/lib$/), expect.objectContaining({ buildArgs: expect.objectContaining({ HELLO: 'WORLD', }), @@ -171,6 +186,7 @@ test('Local bundling', () => { KEY: 'value', }, runtime: Runtime.PROVIDED_AL2, + architecture: Architecture.X86_64, }); expect(bundler.local).toBeDefined(); @@ -188,7 +204,7 @@ test('Local bundling', () => { ); // Docker image is not built - expect(fromAssetMock).not.toHaveBeenCalled(); + expect(DockerImage.fromBuild).not.toHaveBeenCalled(); }); test('Incorrect go version', () => { @@ -198,6 +214,7 @@ test('Incorrect go version', () => { entry, moduleDir, runtime: Runtime.PROVIDED_AL2, + architecture: Architecture.X86_64, }); const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.GO_1_X.bundlingDockerImage }); @@ -211,6 +228,7 @@ test('Custom bundling docker image', () => { entry, moduleDir, runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, forcedDockerBundling: true, dockerImage: DockerImage.fromRegistry('my-custom-image'), }); @@ -227,6 +245,7 @@ test('Go build flags can be passed', () => { Bundling.bundle({ entry, runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, environment: { KEY: 'value', @@ -258,6 +277,7 @@ test('AssetHashType can be specified', () => { Bundling.bundle({ entry, runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, moduleDir, environment: { KEY: 'value', @@ -291,6 +311,7 @@ test('with command hooks', () => { entry, moduleDir, runtime: Runtime.PROVIDED_AL2, + architecture: Architecture.X86_64, commandHooks: { beforeBundling(inputDir: string, outputDir: string): string[] { return [ diff --git a/packages/@aws-cdk/aws-lambda-go/test/docker.test.ts b/packages/@aws-cdk/aws-lambda-go/test/docker.test.ts index d619d9f0fb91d..cd335d834358f 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/docker.test.ts +++ b/packages/@aws-cdk/aws-lambda-go/test/docker.test.ts @@ -2,7 +2,7 @@ import { spawnSync } from 'child_process'; import * as path from 'path'; beforeAll(() => { - spawnSync('docker', ['build', '--build-arg', 'IMAGE=public.ecr.aws/bitnami/golang:1.15', '-t', 'golang', path.join(__dirname, '../lib')]); + spawnSync('docker', ['build', '-t', 'golang', path.join(__dirname, '../lib')]); }); test('golang is available', async () => { diff --git a/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json index 9ae6cc08830e8..fe40549eba6d3 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1S3Bucket62A8237E" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3Bucket6ED74DF1" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1S3VersionKey1C4F3B50" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1S3VersionKey1C4F3B50" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1S3Bucket62A8237E": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3Bucket6ED74DF1": { "Type": "String", - "Description": "S3 bucket for asset \"4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1\"" + "Description": "S3 bucket for asset \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" }, - "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1S3VersionKey1C4F3B50": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE": { "Type": "String", - "Description": "S3 key for asset version \"4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1\"" + "Description": "S3 key for asset version \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" }, - "AssetParameters4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1ArtifactHashBE683488": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1ArtifactHash2DFD542A": { "Type": "String", - "Description": "Artifact hash for asset \"4702fc8f2fac1855e6700e59222ef0ebbeaf11eba75c66af9f2e216e312b16b1\"" + "Description": "Artifact hash for asset \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-go/test/integ.function.ts b/packages/@aws-cdk/aws-lambda-go/test/integ.function.ts index 093597339e8b5..84a30bff352e3 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/integ.function.ts +++ b/packages/@aws-cdk/aws-lambda-go/test/integ.function.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import { App, DockerImage, Stack, StackProps } from '@aws-cdk/core'; +import { App, Stack, StackProps } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as lambda from '../lib'; @@ -15,11 +15,6 @@ class TestStack extends Stack { new lambda.GoFunction(this, 'go-handler-docker', { entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'), bundling: { - dockerImage: DockerImage.fromBuild(path.join(__dirname, '../lib'), { - buildArgs: { - IMAGE: 'public.ecr.aws/bitnami/golang:1.16.3-debian-10-r16', - }, - }), forcedDockerBundling: true, goBuildFlags: ['-mod=readonly', '-ldflags "-s -w"'], }, diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go index 3dc0f71439536..a704fea2def58 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go +++ b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go @@ -1,20 +1,12 @@ package main -import ( - "context" - "fmt" - - "github.com/aws/aws-lambda-go/lambda" -) - -type MyEvent struct { - Name string `json:"name"` -} - -func HandleRequest(ctx context.Context, name MyEvent) (string, error) { - return fmt.Sprintf("Hello %s!", name.Name), nil -} +// Intentionally empty. There were issues with 'gopkg.in', so: +// - we cannot depend on 'github.com/aws/aws-lambda-go' +// - since: it has a dependency on 'gopkg.in/yaml.v3' +// - therefore: we cannot type the handler properly here +// +// It doesn't matter that this isn't an actual Lambda handler, we +// just need the test build to succeed. func main() { - lambda.Start(HandleRequest) } diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod index 24ac2f8c41afa..9f22f42e6b18c 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod +++ b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod @@ -2,9 +2,3 @@ module aws-lambda-golang go 1.16 -require ( - github.com/aws/aws-lambda-go v1.19.1 - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect -) diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum deleted file mode 100644 index 94f7ebf0f685e..0000000000000 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum +++ /dev/null @@ -1,32 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.19.1 h1:5iUHbIZ2sG6Yq/J1IN3sWm3+vAB1CWwhI21NffLNuNI= -github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 22347b28b1dd9..81fb45b3b1f4a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -99,7 +99,8 @@ used by your function. Otherwise bundling will fail. ## Local bundling If `esbuild` is available it will be used to bundle your code in your environment. Otherwise, -bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x). +bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x) +with the Docker platform based on the target architecture of the Lambda function. For macOS the recommendend approach is to install `esbuild` as Docker volume performance is really poor. @@ -172,7 +173,8 @@ new lambda.NodejsFunction(this, 'my-handler', { bundling: { minify: true, // minify code, defaults to false sourceMap: true, // include source map, defaults to false - sourceMapMode: SourceMapMode.INLINE, // defaults to SourceMapMode.DEFAULT + sourceMapMode: lambda.SourceMapMode.INLINE, // defaults to SourceMapMode.DEFAULT + sourcesContent: false, // do not include original source into source map, defaults to true target: 'es2020', // target environment for the generated JavaScript code loader: { // Use the 'dataurl' loader for '.png' files '.png': 'dataurl', @@ -182,12 +184,13 @@ new lambda.NodejsFunction(this, 'my-handler', { 'process.env.PRODUCTION': JSON.stringify(true), 'process.env.NUMBER': JSON.stringify(123), }, - logLevel: LogLevel.SILENT, // defaults to LogLevel.WARNING + logLevel: lambda.LogLevel.SILENT, // defaults to LogLevel.WARNING keepNames: true, // defaults to false tsconfig: 'custom-tsconfig.json', // use custom-tsconfig.json instead of default, metafile: true, // include meta file, defaults to false - banner : '/* comments */', // requires esbuild >= 0.9.0, defaults to none - footer : '/* comments */', // requires esbuild >= 0.9.0, defaults to none + banner: '/* comments */', // requires esbuild >= 0.9.0, defaults to none + footer: '/* comments */', // requires esbuild >= 0.9.0, defaults to none + charset: lambda.Charset.UTF8, // do not escape non-ASCII characters, defaults to Charset.ASCII }, }); ``` @@ -196,18 +199,28 @@ new lambda.NodejsFunction(this, 'my-handler', { It is possible to run additional commands by specifying the `commandHooks` prop: -```ts +```text +// This example only available in TypeScript +// Run additional props via `commandHooks` new lambda.NodejsFunction(this, 'my-handler-with-commands', { bundling: { commandHooks: { - // Copy a file so that it will be included in the bundled asset + beforeBundling(inputDir: string, outputDir: string): string[] { + return [ + `echo hello > ${inputDir}/a.txt`, + `cp ${inputDir}/a.txt ${outputDir}`, + ]; + }, afterBundling(inputDir: string, outputDir: string): string[] { - return [`cp ${inputDir}/my-binary.node ${outputDir}`]; - } + return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; + }, + beforeInstall() { + return []; + }, // ... - } + }, // ... - } + }, }); ``` @@ -224,6 +237,22 @@ an array of commands to run. Commands are chained with `&&`. The commands will run in the environment in which bundling occurs: inside the container for Docker bundling or on the host OS for local bundling. +## Pre Compilation with TSC + +In some cases, `esbuild` may not yet support some newer features of the typescript language, such as, +[`emitDecoratorMetadata`](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata). +In such cases, it is possible to run pre-compilation using `tsc` by setting the `preCompilation` flag. + +```ts +new lambda.NodejsFunction(this, 'my-handler', { + bundling: { + preCompilation: true, + }, +}); +``` + +Note: A [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) is required + ## Customizing Docker bundling Use `bundling.environment` to define environments variables when `esbuild` runs: @@ -243,9 +272,9 @@ Use `bundling.buildArgs` to pass build arguments when building the Docker bundli ```ts new lambda.NodejsFunction(this, 'my-handler', { bundling: { - buildArgs: { - HTTPS_PROXY: 'https://127.0.0.1:3001', - }, + buildArgs: { + HTTPS_PROXY: 'https://127.0.0.1:3001', + }, } }); ``` @@ -255,7 +284,7 @@ Use `bundling.dockerImage` to use a custom Docker bundling image: ```ts new lambda.NodejsFunction(this, 'my-handler', { bundling: { - dockerImage: cdk.DockerImage.fromBuild('/path/to/Dockerfile'), + dockerImage: DockerImage.fromBuild('/path/to/Dockerfile'), }, }); ``` @@ -265,3 +294,20 @@ should also have `npm`, `yarn` or `pnpm` depending on the lock file you're using Use the [default image provided by `@aws-cdk/aws-lambda-nodejs`](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-lambda-nodejs/lib/Dockerfile) as a source of inspiration. + +## Asset hash + +By default the asset hash will be calculated based on the bundled output (`AssetHashType.OUTPUT`). + +Use the `assetHash` prop to pass a custom hash: + +```ts +new lambda.NodejsFunction(this, 'my-handler', { + bundling: { + assetHash: 'my-custom-hash', + }, +}); +``` + +If you chose to customize the hash, you will need to make sure it is updated every time the asset +changes, or otherwise it is possible that some deployments will not be invalidated. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile index 578b1b8135d5c..54969bb5fde2c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile @@ -9,6 +9,9 @@ RUN npm install --global yarn@1.22.5 # Install pnpm RUN npm install --global pnpm +# Install typescript +RUN npm install --global typescript + # Install esbuild # (unsafe-perm because esbuild has a postinstall script) ARG ESBUILD_VERSION=0 diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 3c33ad74f2471..31fedbafc6e3d 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -1,8 +1,8 @@ import * as os from 'os'; import * as path from 'path'; -import { AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; +import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; -import { EsbuildInstallation } from './esbuild-installation'; +import { PackageInstallation } from './package-installation'; import { PackageManager } from './package-manager'; import { BundlingOptions, SourceMapMode } from './types'; import { exec, extractDependencies, findUp } from './util'; @@ -28,10 +28,21 @@ export interface BundlingProps extends BundlingOptions { */ readonly runtime: Runtime; + /** + * The system architecture of the lambda function + */ + readonly architecture: Architecture; + /** * Path to project root */ readonly projectRoot: string; + + /** + * Run compilation using `tsc` before bundling + */ + readonly preCompilation?: boolean + } /** @@ -43,7 +54,8 @@ export class Bundling implements cdk.BundlingOptions { */ public static bundle(options: BundlingProps): AssetCode { return Code.fromAsset(options.projectRoot, { - assetHashType: cdk.AssetHashType.OUTPUT, + assetHash: options.assetHash, + assetHashType: options.assetHash ? cdk.AssetHashType.CUSTOM : cdk.AssetHashType.OUTPUT, bundling: new Bundling(options), }); } @@ -52,7 +64,17 @@ export class Bundling implements cdk.BundlingOptions { this.esbuildInstallation = undefined; } - private static esbuildInstallation?: EsbuildInstallation; + public static clearTscInstallationCache(): void { + this.tscInstallation = undefined; + } + + public static clearTscCompilationCache(): void { + this.tscCompiled = false; + } + + private static esbuildInstallation?: PackageInstallation; + private static tscInstallation?: PackageInstallation; + private static tscCompiled = false // Core bundling options public readonly image: cdk.DockerImage; @@ -71,7 +93,8 @@ export class Bundling implements cdk.BundlingOptions { constructor(private readonly props: BundlingProps) { this.packageManager = PackageManager.fromLockFile(props.depsLockFilePath); - Bundling.esbuildInstallation = Bundling.esbuildInstallation ?? EsbuildInstallation.detect(); + Bundling.esbuildInstallation = Bundling.esbuildInstallation ?? PackageInstallation.detect('esbuild'); + Bundling.tscInstallation = Bundling.tscInstallation ?? PackageInstallation.detect('tsc'); this.projectRoot = props.projectRoot; this.relativeEntryPath = path.relative(this.projectRoot, path.resolve(props.entry)); @@ -85,6 +108,10 @@ export class Bundling implements cdk.BundlingOptions { this.relativeTsconfigPath = path.relative(this.projectRoot, path.resolve(props.tsconfig)); } + if (props.preCompilation && !/\.tsx?$/.test(props.entry)) { + throw new Error('preCompilation can only be used with typescript files'); + } + this.externals = [ ...props.externalModules ?? ['aws-sdk'], // Mark aws-sdk as external by default (available in the runtime) ...props.nodeModules ?? [], // Mark the modules that we are going to install as externals also @@ -92,13 +119,14 @@ export class Bundling implements cdk.BundlingOptions { // Docker bundling const shouldBuildImage = props.forceDockerBundling || !Bundling.esbuildInstallation; - this.image = shouldBuildImage - ? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '../lib'), { + this.image = shouldBuildImage ? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '../lib'), + { buildArgs: { ...props.buildArgs ?? {}, IMAGE: props.runtime.bundlingImage.image, ESBUILD_VERSION: props.esbuildVersion ?? ESBUILD_MAJOR_VERSION, }, + platform: props.architecture.dockerPlatform, }) : cdk.DockerImage.fromRegistry('dummy'); // Do not build if we don't need to @@ -106,6 +134,7 @@ export class Bundling implements cdk.BundlingOptions { inputDir: cdk.AssetStaging.BUNDLING_INPUT_DIR, outputDir: cdk.AssetStaging.BUNDLING_OUTPUT_DIR, esbuildRunner: 'esbuild', // esbuild is installed globally in the docker image + tscRunner: 'tsc', // tsc is installed globally in the docker image osPlatform: 'linux', // linux docker image }); this.command = ['bash', '-c', bundlingCommand]; @@ -122,6 +151,27 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); + let tscCommand: string = ''; + let relativeEntryPath = this.relativeEntryPath; + + if (this.props.preCompilation) { + + let tsconfig = this.relativeTsconfigPath; + if (!tsconfig) { + const findConfig = findUp('tsconfig.json', path.dirname(this.props.entry)); + if (!findConfig) { + throw new Error('Cannot find a tsconfig.json, please specify the prop: tsconfig'); + } + tsconfig = path.relative(this.projectRoot, findConfig); + } + + relativeEntryPath = relativeEntryPath.replace(/\.ts(x?)$/, '.js$1'); + if (!Bundling.tscCompiled) { + // Intentionally Setting rootDir and outDir, so that the compiled js file always end up next ts file. + tscCommand = `${options.tscRunner} --project ${pathJoin(options.inputDir, tsconfig)} --rootDir ./ --outDir ./`; + Bundling.tscCompiled = true; + } + } const loaders = Object.entries(this.props.loader ?? {}); const defines = Object.entries(this.props.define ?? {}); @@ -129,19 +179,21 @@ export class Bundling implements cdk.BundlingOptions { if (this.props.sourceMap === false && this.props.sourceMapMode) { throw new Error('sourceMapMode cannot be used when sourceMap is false'); } - // eslint-disable-next-line no-console + const sourceMapEnabled = this.props.sourceMapMode ?? this.props.sourceMap; const sourceMapMode = this.props.sourceMapMode ?? SourceMapMode.DEFAULT; const sourceMapValue = sourceMapMode === SourceMapMode.DEFAULT ? '' : `=${this.props.sourceMapMode}`; + const sourcesContent = this.props.sourcesContent ?? true; const esbuildCommand: string[] = [ options.esbuildRunner, - '--bundle', `"${pathJoin(options.inputDir, this.relativeEntryPath)}"`, + '--bundle', `"${pathJoin(options.inputDir, relativeEntryPath)}"`, `--target=${this.props.target ?? toTarget(this.props.runtime)}`, '--platform=node', `--outfile="${pathJoin(options.outputDir, 'index.js')}"`, ...this.props.minify ? ['--minify'] : [], ...sourceMapEnabled ? [`--sourcemap${sourceMapValue}`] : [], + ...sourcesContent ? [] : [`--sources-content=${sourcesContent}`], ...this.externals.map(external => `--external:${external}`), ...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), ...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), @@ -151,6 +203,7 @@ export class Bundling implements cdk.BundlingOptions { ...this.props.metafile ? [`--metafile=${pathJoin(options.outputDir, 'index.meta.json')}`] : [], ...this.props.banner ? [`--banner:js=${JSON.stringify(this.props.banner)}`] : [], ...this.props.footer ? [`--footer:js=${JSON.stringify(this.props.footer)}`] : [], + ...this.props.charset ? [`--charset=${this.props.charset}`] : [], ]; let depsCommand = ''; @@ -179,6 +232,7 @@ export class Bundling implements cdk.BundlingOptions { return chain([ ...this.props.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? [], + tscCommand, esbuildCommand.join(' '), ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(options.inputDir, options.outputDir)) ?? [], depsCommand, @@ -188,10 +242,11 @@ export class Bundling implements cdk.BundlingOptions { private getLocalBundlingProvider(): cdk.ILocalBundling { const osPlatform = os.platform(); - const createLocalCommand = (outputDir: string, esbuild: EsbuildInstallation) => this.createBundlingCommand({ + const createLocalCommand = (outputDir: string, esbuild: PackageInstallation, tsc?: PackageInstallation) => this.createBundlingCommand({ inputDir: this.projectRoot, outputDir, esbuildRunner: esbuild.isLocal ? this.packageManager.runBinCommand('esbuild') : 'esbuild', + tscRunner: tsc && (tsc.isLocal ? this.packageManager.runBinCommand('tsc') : 'tsc'), osPlatform, }); const environment = this.props.environment ?? {}; @@ -208,7 +263,7 @@ export class Bundling implements cdk.BundlingOptions { throw new Error(`Expected esbuild version ${ESBUILD_MAJOR_VERSION}.x but got ${Bundling.esbuildInstallation.version}`); } - const localCommand = createLocalCommand(outputDir, Bundling.esbuildInstallation); + const localCommand = createLocalCommand(outputDir, Bundling.esbuildInstallation, Bundling.tscInstallation); exec( osPlatform === 'win32' ? 'cmd' : 'bash', @@ -237,6 +292,7 @@ interface BundlingCommandOptions { readonly inputDir: string; readonly outputDir: string; readonly esbuildRunner: string; + readonly tscRunner?: string; readonly osPlatform: NodeJS.Platform; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 09c4964fd610d..0bb00a2c35e5c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; +import { Architecture } from '@aws-cdk/aws-lambda'; import { Bundling } from './bundling'; import { PackageManager } from './package-manager'; import { BundlingOptions } from './types'; @@ -95,6 +96,7 @@ export class NodejsFunction extends lambda.Function { const entry = path.resolve(findEntry(id, props.entry)); const handler = props.handler ?? 'handler'; const runtime = props.runtime ?? lambda.Runtime.NODEJS_14_X; + const architecture = props.architecture ?? Architecture.X86_64; const depsLockFilePath = findLockFile(props.depsLockFilePath); const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath); @@ -105,6 +107,7 @@ export class NodejsFunction extends lambda.Function { ...props.bundling ?? {}, entry, runtime, + architecture, depsLockFilePath, projectRoot, }), diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts similarity index 59% rename from packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts rename to packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts index 8ef2e8dbb23d9..789246badcceb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts @@ -2,13 +2,13 @@ import { spawnSync } from 'child_process'; import { tryGetModuleVersion } from './util'; /** - * An esbuild installation + * Package installation */ -export abstract class EsbuildInstallation { - public static detect(): EsbuildInstallation | undefined { +export abstract class PackageInstallation { + public static detect(module: string): PackageInstallation | undefined { try { // Check local version first - const version = tryGetModuleVersion('esbuild'); + const version = tryGetModuleVersion(module); if (version) { return { isLocal: true, @@ -17,11 +17,11 @@ export abstract class EsbuildInstallation { } // Fallback to a global version - const esbuild = spawnSync('esbuild', ['--version']); - if (esbuild.status === 0 && !esbuild.error) { + const proc = spawnSync(module, ['--version']); + if (proc.status === 0 && !proc.error) { return { isLocal: false, - version: esbuild.stdout.toString().trim(), + version: proc.stdout.toString().trim(), }; } return undefined; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts index 0f18c92cbde20..f0206bbd19a99 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts @@ -20,7 +20,7 @@ export class PackageManager { public static YARN = new PackageManager({ lockFile: 'yarn.lock', - installCommand: ['yarn', 'install'], + installCommand: ['yarn', 'install', '--no-immutable'], runCommand: ['yarn', 'run'], }); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index 8d0d263fe64e6..e16e9db8120b6 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -26,6 +26,15 @@ export interface BundlingOptions { */ readonly sourceMapMode?: SourceMapMode; + /** + * Whether to include original source code in source maps when bundling. + * + * @see https://esbuild.github.io/api/#sources-content + * + * @default true + */ + readonly sourcesContent?: boolean; + /** * Target environment for the generated JavaScript code. * @@ -43,7 +52,7 @@ export interface BundlingOptions { * * @see https://esbuild.github.io/api/#loader * - * @example { '.png': 'dataurl' } + * For example, `{ '.png': 'dataurl' }`. * * @default - use esbuild default loaders */ @@ -83,7 +92,7 @@ export interface BundlingOptions { * * This can be useful if you need to do multiple builds of the same code with different settings. * - * @example { 'tsconfig': 'path/custom.tsconfig.json' } + * For example, `{ 'tsconfig': 'path/custom.tsconfig.json' }`. * * @default - automatically discovered by `esbuild` */ @@ -94,26 +103,25 @@ export interface BundlingOptions { * * The metadata in this JSON file follows this schema (specified using TypeScript syntax): * - * ```typescript - * { - * outputs: { - * [path: string]: { - * bytes: number - * inputs: { - * [path: string]: { bytesInOutput: number } - * } - * imports: { path: string }[] - * exports: string[] - * } - * } + * ```text + * { + * outputs: { + * [path: string]: { + * bytes: number + * inputs: { + * [path: string]: { bytesInOutput: number } + * } + * imports: { path: string }[] + * exports: string[] * } + * } * } * ``` * This data can then be analyzed by other tools. For example, * bundle buddy can consume esbuild's metadata format and generates a treemap visualization * of the modules in your bundle and how much space each one takes up. * @see https://esbuild.github.io/api/#metafile - * @default - false + * @default false */ readonly metafile?: boolean @@ -124,7 +132,7 @@ export interface BundlingOptions { * * This is commonly used to insert comments: * - * @default - no comments are passed + * @default - no comments are passed */ readonly banner? : string @@ -135,10 +143,23 @@ export interface BundlingOptions { * * This is commonly used to insert comments * - * @default - no comments are passed + * @default - no comments are passed */ readonly footer? : string + /** + * The charset to use for esbuild's output. + * + * By default esbuild's output is ASCII-only. Any non-ASCII characters are escaped + * using backslash escape sequences. Using escape sequences makes the generated output + * slightly bigger, and also makes it harder to read. If you would like for esbuild to print + * the original characters without using escape sequences, use `Charset.UTF8`. + * + * @see https://esbuild.github.io/api/#charset + * @default Charset.ASCII + */ + readonly charset?: Charset; + /** * Environment variables defined when bundling runs. * @@ -149,8 +170,9 @@ export interface BundlingOptions { /** * Replace global identifiers with constant expressions. * - * @example { 'process.env.DEBUG': 'true' } - * @example { 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx') } + * For example, `{ 'process.env.DEBUG': 'true' }`. + * + * Another example, `{ 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx') }`. * * @default - no replacements are made */ @@ -197,6 +219,16 @@ export interface BundlingOptions { */ readonly forceDockerBundling?: boolean; + /** + * Run compilation using tsc before running file through bundling step. + * This usually is not required unless you are using new experimental features that + * are only supported by typescript's `tsc` compiler. + * One example of such feature is `emitDecoratorMetadata`. + * + * @default false + */ + readonly preCompilation?: boolean + /** * A custom bundling Docker image. * @@ -216,6 +248,21 @@ export interface BundlingOptions { * @default - do not run additional commands */ readonly commandHooks?: ICommandHooks; + + /** + * Specify a custom hash for this asset. For consistency, this custom hash will + * be SHA256 hashed and encoded as hex. The resulting hash will be the asset + * hash. + * + * NOTE: the hash is used in order to identify a specific revision of the asset, and + * used for optimizing and caching deployment activities related to this asset such as + * packaging, uploading to Amazon S3, etc. If you chose to customize the hash, you will + * need to make sure it is updated every time the asset changes, or otherwise it is + * possible that some deployments will not be invalidated. + * + * @default - asset hash is calculated based on the bundled output + */ + readonly assetHash?: string; } /** @@ -226,15 +273,14 @@ export interface BundlingOptions { * * Commands are chained with `&&`. * - * @example - * { - * // Copy a file from the input directory to the output directory - * // to include it in the bundled asset - * afterBundling(inputDir: string, outputDir: string): string[] { - * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; - * } - * // ... + * The following example (specified in TypeScript) copies a file from the input + * directory to the output directory to include it in the bundled asset: + * + * ```text + * afterBundling(inputDir: string, outputDir: string): string[]{ + * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; * } + * ``` */ export interface ICommandHooks { /** @@ -300,3 +346,22 @@ export enum SourceMapMode { */ BOTH = 'both' } + +/** + * Charset for esbuild's output + */ +export enum Charset { + /** + * ASCII + * + * Any non-ASCII characters are escaped using backslash escape sequences + */ + ASCII = 'ascii', + + /** + * UTF-8 + * + * Keep original characters without using escape sequences + */ + UTF8 = 'utf8' +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index 5ead91793e93b..af24d3b4ae371 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -64,7 +64,7 @@ export function exec(cmd: string, args: string[], options?: SpawnSyncOptions) { if (proc.stdout || proc.stderr) { throw new Error(`[Status ${proc.status}] stdout: ${proc.stdout?.toString().trim()}\n\n\nstderr: ${proc.stderr?.toString().trim()}`); } - throw new Error(`${cmd} exited with status ${proc.status}`); + throw new Error(`${cmd} ${args.join(' ')} ${options?.cwd ? `run in directory ${options.cwd}` : ''} exited with status ${proc.status}`); } return proc; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 3bb6766b89ccf..b0dfb32289dff 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -69,9 +76,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "delay": "5.0.0", - "esbuild": "^0.13.4" + "esbuild": "^0.13.14" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d1ec43165548e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { DockerImage, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda-nodejs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index a38a6fa08d5bc..9ef40545baf31 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -1,31 +1,31 @@ import * as child_process from 'child_process'; import * as os from 'os'; import * as path from 'path'; -import { Code, Runtime } from '@aws-cdk/aws-lambda'; +import { Architecture, Code, Runtime } from '@aws-cdk/aws-lambda'; import { AssetHashType, DockerImage } from '@aws-cdk/core'; import { version as delayVersion } from 'delay/package.json'; import { Bundling } from '../lib/bundling'; -import { EsbuildInstallation } from '../lib/esbuild-installation'; -import { LogLevel, SourceMapMode } from '../lib/types'; +import { PackageInstallation } from '../lib/package-installation'; +import { Charset, LogLevel, SourceMapMode } from '../lib/types'; import * as util from '../lib/util'; -jest.mock('@aws-cdk/aws-lambda'); -// Mock DockerImage.fromAsset() to avoid building the image -let fromBuildMock: jest.SpyInstance; -let detectEsbuildMock: jest.SpyInstance; +let detectPackageInstallationMock: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); jest.restoreAllMocks(); Bundling.clearEsbuildInstallationCache(); + Bundling.clearTscInstallationCache(); - detectEsbuildMock = jest.spyOn(EsbuildInstallation, 'detect').mockReturnValue({ + jest.spyOn(Code, 'fromAsset'); + + detectPackageInstallationMock = jest.spyOn(PackageInstallation, 'detect').mockReturnValue({ isLocal: true, version: '0.8.8', }); - fromBuildMock = jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({ + jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({ image: 'built-image', cp: () => 'dest-path', run: () => {}, @@ -44,6 +44,7 @@ test('esbuild bundling in Docker', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, environment: { KEY: 'value', }, @@ -67,6 +68,13 @@ test('esbuild bundling in Docker', () => { workingDirectory: '/', }), }); + + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/aws-lambda-nodejs\/lib$/), expect.objectContaining({ + buildArgs: expect.objectContaining({ + IMAGE: expect.stringMatching(/build-nodejs/), + }), + platform: 'linux/amd64', + })); }); test('esbuild bundling with handler named index.ts', () => { @@ -75,6 +83,7 @@ test('esbuild bundling with handler named index.ts', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, forceDockerBundling: true, }); @@ -96,6 +105,7 @@ test('esbuild bundling with tsx handler', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, forceDockerBundling: true, }); @@ -121,6 +131,7 @@ test('esbuild with Windows paths', () => { Bundling.bundle({ entry: 'C:\\my-project\\lib\\entry.ts', runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, projectRoot: 'C:\\my-project', depsLockFilePath: 'C:\\my-project\\package-lock.json', forceDockerBundling: true, @@ -144,6 +155,7 @@ test('esbuild bundling with externals and dependencies', () => { projectRoot: path.dirname(packageLock), depsLockFilePath: packageLock, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, externalModules: ['abc'], nodeModules: ['delay'], forceDockerBundling: true, @@ -173,8 +185,10 @@ test('esbuild bundling with esbuild options', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, minify: true, sourceMap: true, + sourcesContent: false, target: 'es2020', loader: { '.png': 'dataurl', @@ -185,6 +199,7 @@ test('esbuild bundling with esbuild options', () => { metafile: true, banner: '/* comments */', footer: '/* comments */', + charset: Charset.UTF8, forceDockerBundling: true, define: { 'process.env.KEY': JSON.stringify('VALUE'), @@ -204,10 +219,11 @@ test('esbuild bundling with esbuild options', () => { [ 'esbuild --bundle "/asset-input/lib/handler.ts"', '--target=es2020 --platform=node --outfile="/asset-output/index.js"', - '--minify --sourcemap --external:aws-sdk --loader:.png=dataurl', + '--minify --sourcemap --sources-content=false --external:aws-sdk --loader:.png=dataurl', defineInstructions, '--log-level=silent --keep-names --tsconfig=/asset-input/lib/custom-tsconfig.ts', '--metafile=/asset-output/index.meta.json --banner:js="/* comments */" --footer:js="/* comments */"', + '--charset=utf8', ].join(' '), ], }), @@ -224,6 +240,7 @@ test('esbuild bundling source map default', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, sourceMap: true, sourceMapMode: SourceMapMode.DEFAULT, }); @@ -249,6 +266,7 @@ test('esbuild bundling source map inline', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, sourceMap: true, sourceMapMode: SourceMapMode.INLINE, }); @@ -274,6 +292,7 @@ test('esbuild bundling source map enabled when only source map mode exists', () projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, sourceMapMode: SourceMapMode.INLINE, }); @@ -299,6 +318,7 @@ test('esbuild bundling throws when sourceMapMode used with false sourceMap', () projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, sourceMap: false, sourceMapMode: SourceMapMode.INLINE, }); @@ -312,6 +332,7 @@ test('Detects yarn.lock', () => { projectRoot: path.dirname(yarnLock), depsLockFilePath: yarnLock, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, nodeModules: ['delay'], forceDockerBundling: true, }); @@ -321,7 +342,7 @@ test('Detects yarn.lock', () => { assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: expect.arrayContaining([ - expect.stringMatching(/yarn\.lock.+yarn install/), + expect.stringMatching(/yarn\.lock.+yarn install --no-immutable/), ]), }), }); @@ -334,6 +355,7 @@ test('Detects pnpm-lock.yaml', () => { projectRoot, depsLockFilePath: pnpmLock, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, nodeModules: ['delay'], forceDockerBundling: true, }); @@ -355,13 +377,14 @@ test('with Docker build args', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, buildArgs: { HELLO: 'WORLD', }, forceDockerBundling: true, }); - expect(fromBuildMock).toHaveBeenCalledWith(expect.stringMatching(/lib$/), expect.objectContaining({ + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/lib$/), expect.objectContaining({ buildArgs: expect.objectContaining({ HELLO: 'WORLD', }), @@ -383,6 +406,7 @@ test('Local bundling', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, environment: { KEY: 'value', }, @@ -403,14 +427,14 @@ test('Local bundling', () => { ); // Docker image is not built - expect(fromBuildMock).not.toHaveBeenCalled(); + expect(DockerImage.fromBuild).not.toHaveBeenCalled(); spawnSyncMock.mockRestore(); }); test('Incorrect esbuild version', () => { - detectEsbuildMock.mockReturnValueOnce({ + detectPackageInstallationMock.mockReturnValueOnce({ isLocal: true, version: '3.4.5', }); @@ -420,6 +444,7 @@ test('Incorrect esbuild version', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, }); expect(() => bundler.local?.tryBundle('/outdir', { @@ -433,6 +458,7 @@ test('Custom bundling docker image', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, dockerImage: DockerImage.fromRegistry('my-custom-image'), forceDockerBundling: true, }); @@ -451,6 +477,7 @@ test('with command hooks', () => { projectRoot, depsLockFilePath, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, commandHooks: { beforeBundling(inputDir: string, outputDir: string): string[] { return [ @@ -486,6 +513,7 @@ test('esbuild bundling with projectRoot', () => { depsLockFilePath, tsconfig, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, }); // Correctly bundles with esbuild @@ -508,6 +536,7 @@ test('esbuild bundling with projectRoot and externals and dependencies', () => { projectRoot: repoRoot, depsLockFilePath: packageLock, runtime: Runtime.NODEJS_12_X, + architecture: Architecture.X86_64, externalModules: ['abc'], nodeModules: ['delay'], forceDockerBundling: true, @@ -530,3 +559,120 @@ test('esbuild bundling with projectRoot and externals and dependencies', () => { }), }); }); + +test('esbuild bundling with pre compilations', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + architecture: Architecture.X86_64, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'tsc --project /asset-input/lib/custom-tsconfig.ts --rootDir ./ --outDir ./ &&', + 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); + + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + architecture: Architecture.X86_64, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); + +}); + +test('esbuild bundling with pre compilations with undefined tsconfig ( Should find in root directory )', () => { + Bundling.clearTscCompilationCache(); + const packageLock = path.join(__dirname, '..', 'package-lock.json'); + + Bundling.bundle({ + entry: __filename.replace('.js', '.ts'), + projectRoot: path.dirname(packageLock), + depsLockFilePath: packageLock, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + preCompilation: true, + architecture: Architecture.X86_64, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(packageLock), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'tsc --project /asset-input/tsconfig.json --rootDir ./ --outDir ./ &&', + 'esbuild --bundle \"/asset-input/test/bundling.test.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk', + ].join(' '), + ], + }), + }); +}); + +test('esbuild bundling with pre compilations and undefined tsconfig ( Should throw) ', () => { + expect(() => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + preCompilation: true, + architecture: Architecture.X86_64, + }); + }).toThrow('Cannot find a tsconfig.json, please specify the prop: tsconfig'); + +}); + +test('with custom hash', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + assetHash: 'custom', + architecture: Architecture.X86_64, + }); + + // Correctly passes asset hash options + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), expect.objectContaining({ + assetHash: 'custom', + assetHashType: AssetHashType.CUSTOM, + })); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts new file mode 100644 index 0000000000000..99ca5ee8ceec1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts @@ -0,0 +1,23 @@ +function enumerable(value: boolean) { + return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { + descriptor.enumerable = value; + }; +} + +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + + @enumerable(false) + greet() { + return 'Hello, ' + this.greeting; + } +} + + +export async function handler(): Promise { + const message = new Greeter('World').greet(); + console.log(message); // eslint-disable-line no-console +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json new file mode 100644 index 0000000000000..de56ccb33b617 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json @@ -0,0 +1,198 @@ +{ + "Resources": { + "tsdecoratorhandlerServiceRole61E9E52C": { + "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" + ] + ] + } + ] + } + }, + "tsdecoratorhandlerC8E2F076": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "tsdecoratorhandlerServiceRole61E9E52C", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "tsdecoratorhandlerServiceRole61E9E52C" + ] + }, + "tsdecoratorhandlertsconfigServiceRoleC4AE481E": { + "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" + ] + ] + } + ] + } + }, + "tsdecoratorhandlertsconfig68EC191E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "tsdecoratorhandlertsconfigServiceRoleC4AE481E", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "tsdecoratorhandlertsconfigServiceRoleC4AE481E" + ] + } + }, + "Parameters": { + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38": { + "Type": "String", + "Description": "S3 bucket for asset \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + }, + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7": { + "Type": "String", + "Description": "S3 key for asset version \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + }, + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fArtifactHash237F55B4": { + "Type": "String", + "Description": "Artifact hash for asset \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts new file mode 100644 index 0000000000000..2c78bde18bbf7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts @@ -0,0 +1,39 @@ +/// !cdk-integ pragma:ignore-assets +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + new lambda.NodejsFunction(this, 'ts-decorator-handler', { + entry: path.join(__dirname, 'integ-handlers/ts-decorator-handler.ts'), + bundling: { + minify: true, + sourceMap: true, + sourceMapMode: lambda.SourceMapMode.BOTH, + preCompilation: true, + }, + runtime: Runtime.NODEJS_14_X, + }); + + new lambda.NodejsFunction(this, 'ts-decorator-handler-tsconfig', { + entry: path.join(__dirname, 'integ-handlers/ts-decorator-handler.ts'), + bundling: { + minify: true, + sourceMap: true, + sourceMapMode: lambda.SourceMapMode.BOTH, + tsconfig: path.join(__dirname, '..', 'tsconfig.json'), + preCompilation: true, + }, + runtime: Runtime.NODEJS_14_X, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-compilations-lambda-nodejs'); +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts similarity index 84% rename from packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts rename to packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts index 24b9b512c98bc..e7afddc09fdbb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts @@ -1,12 +1,12 @@ import * as child_process from 'child_process'; -import { EsbuildInstallation } from '../lib/esbuild-installation'; +import { PackageInstallation } from '../lib/package-installation'; import * as util from '../lib/util'; // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies const version = require('esbuild/package.json').version; test('detects local version', () => { - expect(EsbuildInstallation.detect()).toEqual({ + expect(PackageInstallation.detect('esbuild')).toEqual({ isLocal: true, version, }); @@ -23,7 +23,7 @@ test('checks global version if local detection fails', () => { signal: null, }); - expect(EsbuildInstallation.detect()).toEqual({ + expect(PackageInstallation.detect('esbuild')).toEqual({ isLocal: false, version: 'global-version', }); @@ -44,7 +44,7 @@ test('returns undefined on error', () => { signal: null, }); - expect(EsbuildInstallation.detect()).toBeUndefined(); + expect(PackageInstallation.detect('esbuild')).toBeUndefined(); spawnSyncMock.mockRestore(); getModuleVersionMock.mockRestore(); diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index ffd19568aa5dc..f238a3f309410 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -25,14 +25,11 @@ To use this module, you will need to have Docker installed. Define a `PythonFunction`: ```ts -import * as lambda from "@aws-cdk/aws-lambda"; -import { PythonFunction } from "@aws-cdk/aws-lambda-python"; - -new PythonFunction(this, 'MyFunction', { +new lambda.PythonFunction(this, 'MyFunction', { entry: '/path/to/my/function', // required index: 'my_index.py', // optional, defaults to 'index.py' handler: 'my_exported_func', // optional, defaults to 'handler' - runtime: lambda.Runtime.PYTHON_3_6, // optional, defaults to lambda.Runtime.PYTHON_3_7 + runtime: Runtime.PYTHON_3_6, // optional, defaults to lambda.Runtime.PYTHON_3_7 }); ``` @@ -42,11 +39,11 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd 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://gallery.ecr.aws/sam/build-python3.7) -according to the `runtime`. +according to the `runtime` and with the Docker platform based on the target architecture of the Lambda function. -Python bundles are only recreated and published when a file in a source directory has changed. +Python bundles are only recreated and published when a file in a source directory has changed. Therefore (and as a general best-practice), it is highly recommended to commit a lockfile with a -list of all transitive dependencies and their exact versions. +list of all transitive dependencies and their exact versions. This will ensure that when any dependency version is updated, the bundle asset is recreated and uploaded. To that end, we recommend using [`pipenv`] or [`poetry`] which has lockfile support. diff --git a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts index 0c2b4bf624786..722cd2d062fb6 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts @@ -27,6 +27,11 @@ export interface BundlingOptions { */ readonly runtime: lambda.Runtime; + /** + * The system architecture of the lambda function + */ + readonly architecture: lambda.Architecture; + /** * Output path suffix ('python' for a layer, '.' otherwise) */ @@ -77,7 +82,7 @@ export interface BundlingOptions { * Produce bundled Lambda asset code */ export function bundle(options: BundlingOptions): lambda.Code { - const { entry, runtime, outputPathSuffix } = options; + const { entry, runtime, architecture, outputPathSuffix } = options; const stagedir = cdk.FileSystem.mkdtemp('python-bundling-'); const hasDeps = stageDependencies(entry, stagedir); @@ -102,6 +107,7 @@ export function bundle(options: BundlingOptions): lambda.Code { buildArgs: { IMAGE: runtime.bundlingImage.image, }, + platform: architecture.dockerPlatform, file: dockerfile, }); diff --git a/packages/@aws-cdk/aws-lambda-python/lib/function.ts b/packages/@aws-cdk/aws-lambda-python/lib/function.ts index 733c115c0383d..84d21b7564909 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/function.ts @@ -102,12 +102,14 @@ export class PythonFunction extends lambda.Function { const handler = props.handler ?? 'handler'; const runtime = props.runtime ?? lambda.Runtime.PYTHON_3_7; + const architecture = props.architecture ?? lambda.Architecture.X86_64; super(scope, id, { ...props, runtime, code: bundle({ runtime, + architecture, entry, outputPathSuffix: '.', assetHashType: props.assetHashType, diff --git a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts index 1a9684e224580..4f247acc10bae 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts @@ -21,6 +21,12 @@ export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { * @default - All runtimes are supported. */ readonly compatibleRuntimes?: lambda.Runtime[]; + + /** + * The system architectures compatible with this layer. + * @default [Architecture.X86_64] + */ + readonly compatibleArchitectures?: lambda.Architecture[]; } /** @@ -30,6 +36,7 @@ export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { export class PythonLayerVersion extends lambda.LayerVersion { constructor(scope: Construct, id: string, props: PythonLayerVersionProps) { const compatibleRuntimes = props.compatibleRuntimes ?? [lambda.Runtime.PYTHON_3_7]; + const compatibleArchitectures = props.compatibleArchitectures ?? [lambda.Architecture.X86_64]; // Ensure that all compatible runtimes are python for (const runtime of compatibleRuntimes) { @@ -40,8 +47,9 @@ export class PythonLayerVersion extends lambda.LayerVersion { // 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; + // Pick the first compatibleRuntime and compatibleArchitectures to use for bundling + const runtime = compatibleRuntimes[0]; + const architecture = compatibleArchitectures[0]; super(scope, id, { ...props, @@ -49,6 +57,7 @@ export class PythonLayerVersion extends lambda.LayerVersion { code: bundle({ entry, runtime, + architecture, outputPathSuffix: 'python', }), }); diff --git a/packages/@aws-cdk/aws-lambda-python/package.json b/packages/@aws-cdk/aws-lambda-python/package.json index b58c0fd91d57c..6cfec00d17893 100644 --- a/packages/@aws-cdk/aws-lambda-python/package.json +++ b/packages/@aws-cdk/aws-lambda-python/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -68,7 +75,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..516396a167e8e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import * as lambda from '@aws-cdk/aws-lambda-python'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} 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 712852023a367..01449e0cadeaa 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -1,10 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Code, Runtime } from '@aws-cdk/aws-lambda'; -import { FileSystem } from '@aws-cdk/core'; +import { Architecture, Code, Runtime } from '@aws-cdk/aws-lambda'; +import { DockerImage, FileSystem } from '@aws-cdk/core'; import { stageDependencies, bundle } from '../lib/bundling'; jest.spyOn(Code, 'fromAsset'); +jest.spyOn(DockerImage, 'fromBuild'); jest.mock('child_process', () => ({ spawnSync: jest.fn(() => { @@ -28,6 +29,7 @@ test('Bundling a function without dependencies', () => { bundle({ entry: entry, runtime: Runtime.PYTHON_3_7, + architecture: Architecture.X86_64, outputPathSuffix: '.', }); @@ -40,6 +42,13 @@ test('Bundling a function without dependencies', () => { ], }), })); + + expect(DockerImage.fromBuild).toHaveBeenCalledWith(expect.stringMatching(/python-bundling/), expect.objectContaining({ + buildArgs: expect.objectContaining({ + IMAGE: expect.stringMatching(/build-python/), + }), + platform: 'linux/amd64', + })); }); test('Bundling a function with requirements.txt installed', () => { @@ -47,6 +56,7 @@ test('Bundling a function with requirements.txt installed', () => { bundle({ entry: entry, runtime: Runtime.PYTHON_3_7, + architecture: Architecture.X86_64, outputPathSuffix: '.', }); @@ -66,6 +76,7 @@ test('Bundling Python 2.7 with requirements.txt installed', () => { bundle({ entry: entry, runtime: Runtime.PYTHON_2_7, + architecture: Architecture.X86_64, outputPathSuffix: '.', }); @@ -86,6 +97,7 @@ test('Bundling a layer with dependencies', () => { bundle({ entry: entry, runtime: Runtime.PYTHON_2_7, + architecture: Architecture.X86_64, outputPathSuffix: 'python', }); @@ -105,6 +117,7 @@ test('Bundling a python code layer', () => { bundle({ entry: path.join(entry, '.'), runtime: Runtime.PYTHON_2_7, + architecture: Architecture.X86_64, outputPathSuffix: 'python', }); 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 e48b9c2e0ad92..ed3d577fb40a3 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": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3Bucket383ED51E" + "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3Bucket07AE44EE" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C" + "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C" + "Ref": "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3Bucket383ED51E": { + "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3Bucket07AE44EE": { "Type": "String", - "Description": "S3 bucket for asset \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "S3 bucket for asset \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" }, - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2S3VersionKeyA520554C": { + "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374S3VersionKey01F8F2A1": { "Type": "String", - "Description": "S3 key for asset version \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "S3 key for asset version \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" }, - "AssetParametersfc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2ArtifactHashB863A6ED": { + "AssetParameters3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374ArtifactHashDECBC32A": { "Type": "String", - "Description": "Artifact hash for asset \"fc7bfbf72c74b955f7bc25d2bb123c0eeec9557cda17481146d51672768907b2\"" + "Description": "Artifact hash for asset \"3dc2f7b8375fbf383f44eb8f798d324f60d516946c9f829fca3c5f747f973374\"" } }, "Outputs": { 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 index 10fdedeb59ab2..149a1792d9cdb 100644 --- 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 @@ -6,4 +6,4 @@ 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 +pillow==8.3.2 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 10fdedeb59ab2..149a1792d9cdb 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 @@ -6,4 +6,4 @@ 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 +pillow==8.3.2 diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 63b0a97e5df4a..bb42d0811994d 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -14,10 +14,10 @@ This construct library allows you to define AWS Lambda Functions. ```ts -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); ``` @@ -62,8 +62,8 @@ The following `DockerImageFunction` construct uses a local folder with a Dockerfile as the asset that will be used as the function handler. ```ts -new DockerImageFunction(this, 'AssetFunction', { - code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-handler')), +new lambda.DockerImageFunction(this, 'AssetFunction', { + code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-handler')), }); ``` @@ -73,8 +73,8 @@ You can also specify an image that already exists in ECR as the function handler import * as ecr from '@aws-cdk/aws-ecr'; const repo = new ecr.Repository(this, 'Repository'); -new DockerImageFunction(this, 'ECRFunction', { - code: DockerImageCode.fromEcr(repo), +new lambda.DockerImageFunction(this, 'ECRFunction', { + code: lambda.DockerImageCode.fromEcr(repo), }); ``` @@ -90,13 +90,13 @@ The autogenerated Role is automatically given permissions to execute the Lambda function. To reference the autogenerated Role: ```ts -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); -fn.role // the Role +const role = fn.role; // the Role ``` You can also provide your own IAM role. Provided IAM roles will not automatically @@ -104,15 +104,15 @@ be given permissions to execute the Lambda function. To provide a role and grant it appropriate permissions: ```ts -import * as iam from '@aws-cdk/aws-iam'; const myRole = new iam.Role(this, 'My Role', { assumedBy: new iam.ServicePrincipal('sns.amazonaws.com'), }); -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, + +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), - role: myRole // user-provided role + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + role: myRole, // user-provided role }); myRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")); @@ -128,8 +128,8 @@ functions. You can also restrict permissions given to AWS services by providing a source account or ARN (representing the account and identifier of the resource that accesses the function or layer). -```ts fixture=function -import * as iam from '@aws-cdk/aws-iam'; +```ts +declare const fn: lambda.Function; const principal = new iam.ServicePrincipal('my-service'); fn.grantInvoke(principal); @@ -151,8 +151,8 @@ principal in question has conditions limiting the source account or ARN of the operation (see above), these conditions will be automatically added to the resource policy. -```ts fixture=function -import * as iam from '@aws-cdk/aws-iam'; +```ts +declare const fn: lambda.Function; const servicePrincipal = new iam.ServicePrincipal('my-service'); const sourceArn = 'arn:aws:s3:::my-bucket'; const sourceAccount = '111122223333'; @@ -193,8 +193,8 @@ The function version includes the following information: You could create a version to your lambda function using the `Version` construct. ```ts -const fn = new Function(this, 'MyFunction', ...); -const version = new Version(this, 'MyVersion', { +declare const fn: lambda.Function; +const version = new lambda.Version(this, 'MyVersion', { lambda: fn, }); ``` @@ -211,9 +211,12 @@ latest code. For instance - ```ts const codeVersion = "stringOrMethodToGetCodeVersion"; const fn = new lambda.Function(this, 'MyFunction', { - environment: { - 'CodeVersionString': codeVersion - } + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + environment: { + 'CodeVersionString': codeVersion, + }, }); ``` @@ -291,16 +294,14 @@ specify options for the current version through the `currentVersionOptions` property. ```ts -import * as cdk from '@aws-cdk/core'; - -const fn = new Function(this, 'MyFunction', { +const fn = new lambda.Function(this, 'MyFunction', { currentVersionOptions: { - removalPolicy: cdk.RemovalPolicy.RETAIN, // retain old versions - retryAttempts: 1 // async retry attempts + removalPolicy: RemovalPolicy.RETAIN, // retain old versions + retryAttempts: 1, // async retry attempts }, - runtime: Runtime.NODEJS_12_X, + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); fn.currentVersion.addAlias('live'); @@ -318,11 +319,9 @@ By default, updating a layer creates a new layer version, and CloudFormation wil Alternatively, a removal policy can be used to retain the old version: ```ts -import * as cdk from '@aws-cdk/core'; - -new LayerVersion(this, 'MyLayer', { - removalPolicy: cdk.RemovalPolicy.RETAIN, - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), +new lambda.LayerVersion(this, 'MyLayer', { + removalPolicy: RemovalPolicy.RETAIN, + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); ``` @@ -333,21 +332,24 @@ Lambda functions, by default, run on compute systems that have the 64 bit x86 ar The AWS Lambda service also runs compute on the ARM architecture, which can reduce cost for some workloads. -A lambda function can be configured to be run on one or both of these platforms - +A lambda function can be configured to be run on one of these platforms: ```ts -new Function(this, 'MyFunction', { - ... - architectures: [ Architecture.X86_64, Architecture.ARM_64 ], +new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + architecture: lambda.Architecture.ARM_64, }); ``` Similarly, lambda layer versions can also be tagged with architectures it is compatible with. ```ts -new LayerVersion(this, 'MyLayer', { - ... - compatibleArchitectures: [ Architecture.X86_64, Architecture.ARM_64 ], +new lambda.LayerVersion(this, 'MyLayer', { + removalPolicy: RemovalPolicy.RETAIN, + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + compatibleArchitectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64], }); ``` @@ -357,20 +359,24 @@ Lambda functions can be configured to use CloudWatch [Lambda Insights](https://d which provides low-level runtime metrics for a Lambda functions. ```ts -import * as lambda from '@aws-cdk/lambda'; - -new Function(this, 'MyFunction', { - insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0 -}) +new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0, +}); ``` If the version of insights is not yet available in the CDK, you can also provide the ARN directly as so - ```ts const layerArn = 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:14'; -new Function(this, 'MyFunction', { - insightsVersion: lambda.LambdaInsightsVersion.fromInsightVersionArn(layerArn) -}) +new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + insightsVersion: lambda.LambdaInsightsVersion.fromInsightVersionArn(layerArn), +}); ``` ## Event Rule Target @@ -378,9 +384,11 @@ new Function(this, 'MyFunction', { You can use an AWS Lambda function as a target for an Amazon CloudWatch event rule: -```ts fixture=function +```ts import * as events from '@aws-cdk/aws-events'; import * as targets from '@aws-cdk/aws-events-targets'; + +declare const fn: lambda.Function; const rule = new events.Rule(this, 'Schedule Rule', { schedule: events.Schedule.cron({ minute: '0', hour: '4' }), }); @@ -402,18 +410,22 @@ includes classes for the various event sources supported by AWS Lambda. For example, the following code adds an SQS queue as an event source for a function: -```ts fixture=function +```ts import * as eventsources from '@aws-cdk/aws-lambda-event-sources'; import * as sqs from '@aws-cdk/aws-sqs'; + +declare const fn: lambda.Function; const queue = new sqs.Queue(this, 'Queue'); fn.addEventSource(new eventsources.SqsEventSource(queue)); ``` The following code adds an S3 bucket notification as an event source: -```ts fixture=function +```ts import * as eventsources from '@aws-cdk/aws-lambda-event-sources'; import * as s3 from '@aws-cdk/aws-s3'; + +declare const fn: lambda.Function; const bucket = new s3.Bucket(this, 'Bucket'); fn.addEventSource(new eventsources.S3EventSource(bucket, { events: [ s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED ], @@ -429,11 +441,11 @@ A dead-letter queue can be automatically created for a Lambda function by setting the `deadLetterQueueEnabled: true` configuration. ```ts -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - deadLetterQueueEnabled: true +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + deadLetterQueueEnabled: true, }); ``` @@ -443,11 +455,11 @@ It is also possible to provide a dead-letter queue instead of getting a new queu import * as sqs from '@aws-cdk/aws-sqs'; const dlq = new sqs.Queue(this, 'DLQ'); -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - deadLetterQueue: dlq +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + deadLetterQueue: dlq, }); ``` @@ -457,11 +469,11 @@ to learn more about AWS Lambdas and DLQs. ## Lambda with X-Ray Tracing ```ts -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - tracing: Tracing.ACTIVE +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + tracing: lambda.Tracing.ACTIVE, }); ``` @@ -474,13 +486,11 @@ The following code configures the lambda function with CodeGuru profiling. By de profiling group - ```ts -import * as lambda from '@aws-cdk/aws-lambda'; - -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.PYTHON_3_6, - handler: 'index.handler', - code: Code.fromAsset('lambda-handler'), - profiling: true +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda-handler'), + profiling: true, }); ``` @@ -494,11 +504,11 @@ to learn more about AWS Lambda's Profiling support. ## Lambda with Reserved Concurrent Executions ```ts -const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - reservedConcurrentExecutions: 100 +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + reservedConcurrentExecutions: 100, }); ``` @@ -509,15 +519,17 @@ managing concurrency. You can use Application AutoScaling to automatically configure the provisioned concurrency for your functions. AutoScaling can be set to track utilization or be based on a schedule. To configure AutoScaling on a function alias: -```ts fixture=function +```ts import * as autoscaling from '@aws-cdk/aws-autoscaling'; -const alias = new Alias(this, 'Alias', { + +declare const fn: lambda.Function; +const alias = new lambda.Alias(this, 'Alias', { aliasName: 'prod', version: fn.latestVersion, }); // Create AutoScaling target -const as = alias.addAutoScaling({ maxCapacity: 50 }) +const as = alias.addAutoScaling({ maxCapacity: 50 }); // Configure Target Tracking as.scaleOnUtilization({ @@ -591,12 +603,12 @@ const accessPoint = fileSystem.addAccessPoint('AccessPoint', { }, }); -const fn = new Function(this, 'MyLambda', { +const fn = new lambda.Function(this, 'MyLambda', { // mount the access point to /mnt/msg in the lambda runtime environment - filesystem: FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), - runtime: Runtime.NODEJS_12_X, + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), vpc, }); ``` @@ -626,17 +638,17 @@ Docker container is responsible for putting content at `/asset-output`. The cont Example with Python: ```ts -new Function(this, 'Function', { - code: Code.fromAsset(path.join(__dirname, 'my-python-handler'), { +new lambda.Function(this, 'Function', { + code: lambda.Code.fromAsset(path.join(__dirname, 'my-python-handler'), { bundling: { - image: Runtime.PYTHON_3_9.bundlingImage, + image: lambda.Runtime.PYTHON_3_9.bundlingImage, command: [ 'bash', '-c', 'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output' ], }, }), - runtime: Runtime.PYTHON_3_9, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.handler', }); ``` @@ -647,12 +659,10 @@ Use `cdk.DockerImage.fromRegistry(image)` to use an existing image or `cdk.DockerImage.fromBuild(path)` to build a specific image: ```ts -import * as cdk from '@aws-cdk/core'; - -new Function(this, 'Function', { - code: Code.fromAsset('/path/to/handler', { +new lambda.Function(this, 'Function', { + code: lambda.Code.fromAsset('/path/to/handler', { bundling: { - image: cdk.DockerImage.fromBuild('/path/to/dir/with/DockerFile', { + image: DockerImage.fromBuild('/path/to/dir/with/DockerFile', { buildArgs: { ARG1: 'value1', }, @@ -660,7 +670,7 @@ new Function(this, 'Function', { command: ['my', 'cool', 'command'], }, }), - runtime: Runtime.PYTHON_3_9, + runtime: lambda.Runtime.PYTHON_3_9, handler: 'index.handler', }); ``` @@ -674,26 +684,26 @@ Language-specific higher level constructs are provided in separate modules: ## Code Signing -Code signing for AWS Lambda helps to ensure that only trusted code runs in your Lambda functions. +Code signing for AWS Lambda helps to ensure that only trusted code runs in your Lambda functions. When enabled, AWS Lambda checks every code deployment and verifies that the code package is signed by a trusted source. For more information, see [Configuring code signing for AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-codesigning.html). The following code configures a function with code signing. -```typescript +```ts import * as signer from '@aws-cdk/aws-signer'; const signingProfile = new signer.SigningProfile(this, 'SigningProfile', { - platform: signer.Platform.AWS_LAMBDA_SHA384_ECDSA + platform: signer.Platform.AWS_LAMBDA_SHA384_ECDSA, }); -const codeSigningConfig = new CodeSigningConfig(this, 'CodeSigningConfig', { +const codeSigningConfig = new lambda.CodeSigningConfig(this, 'CodeSigningConfig', { signingProfiles: [signingProfile], }); -new Function(this, 'Function', { +new lambda.Function(this, 'Function', { codeSigningConfig, - runtime: Runtime.NODEJS_12_X, + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); ``` diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 4287ffde73d6e..e497ad2e29071 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,6 +1,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IFunction, QualifiedFunctionBase } from './function-base'; @@ -167,7 +168,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { service: 'lambda', resource: 'function', resourceName: `${this.lambda.functionName}:${this.physicalName}`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.qualifier = extractQualifierFromArn(alias.ref); @@ -184,7 +185,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { // ARN parsing splits on `:`, so we can only get the function's name from the ARN as resourceName... // And we're parsing it out (instead of using the underlying function directly) in order to have use of it incur // an implicit dependency on the resource. - this.functionName = `${this.stack.parseArn(this.functionArn, ':').resourceName!}:${this.aliasName}`; + this.functionName = `${this.stack.splitArn(this.functionArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!}:${this.aliasName}`; } public get grantPrincipal() { @@ -198,7 +199,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { public metric(metricName: string, props: cloudwatch.MetricOptions = {}): cloudwatch.Metric { // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differs from the base behavior. return super.metric(metricName, { - dimensions: { + dimensionsMap: { FunctionName: this.lambda.functionName, // construct the name from the underlying lambda so that alarms on an alias // don't cause a circular dependency with CodeDeploy diff --git a/packages/@aws-cdk/aws-lambda/lib/architecture.ts b/packages/@aws-cdk/aws-lambda/lib/architecture.ts index 40edee1896755..ae86624ca006b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/architecture.ts +++ b/packages/@aws-cdk/aws-lambda/lib/architecture.ts @@ -5,20 +5,21 @@ export class Architecture { /** * 64 bit architecture with x86 instruction set. */ - public static readonly X86_64 = new Architecture('x86_64'); + public static readonly X86_64 = new Architecture('x86_64', 'linux/amd64'); /** * 64 bit architecture with the ARM instruction set. */ - public static readonly ARM_64 = new Architecture('arm64'); + public static readonly ARM_64 = new Architecture('arm64', 'linux/arm64'); /** * Used to specify a custom architecture name. * Use this if the architecture name is not yet supported by the CDK. * @param name the architecture name as recognized by AWS Lambda. + * @param [dockerPlatform=linux/amd64] the platform to use for this architecture when building with Docker */ - public static custom(name: string) { - return new Architecture(name); + public static custom(name: string, dockerPlatform?: string) { + return new Architecture(name, dockerPlatform ?? 'linux/amd64'); } /** @@ -26,7 +27,13 @@ export class Architecture { */ public readonly name: string; - private constructor(archName: string) { + /** + * The platform to use for this architecture when building with Docker. + */ + public readonly dockerPlatform: string; + + private constructor(archName: string, dockerPlatform: string) { this.name = archName; + this.dockerPlatform = dockerPlatform; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts b/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts index 5e76436b75cca..9f9d017bc1ed7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts @@ -1,5 +1,5 @@ import { ISigningProfile } from '@aws-cdk/aws-signer'; -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnCodeSigningConfig } from './lambda.generated'; @@ -79,7 +79,7 @@ export class CodeSigningConfig extends Resource implements ICodeSigningConfig { * @param codeSigningConfigArn The ARN of code signing config. */ public static fromCodeSigningConfigArn( scope: Construct, id: string, codeSigningConfigArn: string): ICodeSigningConfig { - const codeSigningProfileId = Stack.of(scope).parseArn(codeSigningConfigArn).resourceName; + const codeSigningProfileId = Stack.of(scope).splitArn(codeSigningConfigArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!codeSigningProfileId) { throw new Error(`Code signing config ARN must be in the format 'arn:aws:lambda:::code-signing-config:', got: '${codeSigningConfigArn}'`); } diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 293c91f1485d9..f51e91de9bfb7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -517,28 +517,45 @@ export interface AssetImageCodeProps extends ecr_assets.DockerImageAssetOptions */ export class AssetImageCode extends Code { public readonly isInline: boolean = false; + private asset?: ecr_assets.DockerImageAsset; constructor(private readonly directory: string, private readonly props: AssetImageCodeProps) { super(); } public bind(scope: Construct): CodeConfig { - const asset = new ecr_assets.DockerImageAsset(scope, 'AssetImage', { - directory: this.directory, - ...this.props, - }); - - asset.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); + // If the same AssetImageCode is used multiple times, retain only the first instantiation. + if (!this.asset) { + this.asset = new ecr_assets.DockerImageAsset(scope, 'AssetImage', { + directory: this.directory, + ...this.props, + }); + this.asset.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); + } else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) { + throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` + + 'Create a new Code instance for every stack.'); + } return { image: { - imageUri: asset.imageUri, + imageUri: this.asset.imageUri, entrypoint: this.props.entrypoint, cmd: this.props.cmd, workingDirectory: this.props.workingDirectory, }, }; } + + public bindToResource(resource: cdk.CfnResource, options: ResourceBindOptions = { }) { + if (!this.asset) { + throw new Error('bindToResource() must be called after bind()'); + } + + const resourceProperty = options.resourceProperty || 'Code.ImageUri'; + + // https://github.com/aws/aws-cdk/issues/14593 + this.asset.addResourceMetadata(resource, resourceProperty); + } } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 746cb23438c51..8a28f2e423657 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { ConstructNode, IResource, Resource, Token } from '@aws-cdk/core'; +import { ArnFormat, ConstructNode, IResource, Resource, Token } from '@aws-cdk/core'; import { AliasOptions } from './alias'; import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; @@ -383,7 +383,7 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC if (Token.isUnresolved(this.stack.account) || Token.isUnresolved(this.functionArn)) { return false; } - return this.stack.parseArn(this.functionArn).account === this.stack.account; + return this.stack.splitArn(this.functionArn, ArnFormat.SLASH_RESOURCE_NAME).account === this.stack.account; } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 7505cdf463a1b..c3936287a0990 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -5,7 +5,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; +import { Annotations, ArnFormat, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Architecture } from './architecture'; import { Code, CodeConfig } from './code'; @@ -220,6 +220,10 @@ export interface FunctionOptions extends EventInvokeConfigOptions { * Specify the version of CloudWatch Lambda insights to use for monitoring * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights.html * + * When used with `DockerImageFunction` or `DockerImageCode`, the Docker image should have + * the Lambda insights agent installed. + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-Getting-Started-docker.html + * * @default - No Lambda Insights */ readonly insightsVersion?: LambdaInsightsVersion; @@ -569,8 +573,13 @@ export class Function extends FunctionBase { */ public readonly deadLetterQueue?: sqs.IQueue; + /** + * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64). + */ + public readonly architecture?: Architecture; public readonly permissionsNode = this.node; + protected readonly canCreatePermissions = true; private readonly layers: ILayerVersion[] = []; @@ -712,11 +721,13 @@ export class Function extends FunctionBase { service: 'lambda', resource: 'function', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.runtime = props.runtime; + this.architecture = props.architecture; + if (props.layers) { if (props.runtime === Runtime.FROM_IMAGE) { throw new Error('Layers are not supported for container image functions'); @@ -782,9 +793,7 @@ export class Function extends FunctionBase { } // Configure Lambda insights - if (props.insightsVersion !== undefined) { - this.configureLambdaInsights(props.insightsVersion); - } + this.configureLambdaInsights(props); } /** @@ -912,8 +921,15 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett * * https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html */ - private configureLambdaInsights(insightsVersion: LambdaInsightsVersion): void { - this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', insightsVersion.layerVersionArn)); + private configureLambdaInsights(props: FunctionProps): void { + if (props.insightsVersion === undefined) { + return; + } + if (props.runtime !== Runtime.FROM_IMAGE) { + // Layers cannot be added to Lambda container images. The image should have the insights agent installed. + // See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-Getting-Started-docker.html + this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', props.insightsVersion.layerVersionArn)); + } this.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy')); } diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 431b9bf6a71d9..c096730b1e8eb 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,11 +1,14 @@ 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 cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { Function as LambdaFunction, FunctionProps } from './function'; +import { Function as LambdaFunction, FunctionProps, EnvironmentOptions } from './function'; import { FunctionBase } from './function-base'; import { Version } from './lambda-version'; +import { ILayerVersion } from './layers'; import { Permission } from './permission'; +import { Runtime } from './runtime'; /** * Properties for a newly created singleton Lambda @@ -47,6 +50,12 @@ export class SingletonFunction extends FunctionBase { public readonly functionArn: string; public readonly role?: iam.IRole; public readonly permissionsNode: cdk.ConstructNode; + + /** + * The runtime environment for the Lambda function. + */ + public readonly runtime: Runtime; + protected readonly canCreatePermissions: boolean; private lambdaFunction: LambdaFunction; @@ -59,6 +68,7 @@ export class SingletonFunction extends FunctionBase { this.functionArn = this.lambdaFunction.functionArn; this.functionName = this.lambdaFunction.functionName; this.role = this.lambdaFunction.role; + this.runtime = this.lambdaFunction.runtime; this.grantPrincipal = this.lambdaFunction.grantPrincipal; this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway @@ -78,6 +88,20 @@ export class SingletonFunction extends FunctionBase { return this.lambdaFunction.connections; } + /** + * The LogGroup where the Lambda function's logs are made available. + * + * If either `logRetention` is set or this property is called, a CloudFormation custom resource is added to the stack that + * pre-creates the log group as part of the stack deployment, if it already doesn't exist, and sets the correct log retention + * period (never expire, by default). + * + * Further, if the log group already exists and the `logRetention` is not set, the custom resource will reset the log retention + * to never expire even if it was configured with a different value. + */ + public get logGroup(): logs.ILogGroup { + return this.lambdaFunction.logGroup; + } + /** * Returns a `lambda.Version` which represents the current version of this * singleton Lambda function. A new version will be created every time the @@ -90,6 +114,28 @@ export class SingletonFunction extends FunctionBase { return this.lambdaFunction.currentVersion; } + /** + * Adds an environment variable to this Lambda function. + * If this is a ref to a Lambda function, this operation results in a no-op. + * @param key The environment variable key. + * @param value The environment variable's value. + * @param options Environment variable options. + */ + public addEnvironment(key: string, value: string, options?: EnvironmentOptions) { + return this.lambdaFunction.addEnvironment(key, value, options); + } + + /** + * Adds one or more Lambda Layers to this Lambda function. + * + * @param layers the layers to be added. + * + * @throws if there are already 5 layers on this function, or the layer is incompatible with this function's runtime. + */ + public addLayers(...layers: ILayerVersion[]) { + return this.lambdaFunction.addLayers(...layers); + } + public addPermission(name: string, permission: Permission) { return this.lambdaFunction.addPermission(name, permission); } diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index b00072d041a4d..93b724cdc1282 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -82,10 +89,10 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cfnspec": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", - "@types/lodash": "^4.14.175", - "jest": "^26.6.3", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", + "@types/lodash": "^4.14.177", + "jest": "^27.3.1", "lodash": "^4.17.21" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture index 417454806bb9d..f473db8d31bd5 100644 --- a/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture @@ -1,7 +1,9 @@ // Fixture with packages imported, but nothing else import * as path from 'path'; -import { Construct, Stack } from '@aws-cdk/core'; -import { Alias, Code, CodeSigningConfig, DockerImageCode, DockerImageFunction, FileSystem, Function, LayerVersion, Runtime, Tracing } from '@aws-cdk/aws-lambda'; +import { Construct } from 'constructs'; +import { DockerImage, RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as iam from '@aws-cdk/aws-iam'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-lambda/rosetta/function.ts-fixture b/packages/@aws-cdk/aws-lambda/rosetta/function.ts-fixture deleted file mode 100644 index 91eafe0abfcc0..0000000000000 --- a/packages/@aws-cdk/aws-lambda/rosetta/function.ts-fixture +++ /dev/null @@ -1,18 +0,0 @@ -// Fixture with function (`fn`) already created -import * as path from 'path'; -import { Construct, Stack } from '@aws-cdk/core'; -import { Alias, Code, Function, Runtime } from '@aws-cdk/aws-lambda'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), - }); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-lambda/test/alias.test.ts b/packages/@aws-cdk/aws-lambda/test/alias.test.ts index bfe85e7acddd2..a470ace2a366a 100644 --- a/packages/@aws-cdk/aws-lambda/test/alias.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/alias.test.ts @@ -2,11 +2,12 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Lazy, Stack } from '@aws-cdk/core'; import * as lambda from '../lib'; describe('alias', () => { - test('version and aliases', () => { + testDeprecated('version and aliases', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -53,7 +54,7 @@ describe('alias', () => { expect(stack).not.toHaveResource('AWS::Lambda::Version'); }); - test('can use newVersion to create a new Version', () => { + testDeprecated('can use newVersion to create a new Version', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -78,7 +79,7 @@ describe('alias', () => { }); }); - test('can add additional versions to alias', () => { + testDeprecated('can add additional versions to alias', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -109,7 +110,7 @@ describe('alias', () => { }); }); - test('version and aliases with provisioned execution', () => { + testDeprecated('version and aliases with provisioned execution', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -150,7 +151,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; // WHEN: Individual weight too high expect(() => { @@ -181,7 +182,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN @@ -214,7 +215,7 @@ describe('alias', () => { }); }); - test('sanity checks provisionedConcurrentExecutions', () => { + testDeprecated('sanity checks provisionedConcurrentExecutions', () => { const stack = new Stack(); const pce = -1; @@ -228,7 +229,7 @@ describe('alias', () => { expect(() => { new lambda.Alias(stack, 'Alias1', { aliasName: 'prod', - version: fn.addVersion('1'), + version: fn.currentVersion, provisionedConcurrentExecutions: pce, }); }).toThrow(); @@ -259,7 +260,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // THEN @@ -276,7 +277,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN @@ -311,12 +312,11 @@ describe('alias', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); // WHEN new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, onSuccess: { bind: () => ({ destination: 'on-success-arn', @@ -358,10 +358,9 @@ describe('alias', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, onSuccess: { bind: () => ({ destination: 'on-success-arn', @@ -392,7 +391,7 @@ describe('alias', () => { }); }); - test('can enable AutoScaling on aliases', () => { + testDeprecated('can enable AutoScaling on aliases', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -401,11 +400,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -441,11 +438,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, provisionedConcurrentExecutions: 10, }); @@ -488,11 +483,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, provisionedConcurrentExecutions: 10, }); @@ -520,11 +513,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -543,11 +534,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -566,11 +555,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 194ebda9aff37..40db469cc12d4 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -2,9 +2,9 @@ import '@aws-cdk/assert-internal/jest'; import * as path from 'path'; import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; import * as ecr from '@aws-cdk/aws-ecr'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as lambda from '../lib'; /* eslint-disable dot-notation */ @@ -332,6 +332,110 @@ describe('code', () => { }, }); }); + + test('only one Asset object gets created even if multiple functions use the same AssetImageCode', () => { + // given + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + const directoryAsset = lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')); + + // when + new lambda.Function(stack, 'Fn1', { + code: directoryAsset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + new lambda.Function(stack, 'Fn2', { + code: directoryAsset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + const assembly = app.synth(); + const synthesized = assembly.stacks[0]; + + // Func1 has an asset, Func2 does not + expect(synthesized.assets.length).toEqual(1); + }); + + test('adds code asset metadata', () => { + // given + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + + const dockerfilePath = 'Dockerfile'; + const dockerBuildTarget = 'stage'; + const dockerBuildArgs = { arg1: 'val1', arg2: 'val2' }; + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler'), { + file: dockerfilePath, + target: dockerBuildTarget, + buildArgs: dockerBuildArgs, + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.650a009a909c30e767a843a84ff7812616447251d245e0ab65d9bfb37f413e32', + [cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY]: dockerfilePath, + [cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY]: dockerBuildArgs, + [cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY]: dockerBuildTarget, + [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code.ImageUri', + }, + }, ResourcePart.CompleteDefinition); + }); + + test('adds code asset metadata with default dockerfile path', () => { + // given + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.a3cc4528c34874616814d9b3436ff0e5d01514c1d563ed8899657ca00982f308', + [cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY]: 'Dockerfile', + [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code.ImageUri', + }, + }, ResourcePart.CompleteDefinition); + }); + + test('fails if asset is bound with a second stack', () => { + // given + const app = new cdk.App(); + const asset = lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')); + + // when + const stack1 = new cdk.Stack(app, 'Stack1'); + new lambda.Function(stack1, 'Fn', { + code: asset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + const stack2 = new cdk.Stack(app, 'Stack2'); + + // then + expect(() => new lambda.Function(stack2, 'Fn', { + code: asset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + })).toThrow(/already associated/); + }); }); describe('lambda.Code.fromDockerBuild', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 66d18e663e7f9..965f41aed66f7 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -10,6 +10,7 @@ import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as signer from '@aws-cdk/aws-signer'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as _ from 'lodash'; @@ -1382,7 +1383,7 @@ describe('function', () => { }); }); - test('add a version with event invoke config', () => { + testDeprecated('add a version with event invoke config', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'fn', { @@ -2174,7 +2175,7 @@ describe('function', () => { })).toThrow(/Layers are not supported for container image functions/); }); - test('specified architectures is recognized', () => { + testDeprecated('specified architectures is recognized', () => { const stack = new cdk.Stack(); new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2204,7 +2205,7 @@ describe('function', () => { }); }); - test('both architectures and architecture are not recognized', () => { + testDeprecated('both architectures and architecture are not recognized', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2216,7 +2217,7 @@ describe('function', () => { })).toThrow(/architecture or architectures must be specified/); }); - test('Only one architecture allowed', () => { + testDeprecated('Only one architecture allowed', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2226,7 +2227,16 @@ describe('function', () => { architectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64], })).toThrow(/one architecture must be specified/); }); - + test('Architecture is properly readable from the function', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'MyFunction', { + code: lambda.Code.fromInline('foo'), + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + architecture: lambda.Architecture.ARM_64, + }); + expect(fn.architecture?.name).toEqual('arm64'); + }); }); function newTestLambda(scope: constructs.Construct) { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json index dc1f0b431db23..1ee05ac72cef8 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json @@ -50,13 +50,12 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaVersion16CDE3C40": { + "MyLambdaCurrentVersionE7A382CC03fc10af301b823dc69dee9357b5caa0": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { "Ref": "MyLambdaCCE802FB" - }, - "Description": "integ-test" + } } }, "Alias325C5727": { @@ -67,7 +66,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersion16CDE3C40", + "MyLambdaCurrentVersionE7A382CC03fc10af301b823dc69dee9357b5caa0", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts index 949670ec636b6..e8d3411b072f3 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts @@ -17,7 +17,7 @@ class TestStack extends cdk.Stack { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'integ-test'); + const version = fn.currentVersion; const alias = new lambda.Alias(this, 'Alias', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json index 86a20d58e17c1..66f651fdbf280 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.main", "Role": { "Fn::GetAtt": [ "MyLambdaServiceRole4539ECB6", "Arn" ] }, + "Handler": "index.main", "Runtime": "python3.8" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json index 65f9ebbeb5855..88e7b53442a15 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json @@ -72,7 +72,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaVersion16CDE3C40": { + "MyLambdaCurrentVersionE7A382CC306b64ef431b3e873cc6258340b63a78": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -88,7 +88,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersion16CDE3C40", + "MyLambdaCurrentVersionE7A382CC306b64ef431b3e873cc6258340b63a78", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json index ad788ff4425b6..15a57b7a0e598 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json @@ -72,7 +72,7 @@ "MyLambdaAliasPCEServiceRoleF7C9F212" ] }, - "MyLambdaAliasPCEVersion15F479C08": { + "MyLambdaAliasPCECurrentVersion072335D3974767ca5ab9a8786a5779ede8cb8cc5": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -88,7 +88,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaAliasPCEVersion15F479C08", + "MyLambdaAliasPCECurrentVersion072335D3974767ca5ab9a8786a5779ede8cb8cc5", "Version" ] }, @@ -180,7 +180,7 @@ "MyLambdaVersionPCEServiceRole2ACFB73E" ] }, - "MyLambdaVersionPCEVersion2C704112A": { + "MyLambdaVersionPCECurrentVersion27FC3932a1bc5d5d20600bf4225d17df43a36ea5": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -199,7 +199,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersionPCEVersion2C704112A", + "MyLambdaVersionPCECurrentVersion27FC3932a1bc5d5d20600bf4225d17df43a36ea5", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts index 0544d2535784c..4a100b4d6e462 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts @@ -23,7 +23,7 @@ fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version = fn.addVersion('1'); +const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', @@ -40,6 +40,9 @@ const fnVersionPCE = new lambda.Function(stack, 'MyLambdaVersionPCE', { code: new lambda.InlineCode(lambdaCode.replace('#type#', 'Version')), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, + currentVersionOptions: { + provisionedConcurrentExecutions: pce, + }, }); fnVersionPCE.addToRolePolicy(new iam.PolicyStatement({ @@ -47,7 +50,7 @@ fnVersionPCE.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version2 = fnVersionPCE.addVersion('2', undefined, undefined, pce); +const version2 = fnVersionPCE.currentVersion; const alias2 = new lambda.Alias(stack, 'Alias2', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts index e2a97353c00e6..c6ca7302a1f91 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts @@ -17,7 +17,7 @@ fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version = fn.addVersion('1'); +const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts index 990c0c7fb7405..762df158da6f4 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-insights.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { MatchStyle } from '@aws-cdk/assert-internal'; +import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -313,4 +314,43 @@ describe('lambda-insights', () => { // On synthesis it should not throw an error expect(() => app.synth()).not.toThrow(); }); + + test('insights layer is skipped for container images and the role is updated', () => { + const stack = new cdk.Stack(); + new lambda.DockerImageFunction(stack, 'MyFunction', { + code: lambda.DockerImageCode.fromEcr(ecr.Repository.fromRepositoryArn(stack, 'MyRepo', + 'arn:aws:ecr:us-east-1:0123456789:repository/MyRepo')), + insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_98_0, + }); + + expect(stack).toCountResources('AWS::Lambda::LayerVersion', 0); + + expect(stack).toHaveResourceLike('AWS::IAM::Role', { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Principal': { + 'Service': 'lambda.amazonaws.com', + }, + }, + ], + }, + 'ManagedPolicyArns': [ + { }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy', + ], + ], + }, + ], + }); + }); }); diff --git a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts index 3bf78253d5ed6..4200b7a18a6e5 100644 --- a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { ResourcePart } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -109,6 +110,57 @@ describe('singleton lambda', () => { }, ResourcePart.CompleteDefinition); }); + test('Environment is added to Lambda, when .addEnvironment() is provided one key pair', () => { + // GIVEN + const stack = new cdk.Stack(); + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // WHEN + singleton.addEnvironment('KEY', 'value'); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + KEY: 'value', + }, + }, + }); + }); + + test('Layer is added to Lambda, when .addLayers() is provided a valid layer', () => { + // GIVEN + const stack = new cdk.Stack(); + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + const bucket = new s3.Bucket(stack, 'Bucket'); + const layer = new lambda.LayerVersion(stack, 'myLayer', { + code: new lambda.S3Code(bucket, 'ObjectKey'), + compatibleRuntimes: [lambda.Runtime.PYTHON_2_7], + }); + + // WHEN + singleton.addLayers(layer); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Layers: [{ + Ref: 'myLayerBA1B098A', + }], + }); + }); + test('grantInvoke works correctly', () => { // GIVEN const stack = new cdk.Stack(); @@ -154,6 +206,41 @@ describe('singleton lambda', () => { .toThrow(/contains environment variables .* and is not compatible with Lambda@Edge/); }); + test('logGroup is correctly returned', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // THEN + expect(singleton.logGroup.logGroupName).toBeDefined(); + expect(singleton.logGroup.logGroupArn).toBeDefined(); + }); + + test('runtime is correctly returned', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // THEN + expect(singleton.runtime).toStrictEqual(lambda.Runtime.PYTHON_2_7); + }); + test('current version of a singleton function', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index 2ecd2819becf5..409ffc5fa3a45 100644 --- a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -41,7 +42,7 @@ describe('lambda + vpc', () => { }); }); - test('has securitygroup that is passed in props', () => { + testDeprecated('has securitygroup that is passed in props', () => { // WHEN new lambda.Function(stack, 'LambdaWithCustomSG', { code: new lambda.InlineCode('foo'), @@ -91,7 +92,7 @@ describe('lambda + vpc', () => { }); }); - test('fails if both of securityGroup and securityGroups are passed in props at once', () => { + testDeprecated('fails if both of securityGroup and securityGroups are passed in props at once', () => { // THEN expect(() => { new lambda.Function(stack, 'LambdaWithWrongProps', { diff --git a/packages/@aws-cdk/aws-licensemanager/package.json b/packages/@aws-cdk/aws-licensemanager/package.json index 0e2ca75813a54..937f71dc91629 100644 --- a/packages/@aws-cdk/aws-licensemanager/package.json +++ b/packages/@aws-cdk/aws-licensemanager/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-lightsail/package.json b/packages/@aws-cdk/aws-lightsail/package.json index 4af08f90eb479..926c902210254 100644 --- a/packages/@aws-cdk/aws-lightsail/package.json +++ b/packages/@aws-cdk/aws-lightsail/package.json @@ -81,7 +81,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-location/package.json b/packages/@aws-cdk/aws-location/package.json index 745400558765a..f69267cda2085 100644 --- a/packages/@aws-cdk/aws-location/package.json +++ b/packages/@aws-cdk/aws-location/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-logs-destinations/package.json b/packages/@aws-cdk/aws-logs-destinations/package.json index 13f8f5c567006..308d6ec0085c6 100644 --- a/packages/@aws-cdk/aws-logs-destinations/package.json +++ b/packages/@aws-cdk/aws-logs-destinations/package.json @@ -69,8 +69,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-logs/README.md b/packages/@aws-cdk/aws-logs/README.md index 497526ca6ec60..804b5c56c4eb3 100644 --- a/packages/@aws-cdk/aws-logs/README.md +++ b/packages/@aws-cdk/aws-logs/README.md @@ -48,6 +48,27 @@ By default, the log group will be created in the same region as the stack. The ` log groups in other regions. This is typically useful when controlling retention for log groups auto-created by global services that publish their log group to a specific region, such as AWS Chatbot creating a log group in `us-east-1`. +## Resource Policy + +CloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the log groups. +A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time: + +```ts +const logGroup = new logs.LogGroup(this, 'LogGroup'); +logGroup.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + principals: [new iam.ServicePrincipal('es.amazonaws.com')], + resources: [logGroup.logGroupArn], +})); +``` + +Or more conveniently, write permissions to the log group can be granted as follows which gives same result as in the above example. + +```ts +const logGroup = new logs.LogGroup(this, 'LogGroup'); +logGroup.grantWrite(new iam.ServicePrincipal('es.amazonaws.com')); +``` + ## Encrypting Log Groups By default, log group data is always encrypted in CloudWatch Logs. You have the @@ -61,7 +82,7 @@ Here's a simple example of creating an encrypted Log Group using a KMS CMK. ```ts import * as kms from '@aws-cdk/aws-kms'; -new LogGroup(this, 'LogGroup', { +new logs.LogGroup(this, 'LogGroup', { encryptionKey: new kms.Key(this, 'Key'), }); ``` @@ -83,13 +104,14 @@ Create a `SubscriptionFilter`, initialize it with an appropriate `Pattern` (see below) and supply the intended destination: ```ts -const fn = new lambda.Function(this, 'Lambda', { ... }); -const logGroup = new LogGroup(this, 'LogGroup', { ... }); +import * as destinations from '@aws-cdk/aws-logs-destinations'; +declare const fn: lambda.Function; +declare const logGroup: logs.LogGroup; -new SubscriptionFilter(this, 'Subscription', { - logGroup, - destination: new LogsDestinations.LambdaDestination(fn), - filterPattern: FilterPattern.allTerms("ERROR", "MainThread") +new logs.SubscriptionFilter(this, 'Subscription', { + logGroup, + destination: new destinations.LambdaDestination(fn), + filterPattern: logs.FilterPattern.allTerms("ERROR", "MainThread"), }); ``` @@ -114,6 +136,7 @@ A very simple MetricFilter can be created by using the `logGroup.extractMetric() helper function: ```ts +declare const logGroup: logs.LogGroup; logGroup.extractMetric('$.jsonField', 'Namespace', 'MetricName'); ``` @@ -127,11 +150,12 @@ You can expose a metric on a metric filter by calling the `MetricFilter.metric() This has a default of `statistic = 'avg'` if the statistic is not set in the `props`. ```ts -const mf = new MetricFilter(this, 'MetricFilter', { +declare const logGroup: logs.LogGroup; +const mf = new logs.MetricFilter(this, 'MetricFilter', { logGroup, metricNamespace: 'MyApp', metricName: 'Latency', - filterPattern: FilterPattern.exists('$.latency'), + filterPattern: logs.FilterPattern.exists('$.latency'), metricValue: '$.latency', }); @@ -139,7 +163,7 @@ const mf = new MetricFilter(this, 'MetricFilter', { const metric = mf.metric(); //you can use the metric to create a new alarm -new Alarm(this, 'alarm from metric filter', { +new cloudwatch.Alarm(this, 'alarm from metric filter', { metric, threshold: 100, evaluationPeriods: 2, @@ -175,23 +199,22 @@ line. (substrings) appear in the log event. * `FilterPattern.anyTerm(term, term, ...)`: matches if all of the given terms (substrings) appear in the log event. -* `FilterPattern.anyGroup([term, term, ...], [term, term, ...], ...)`: matches if +* `FilterPattern.anyTermGroup([term, term, ...], [term, term, ...], ...)`: matches if all of the terms in any of the groups (specified as arrays) matches. This is an OR match. - Examples: ```ts // Search for lines that contain both "ERROR" and "MainThread" -const pattern1 = FilterPattern.allTerms('ERROR', 'MainThread'); +const pattern1 = logs.FilterPattern.allTerms('ERROR', 'MainThread'); // Search for lines that either contain both "ERROR" and "MainThread", or // both "WARN" and "Deadlock". -const pattern2 = FilterPattern.anyGroup( - ['ERROR', 'MainThread'], - ['WARN', 'Deadlock'], - ); +const pattern2 = logs.FilterPattern.anyTermGroup( + ['ERROR', 'MainThread'], + ['WARN', 'Deadlock'], +); ``` ## JSON Patterns @@ -228,19 +251,19 @@ and then descending into it, such as `$.field` or `$.list[0].field`. given JSON patterns match. This makes an OR combination of the given patterns. - Example: ```ts // Search for all events where the component field is equal to // "HttpServer" and either error is true or the latency is higher // than 1000. -const pattern = FilterPattern.all( - FilterPattern.stringValue('$.component', '=', 'HttpServer'), - FilterPattern.any( - FilterPattern.booleanValue('$.error', true), - FilterPattern.numberValue('$.latency', '>', 1000) - )); +const pattern = logs.FilterPattern.all( + logs.FilterPattern.stringValue('$.component', '=', 'HttpServer'), + logs.FilterPattern.any( + logs.FilterPattern.booleanValue('$.error', true), + logs.FilterPattern.numberValue('$.latency', '>', 1000), + ), +); ``` ## Space-delimited table patterns @@ -274,9 +297,9 @@ Example: ```ts // Search for all events where the component is "HttpServer" and the // result code is not equal to 200. -const pattern = FilterPattern.spaceDelimited('time', 'component', '...', 'result_code', 'latency') - .whereString('component', '=', 'HttpServer') - .whereNumber('result_code', '!=', 200); +const pattern = logs.FilterPattern.spaceDelimited('time', 'component', '...', 'result_code', 'latency') + .whereString('component', '=', 'HttpServer') + .whereNumber('result_code', '!=', 200); ``` ## Notes diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 2d024ad8e88e1..7684ee84befd8 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -5,6 +5,10 @@ import { ILogGroup } from './log-group'; import { CfnDestination } from './logs.generated'; import { ILogSubscriptionDestination, LogSubscriptionDestinationConfig } from './subscription-filter'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { ArnFormat } from '@aws-cdk/core'; + // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; @@ -88,7 +92,7 @@ export class CrossAccountDestination extends cdk.Resource implements ILogSubscri service: 'logs', resource: 'destination', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.destinationName = this.getResourceNameAttribute(this.resource.ref); } diff --git a/packages/@aws-cdk/aws-logs/lib/index.ts b/packages/@aws-cdk/aws-logs/lib/index.ts index 5054715ffe52b..416a9c9a9b257 100644 --- a/packages/@aws-cdk/aws-logs/lib/index.ts +++ b/packages/@aws-cdk/aws-logs/lib/index.ts @@ -5,6 +5,7 @@ export * from './metric-filter'; export * from './pattern'; export * from './subscription-filter'; export * from './log-retention'; +export * from './policy'; // AWS::Logs CloudFormation Resources: export * from './logs.generated'; diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index 4c74dbf02f3ee..539c8ec035548 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -1,15 +1,16 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { IResource, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; import { MetricFilter } from './metric-filter'; import { FilterPattern, IFilterPattern } from './pattern'; +import { ResourcePolicy } from './policy'; import { ILogSubscriptionDestination, SubscriptionFilter } from './subscription-filter'; -export interface ILogGroup extends IResource { +export interface ILogGroup extends iam.IResourceWithPolicy { /** * The ARN of this log group, with ':*' appended * @@ -93,6 +94,9 @@ abstract class LogGroupBase extends Resource implements ILogGroup { */ public abstract readonly logGroupName: string; + + private policy?: ResourcePolicy; + /** * Create a new Log Stream for this Log Group * @@ -169,13 +173,13 @@ abstract class LogGroupBase extends Resource implements ILogGroup { * Give the indicated permissions on this log group and all streams */ public grant(grantee: iam.IGrantable, ...actions: string[]) { - return iam.Grant.addToPrincipal({ + return iam.Grant.addToPrincipalOrResource({ grantee, actions, // A LogGroup ARN out of CloudFormation already includes a ':*' at the end to include the log streams under the group. // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#w2ab1c21c10c63c43c11 resourceArns: [this.logGroupArn], - scope: this, + resource: this, }); } @@ -186,6 +190,19 @@ abstract class LogGroupBase extends Resource implements ILogGroup { public logGroupPhysicalName(): string { return this.physicalName; } + + /** + * Adds a statement to the resource policy associated with this log group. + * A resource policy will be automatically created upon the first call to `addToResourcePolicy`. + * @param statement The policy statement to add + */ + public addToResourcePolicy(statement: iam.PolicyStatement): iam.AddToResourcePolicyResult { + if (!this.policy) { + this.policy = new ResourcePolicy(this, 'Policy'); + } + this.policy.document.addStatements(statement); + return { statementAdded: true, policyDependable: this.policy }; + } } /** @@ -335,7 +352,7 @@ export class LogGroup extends LogGroupBase { class Import extends LogGroupBase { public readonly logGroupArn = `${baseLogGroupArn}:*`; - public readonly logGroupName = Stack.of(scope).parseArn(baseLogGroupArn, ':').resourceName!; + public readonly logGroupName = Stack.of(scope).splitArn(baseLogGroupArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!; } return new Import(scope, id); @@ -352,7 +369,7 @@ export class LogGroup extends LogGroupBase { public readonly logGroupArn = Stack.of(scope).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: baseLogGroupName + ':*', }); } @@ -395,7 +412,7 @@ export class LogGroup extends LogGroupBase { service: 'logs', resource: 'log-group', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.logGroupName = this.getResourceNameAttribute(resource.ref); } diff --git a/packages/@aws-cdk/aws-logs/lib/log-retention.ts b/packages/@aws-cdk/aws-logs/lib/log-retention.ts index f3054cf910f1c..5af8edaac96ed 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-retention.ts @@ -5,6 +5,10 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { RetentionDays } from './log-group'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { ArnFormat } from '@aws-cdk/core'; + // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; @@ -107,7 +111,7 @@ export class LogRetention extends CoreConstruct { service: 'logs', resource: 'log-group', resourceName: `${logGroupName}:*`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } @@ -128,9 +132,11 @@ export class LogRetention extends CoreConstruct { /** * Private provider Lambda function to support the log retention custom resource. */ -class LogRetentionFunction extends CoreConstruct { +class LogRetentionFunction extends CoreConstruct implements cdk.ITaggable { public readonly functionArn: cdk.Reference; + public readonly tags: cdk.TagManager = new cdk.TagManager(cdk.TagType.KEY_VALUE, 'AWS::Lambda::Function'); + constructor(scope: Construct, id: string, props: LogRetentionProps) { super(scope, id); @@ -164,10 +170,13 @@ class LogRetentionFunction extends CoreConstruct { S3Key: asset.s3ObjectKey, }, Role: role.roleArn, + Tags: this.tags.renderedTags, }, }); this.functionArn = resource.getAtt('Arn'); + asset.addResourceMetadata(resource, 'Code'); + // Function dependencies role.node.children.forEach((child) => { if (cdk.CfnResource.isCfnResource(child)) { diff --git a/packages/@aws-cdk/aws-logs/lib/policy.ts b/packages/@aws-cdk/aws-logs/lib/policy.ts new file mode 100644 index 0000000000000..de3af44f1ae2f --- /dev/null +++ b/packages/@aws-cdk/aws-logs/lib/policy.ts @@ -0,0 +1,51 @@ +import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; +import { Resource, Lazy, Names } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnResourcePolicy } from './logs.generated'; + +/** + * Properties to define Cloudwatch log group resource policy + */ +export interface ResourcePolicyProps { + /** + * Name of the log group resource policy + * @default - Uses a unique id based on the construct path + */ + readonly resourcePolicyName?: string; + + /** + * Initial statements to add to the resource policy + * + * @default - No statements + */ + readonly policyStatements?: PolicyStatement[]; +} + +/** + * Creates Cloudwatch log group resource policies + */ +export class ResourcePolicy extends Resource { + /** + * The IAM policy document for this resource policy. + */ + public readonly document = new PolicyDocument(); + + constructor(scope: Construct, id: string, props?: ResourcePolicyProps) { + super(scope, id, { + physicalName: props?.resourcePolicyName, + }); + + new CfnResourcePolicy(this, 'ResourcePolicy', { + policyName: Lazy.string({ + produce: () => props?.resourcePolicyName ?? Names.uniqueId(this), + }), + policyDocument: Lazy.string({ + produce: () => JSON.stringify(this.document), + }), + }); + + if (props?.policyStatements) { + this.document.addStatements(...props.policyStatements); + } + } +} diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 9e13692d15a14..a865bde5c1dae 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,13 +84,13 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", - "jest": "^26.6.3", - "nock": "^13.1.3", + "jest": "^27.3.1", + "nock": "^13.2.1", "sinon": "^9.2.4" }, "dependencies": { @@ -92,6 +99,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", @@ -101,6 +109,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "engines": { diff --git a/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6c34f60674f19 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as logs from '@aws-cdk/aws-logs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} + diff --git a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts index 8f8ed85bb4d47..af746f956e675 100644 --- a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts +++ b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts @@ -1,7 +1,9 @@ +import * as path from 'path'; import '@aws-cdk/assert-internal/jest'; -import { ABSENT } from '@aws-cdk/assert-internal'; +import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { LogRetention, RetentionDays } from '../lib'; /* eslint-disable quote-props */ @@ -156,4 +158,47 @@ describe('log retention', () => { expect(logGroupArn.endsWith(':*')).toEqual(true); }); + + test('retention Lambda CfnResource receives propagated tags', () => { + const stack = new cdk.Stack(); + cdk.Tags.of(stack).add('test-key', 'test-value'); + new LogRetention(stack, 'MyLambda', { + logGroupName: 'group', + retention: RetentionDays.ONE_MONTH, + }); + + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { + Tags: [ + { + Key: 'test-key', + Value: 'test-value', + }, + ], + }); + + }); + + test('asset metadata added to log retention construct lambda function', () => { + // GIVEN + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + + const assetLocation = path.join(__dirname, '../', '/lib', '/log-retention-provider'); + + // WHEN + new LogRetention(stack, 'MyLambda', { + logGroupName: 'group', + retention: RetentionDays.ONE_MONTH, + }); + + // Then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + 'aws:asset:path': assetLocation, + 'aws:asset:property': 'Code', + }, + }, ResourcePart.CompleteDefinition); + + }); }); diff --git a/packages/@aws-cdk/aws-logs/test/loggroup.test.ts b/packages/@aws-cdk/aws-logs/test/loggroup.test.ts index 72b3c390a2f96..d7fa5cb600b76 100644 --- a/packages/@aws-cdk/aws-logs/test/loggroup.test.ts +++ b/packages/@aws-cdk/aws-logs/test/loggroup.test.ts @@ -335,6 +335,57 @@ describe('log group', () => { }); + test('grant to service principal', () => { + // GIVEN + const stack = new Stack(); + const lg = new LogGroup(stack, 'LogGroup'); + const sp = new iam.ServicePrincipal('es.amazonaws.com'); + + // WHEN + lg.grantWrite(sp); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyDocument: { + 'Fn::Join': [ + '', + [ + '{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Effect":"Allow","Principal":{"Service":"es.amazonaws.com"},"Resource":"', + { + 'Fn::GetAtt': [ + 'LogGroupF5B46931', + 'Arn', + ], + }, + '"}],"Version":"2012-10-17"}', + ], + ], + }, + PolicyName: 'LogGroupPolicy643B329C', + }); + + }); + + + test('can add a policy to the log group', () => { + // GIVEN + const stack = new Stack(); + const lg = new LogGroup(stack, 'LogGroup'); + + // WHEN + lg.addToResourcePolicy(new iam.PolicyStatement({ + resources: ['*'], + actions: ['logs:PutLogEvents'], + principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/user-name')], + })); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyDocument: '{"Statement":[{"Action":"logs:PutLogEvents","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:user/user-name"},"Resource":"*"}],"Version":"2012-10-17"}', + PolicyName: 'LogGroupPolicy643B329C', + }); + }); + test('correctly returns physical name of the log group', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-logs/test/policy.test.ts b/packages/@aws-cdk/aws-logs/test/policy.test.ts new file mode 100644 index 0000000000000..4b2684a9957b1 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/policy.test.ts @@ -0,0 +1,52 @@ +import '@aws-cdk/assert-internal/jest'; +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; +import { LogGroup, ResourcePolicy } from '../lib'; + +describe('resource policy', () => { + test('ResourcePolicy is added to stack, when .addToResourcePolicy() is provided a valid Statement', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + logGroup.addToResourcePolicy(new PolicyStatement({ + actions: ['logs:CreateLogStream'], + resources: ['*'], + })); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyName: 'LogGroupPolicy643B329C', + PolicyDocument: JSON.stringify({ + Statement: [ + { + Action: 'logs:CreateLogStream', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }), + }); + }); + + test('ResourcePolicy is added to stack, when created manually/directly', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + const resourcePolicy = new ResourcePolicy(stack, 'ResourcePolicy'); + resourcePolicy.document.addStatements(new PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + principals: [new ServicePrincipal('es.amazonaws.com')], + resources: [logGroup.logGroupArn], + })); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyName: 'ResourcePolicy', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-lookoutequipment/package.json b/packages/@aws-cdk/aws-lookoutequipment/package.json index 583212fe12b16..a7f0b5af8190d 100644 --- a/packages/@aws-cdk/aws-lookoutequipment/package.json +++ b/packages/@aws-cdk/aws-lookoutequipment/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-lookoutmetrics/package.json b/packages/@aws-cdk/aws-lookoutmetrics/package.json index b1da867ff825e..32a7ad91634f7 100644 --- a/packages/@aws-cdk/aws-lookoutmetrics/package.json +++ b/packages/@aws-cdk/aws-lookoutmetrics/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-lookoutvision/package.json b/packages/@aws-cdk/aws-lookoutvision/package.json index 41f2c0c5d20c0..3b078e5fbc87d 100644 --- a/packages/@aws-cdk/aws-lookoutvision/package.json +++ b/packages/@aws-cdk/aws-lookoutvision/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-macie/package.json b/packages/@aws-cdk/aws-macie/package.json index bf3b4f9104a59..366b44cc1436d 100644 --- a/packages/@aws-cdk/aws-macie/package.json +++ b/packages/@aws-cdk/aws-macie/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-managedblockchain/package.json b/packages/@aws-cdk/aws-managedblockchain/package.json index 3994b29d5fa75..0d3ab22610d09 100644 --- a/packages/@aws-cdk/aws-managedblockchain/package.json +++ b/packages/@aws-cdk/aws-managedblockchain/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-mediaconnect/package.json b/packages/@aws-cdk/aws-mediaconnect/package.json index c329c2d368efc..5ea1c867f5c9c 100644 --- a/packages/@aws-cdk/aws-mediaconnect/package.json +++ b/packages/@aws-cdk/aws-mediaconnect/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-mediaconvert/package.json b/packages/@aws-cdk/aws-mediaconvert/package.json index 172cd0658252e..db46b10c78796 100644 --- a/packages/@aws-cdk/aws-mediaconvert/package.json +++ b/packages/@aws-cdk/aws-mediaconvert/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-medialive/package.json b/packages/@aws-cdk/aws-medialive/package.json index cdf5f7caac8fe..899730b583263 100644 --- a/packages/@aws-cdk/aws-medialive/package.json +++ b/packages/@aws-cdk/aws-medialive/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-mediapackage/package.json b/packages/@aws-cdk/aws-mediapackage/package.json index 817eafaaea6da..ea1fbb7346c47 100644 --- a/packages/@aws-cdk/aws-mediapackage/package.json +++ b/packages/@aws-cdk/aws-mediapackage/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-mediastore/package.json b/packages/@aws-cdk/aws-mediastore/package.json index 34e48b7593020..8123929e5cb85 100644 --- a/packages/@aws-cdk/aws-mediastore/package.json +++ b/packages/@aws-cdk/aws-mediastore/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-memorydb/package.json b/packages/@aws-cdk/aws-memorydb/package.json index 4ecfac53a2db6..375bc1a8f27c0 100644 --- a/packages/@aws-cdk/aws-memorydb/package.json +++ b/packages/@aws-cdk/aws-memorydb/package.json @@ -81,7 +81,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-msk/README.md b/packages/@aws-cdk/aws-msk/README.md index ce86c6a477c45..664ec4f66c973 100644 --- a/packages/@aws-cdk/aws-msk/README.md +++ b/packages/@aws-cdk/aws-msk/README.md @@ -29,7 +29,7 @@ The following example creates an MSK Cluster. import * as msk from '@aws-cdk/aws-msk'; const cluster = new Cluster(this, 'Cluster', { - kafkaVersion: msk.KafkaVersion.V2_6_1, + kafkaVersion: msk.KafkaVersion.V2_8_1, vpc, }); ``` diff --git a/packages/@aws-cdk/aws-msk/lib/cluster-version.ts b/packages/@aws-cdk/aws-msk/lib/cluster-version.ts index 0d3b511aae59e..b04c154b76054 100644 --- a/packages/@aws-cdk/aws-msk/lib/cluster-version.ts +++ b/packages/@aws-cdk/aws-msk/lib/cluster-version.ts @@ -47,6 +47,11 @@ export class KafkaVersion { */ public static readonly V2_8_0 = KafkaVersion.of('2.8.0'); + /** + * Kafka version 2.8.1 + */ + public static readonly V2_8_1 = KafkaVersion.of('2.8.1'); + /** * Custom cluster version * @param version custom version number diff --git a/packages/@aws-cdk/aws-msk/package.json b/packages/@aws-cdk/aws-msk/package.json index 7c5d79482230f..0a38efe4bd9c7 100644 --- a/packages/@aws-cdk/aws-msk/package.json +++ b/packages/@aws-cdk/aws-msk/package.json @@ -79,8 +79,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-acmpca": "0.0.0", diff --git a/packages/@aws-cdk/aws-msk/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-msk/test/integ.cluster.expected.json index 9a0d0db6e2325..2b523706bd3e2 100644 --- a/packages/@aws-cdk/aws-msk/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-msk/test/integ.cluster.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -399,7 +399,7 @@ } }, "ClusterName": "integ-test", - "KafkaVersion": "2.6.1", + "KafkaVersion": "2.8.1", "NumberOfBrokerNodes": 2, "EncryptionInfo": { "EncryptionInTransit": { @@ -524,7 +524,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4S3BucketB17E5ABD" + "Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3Bucket4C71F166" }, "S3Key": { "Fn::Join": [ @@ -537,7 +537,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4S3VersionKey77778F6A" + "Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4" } ] } @@ -550,7 +550,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4S3VersionKey77778F6A" + "Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4" } ] } @@ -576,17 +576,17 @@ } }, "Parameters": { - "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4S3BucketB17E5ABD": { + "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3Bucket4C71F166": { "Type": "String", - "Description": "S3 bucket for asset \"5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4\"" + "Description": "S3 bucket for asset \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\"" }, - "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4S3VersionKey77778F6A": { + "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4": { "Type": "String", - "Description": "S3 key for asset version \"5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4\"" + "Description": "S3 key for asset version \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\"" }, - "AssetParameters5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4ArtifactHash580E429C": { + "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dArtifactHash6350D824": { "Type": "String", - "Description": "Artifact hash for asset \"5c61041c12314e1ad8e67a0107fa3733382a206a78cdc1576fffa7e93caca5b4\"" + "Description": "Artifact hash for asset \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-msk/test/integ.cluster.ts b/packages/@aws-cdk/aws-msk/test/integ.cluster.ts index c05fa496d7210..06ad0893d20d7 100644 --- a/packages/@aws-cdk/aws-msk/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-msk/test/integ.cluster.ts @@ -10,7 +10,7 @@ const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); const cluster = new msk.Cluster(stack, 'Cluster', { clusterName: 'integ-test', - kafkaVersion: msk.KafkaVersion.V2_6_1, + kafkaVersion: msk.KafkaVersion.V2_8_1, vpc, removalPolicy: cdk.RemovalPolicy.DESTROY, }); diff --git a/packages/@aws-cdk/aws-mwaa/package.json b/packages/@aws-cdk/aws-mwaa/package.json index e7c300df1afa8..d683008ab9a71 100644 --- a/packages/@aws-cdk/aws-mwaa/package.json +++ b/packages/@aws-cdk/aws-mwaa/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index fd8bf57f76a8e..f56fd6e5ce9ba 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-networkfirewall/package.json b/packages/@aws-cdk/aws-networkfirewall/package.json index a0205fb192de7..0ee80543d4a43 100644 --- a/packages/@aws-cdk/aws-networkfirewall/package.json +++ b/packages/@aws-cdk/aws-networkfirewall/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-networkmanager/package.json b/packages/@aws-cdk/aws-networkmanager/package.json index 6891e55cb1b6e..76ed9c07d1319 100644 --- a/packages/@aws-cdk/aws-networkmanager/package.json +++ b/packages/@aws-cdk/aws-networkmanager/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-nimblestudio/package.json b/packages/@aws-cdk/aws-nimblestudio/package.json index c0291fea71a29..38e31a93dc9cd 100644 --- a/packages/@aws-cdk/aws-nimblestudio/package.json +++ b/packages/@aws-cdk/aws-nimblestudio/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-opensearchservice/README.md b/packages/@aws-cdk/aws-opensearchservice/README.md index d0ddd81e7c0d4..79d2289dcfffb 100644 --- a/packages/@aws-cdk/aws-opensearchservice/README.md +++ b/packages/@aws-cdk/aws-opensearchservice/README.md @@ -31,21 +31,17 @@ See [Migrating to OpenSearch](https://docs.aws.amazon.com/cdk/api/latest/docs/aw Create a development cluster by simply specifying the version: ```ts -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - const devDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, + version: opensearch.EngineVersion.OPENSEARCH_1_0, }); ``` To perform version upgrades without replacing the entire domain, specify the `enableVersionUpgrade` property. ```ts -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - const devDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enableVersionUpgrade: true // defaults to false + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enableVersionUpgrade: true, // defaults to false }); ``` @@ -53,22 +49,22 @@ Create a production grade cluster by also specifying things like capacity and az ```ts const prodDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - capacity: { - masterNodes: 5, - dataNodes: 20 - }, - ebs: { - volumeSize: 20 - }, - zoneAwareness: { - availabilityZoneCount: 3 - }, - logging: { - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + capacity: { + masterNodes: 5, + dataNodes: 20, + }, + ebs: { + volumeSize: 20, + }, + zoneAwareness: { + availabilityZoneCount: 3, + }, + logging: { + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -95,7 +91,7 @@ You can also create it using the CDK, **but note that only the first application ```ts const slr = new iam.CfnServiceLinkedRole(this, 'Service Linked Role', { - awsServiceName: 'es.amazonaws.com' + awsServiceName: 'es.amazonaws.com', }); ``` @@ -106,7 +102,7 @@ This method accepts a domain endpoint of an already existing domain: ```ts const domainEndpoint = 'https://my-domain-jcjotrt6f7otem4sqcwbch3c4u.us-east-1.es.amazonaws.com'; -const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); +const domain = opensearch.Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); ``` ## Permissions @@ -116,13 +112,14 @@ const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint) Helper methods also exist for managing access to the domain. ```ts -const lambda = new lambda.Function(this, 'Lambda', { /* ... */ }); +declare const fn: lambda.Function; +declare const domain: opensearch.Domain; // Grant write access to the app-search index -domain.grantIndexWrite('app-search', lambda); +domain.grantIndexWrite('app-search', fn); // Grant read access to the 'app-search/_search' path -domain.grantPathRead('app-search/_search', lambda); +domain.grantPathRead('app-search/_search', fn); ``` ## Encryption @@ -131,15 +128,15 @@ The domain can also be created with encryption enabled: ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + ebs: { + volumeSize: 100, + volumeType: ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, }); ``` @@ -179,6 +176,7 @@ which security groups will be attached to the domain. By default, CDK will selec Helper methods exist to access common domain metrics for example: ```ts +declare const domain: opensearch.Domain; const freeStorageSpace = domain.metricFreeStorageSpace(); const masterSysMemoryUtilization = domain.metric('MasterSysMemoryUtilization'); ``` @@ -192,15 +190,15 @@ be supplied or dynamically created if not supplied. ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, }); const masterUserPassword = domain.masterUserPassword; @@ -230,8 +228,8 @@ stored in the AWS Secrets Manager as secret. The secret has the prefix ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - useUnsignedBasicAuth: true, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + useUnsignedBasicAuth: true, }); const masterUserPassword = domain.masterUserPassword; @@ -245,21 +243,21 @@ Audit logs can be enabled for a domain, but only when fine grained access contro ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, - logging: { - auditLogEnabled: true, - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, + logging: { + auditLogEnabled: true, + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -269,12 +267,12 @@ UltraWarm nodes can be enabled to provide a cost-effective way to store large am ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - capacity: { - masterNodes: 2, - warmNodes: 2, - warmInstanceType: 'ultrawarm1.medium.search', - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + capacity: { + masterNodes: 2, + warmNodes: 2, + warmInstanceType: 'ultrawarm1.medium.search', + }, }); ``` @@ -283,11 +281,11 @@ const domain = new opensearch.Domain(this, 'Domain', { Custom endpoints can be configured to reach the domain under a custom domain name. ```ts -new Domain(stack, 'Domain', { - version: EngineVersion.OPENSEARCH_1_0, - customEndpoint: { - domainName: 'search.example.com', - }, +new opensearch.Domain(this, 'Domain', { + version: opensearch.EngineVersion.OPENSEARCH_1_0, + customEndpoint: { + domainName: 'search.example.com', + }, }); ``` @@ -300,12 +298,12 @@ Additionally, an automatic CNAME-Record is created if a hosted zone is provided [Advanced options](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createupdatedomains.html#createdomain-configure-advanced-options) can used to configure additional options. ```ts -new Domain(stack, 'Domain', { - version: EngineVersion.OPENSEARCH_1_0, - advancedOptions: { - 'rest.action.multi.allow_explicit_index': 'false', - 'indices.fielddata.cache.size': '25', - 'indices.query.bool.max_clause_count': '2048', - }, +new opensearch.Domain(this, 'Domain', { + version: opensearch.EngineVersion.OPENSEARCH_1_0, + advancedOptions: { + 'rest.action.multi.allow_explicit_index': 'false', + 'indices.fielddata.cache.size': '25', + 'indices.query.bool.max_clause_count': '2048', + }, }); ``` diff --git a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts index 1279d9525f831..3d5eebb6f32d6 100644 --- a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts +++ b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts @@ -881,7 +881,7 @@ abstract class DomainBase extends cdk.Resource implements IDomain { return new Metric({ namespace: 'AWS/ES', metricName, - dimensions: { + dimensionsMap: { DomainName: this.domainName, ClientId: this.stack.account, }, @@ -1141,7 +1141,8 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { */ public static fromDomainAttributes(scope: Construct, id: string, attrs: DomainAttributes): IDomain { const { domainArn, domainEndpoint } = attrs; - const domainName = cdk.Stack.of(scope).parseArn(domainArn).resourceName ?? extractNameFromEndpoint(domainEndpoint); + const domainName = cdk.Stack.of(scope).splitArn(domainArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName + ?? extractNameFromEndpoint(domainEndpoint); return new class extends DomainBase { public readonly domainArn = domainArn; @@ -1204,22 +1205,16 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { const defaultInstanceType = 'r5.large.search'; const warmDefaultInstanceType = 'ultrawarm1.medium.search'; - const dedicatedMasterType = - props.capacity?.masterNodeInstanceType?.toLowerCase() ?? - defaultInstanceType; + const dedicatedMasterType = initializeInstanceType(defaultInstanceType, props.capacity?.masterNodeInstanceType); const dedicatedMasterCount = props.capacity?.masterNodes ?? 0; - const dedicatedMasterEnabled = dedicatedMasterCount > 0; + const dedicatedMasterEnabled = cdk.Token.isUnresolved(dedicatedMasterCount) ? true : dedicatedMasterCount > 0; - const instanceType = - props.capacity?.dataNodeInstanceType?.toLowerCase() ?? - defaultInstanceType; + const instanceType = initializeInstanceType(defaultInstanceType, props.capacity?.dataNodeInstanceType); const instanceCount = props.capacity?.dataNodes ?? 1; - const warmType = - props.capacity?.warmInstanceType?.toLowerCase() ?? - warmDefaultInstanceType; + const warmType = initializeInstanceType(warmDefaultInstanceType, props.capacity?.warmInstanceType); const warmCount = props.capacity?.warmNodes ?? 0; - const warmEnabled = warmCount > 0; + const warmEnabled = cdk.Token.isUnresolved(warmCount) ? true : warmCount > 0; const availabilityZoneCount = props.zoneAwareness?.availabilityZoneCount ?? 2; @@ -1250,11 +1245,11 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { throw new Error('When providing vpc options you need to provide a subnet for each AZ you are using'); } - if ([dedicatedMasterType, instanceType, warmType].some(t => !t.endsWith('.search'))) { + if ([dedicatedMasterType, instanceType, warmType].some(t => (!cdk.Token.isUnresolved(t) && !t.endsWith('.search')))) { throw new Error('Master, data and UltraWarm node instance types must end with ".search".'); } - if (!warmType.startsWith('ultrawarm')) { + if (!cdk.Token.isUnresolved(warmType) && !warmType.startsWith('ultrawarm')) { throw new Error('UltraWarm node instance type must start with "ultrawarm".'); } @@ -1472,9 +1467,9 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { if (props.logging?.auditLogEnabled) { this.auditLogGroup = props.logging.auditLogGroup ?? - new logs.LogGroup(this, 'AuditLogs', { - retention: logs.RetentionDays.ONE_MONTH, - }); + new logs.LogGroup(this, 'AuditLogs', { + retention: logs.RetentionDays.ONE_MONTH, + }); logGroups.push(this.auditLogGroup); }; @@ -1624,7 +1619,21 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { if (logGroupResourcePolicy) { this.domain.node.addDependency(logGroupResourcePolicy); } - if (props.domainName) { this.node.addMetadata('aws:cdk:hasPhysicalName', props.domainName); } + if (props.domainName) { + if (!cdk.Token.isUnresolved(props.domainName)) { + // https://docs.aws.amazon.com/opensearch-service/latest/developerguide/configuration-api.html#configuration-api-datatypes-domainname + if (!props.domainName.match(/^[a-z0-9\-]+$/)) { + throw new Error(`Invalid domainName '${props.domainName}'. Valid characters are a-z (lowercase only), 0-9, and – (hyphen).`); + } + if (props.domainName.length < 3 || props.domainName.length > 28) { + throw new Error(`Invalid domainName '${props.domainName}'. It must be between 3 and 28 characters`); + } + if (props.domainName[0] < 'a' || props.domainName[0] > 'z') { + throw new Error(`Invalid domainName '${props.domainName}'. It must start with a lowercase letter`); + } + } + this.node.addMetadata('aws:cdk:hasPhysicalName', props.domainName); + } this.domainName = this.getResourceNameAttribute(this.domain.ref); @@ -1747,3 +1756,18 @@ function selectSubnets(vpc: ec2.IVpc, vpcSubnets: ec2.SubnetSelection[]): ec2.IS } return selected; } + +/** + * Initializes an instance type. + * + * @param defaultInstanceType Default instance type which is used if no instance type is provided + * @param instanceType Instance type + * @returns Instance type in lowercase (if provided) or default instance type + */ +function initializeInstanceType(defaultInstanceType: string, instanceType?: string): string { + if (instanceType) { + return cdk.Token.isUnresolved(instanceType) ? instanceType : instanceType.toLowerCase(); + } else { + return defaultInstanceType; + } +} diff --git a/packages/@aws-cdk/aws-opensearchservice/package.json b/packages/@aws-cdk/aws-opensearchservice/package.json index 523f58fe1caea..24c01f69d0dd7 100644 --- a/packages/@aws-cdk/aws-opensearchservice/package.json +++ b/packages/@aws-cdk/aws-opensearchservice/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.OpenSearchService", @@ -82,7 +89,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..625996f43fb30 --- /dev/null +++ b/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as opensearch from '@aws-cdk/aws-opensearchservice'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-opensearchservice/test/domain.test.ts b/packages/@aws-cdk/aws-opensearchservice/test/domain.test.ts index 4ad11ac372f97..5b190e2b71a5c 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/domain.test.ts +++ b/packages/@aws-cdk/aws-opensearchservice/test/domain.test.ts @@ -8,7 +8,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as route53 from '@aws-cdk/aws-route53'; -import { App, Stack, Duration, SecretValue, CfnParameter } from '@aws-cdk/core'; +import { App, Stack, Duration, SecretValue, CfnParameter, Token } from '@aws-cdk/core'; import { Domain, EngineVersion } from '../lib'; let app: App; @@ -248,6 +248,45 @@ describe('UltraWarm instances', () => { }); +test('can use tokens in capacity configuration', () => { + new Domain(stack, 'Domain', { + version: defaultVersion, + capacity: { + dataNodeInstanceType: Token.asString({ Ref: 'dataNodeInstanceType' }), + dataNodes: Token.asNumber({ Ref: 'dataNodes' }), + masterNodeInstanceType: Token.asString({ Ref: 'masterNodeInstanceType' }), + masterNodes: Token.asNumber({ Ref: 'masterNodes' }), + warmInstanceType: Token.asString({ Ref: 'warmInstanceType' }), + warmNodes: Token.asNumber({ Ref: 'warmNodes' }), + }, + }); + + expect(stack).toHaveResourceLike('AWS::OpenSearchService::Domain', { + ClusterConfig: { + InstanceCount: { + Ref: 'dataNodes', + }, + InstanceType: { + Ref: 'dataNodeInstanceType', + }, + DedicatedMasterEnabled: true, + DedicatedMasterCount: { + Ref: 'masterNodes', + }, + DedicatedMasterType: { + Ref: 'masterNodeInstanceType', + }, + WarmEnabled: true, + WarmCount: { + Ref: 'warmNodes', + }, + WarmType: { + Ref: 'warmInstanceType', + }, + }, + }); +}); + describe('log groups', () => { test('slowSearchLogEnabled should create a custom log group', () => { @@ -1319,6 +1358,21 @@ describe('custom error responses', () => { })).toThrow('Unknown Elasticsearch version: 5.4'); }); + test('error when invalid domain name is given', () => { + expect(() => new Domain(stack, 'Domain1', { + version: EngineVersion.OPENSEARCH_1_0, + domainName: 'InvalidName', + })).toThrow(/Valid characters are a-z/); + expect(() => new Domain(stack, 'Domain2', { + version: EngineVersion.OPENSEARCH_1_0, + domainName: 'a'.repeat(29), + })).toThrow(/It must be between 3 and 28 characters/); + expect(() => new Domain(stack, 'Domain3', { + version: EngineVersion.OPENSEARCH_1_0, + domainName: '123domain', + })).toThrow(/It must start with a lowercase letter/); + }); + test('error when error log publishing is enabled for Elasticsearch version < 5.1', () => { const error = /Error logs publishing requires Elasticsearch version 5.1 or later or OpenSearch version 1.0 or later/; expect(() => new Domain(stack, 'Domain1', { diff --git a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.custom-kms-key.expected.json b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.custom-kms-key.expected.json index 5a2791399497d..63a1cb0c236d5 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.custom-kms-key.expected.json +++ b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.custom-kms-key.expected.json @@ -232,7 +232,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3BucketF2AC65B6" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -245,7 +245,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -258,7 +258,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -285,17 +285,17 @@ } }, "Parameters": { - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3BucketF2AC65B6": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dArtifactHash30B93F3B": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.expected.json b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.expected.json index f317e3a7eb835..da98a9b24ab35 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.expected.json +++ b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.expected.json @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eS3BucketF2C16119" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eS3VersionKey60E022E2" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eS3VersionKey60E022E2" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -608,17 +608,17 @@ } }, "Parameters": { - "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eS3BucketF2C16119": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85e\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eS3VersionKey60E022E2": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85e\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85eArtifactHash4E1710B0": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"0036020ad5459434aa7b01d7769825c4b189b2a7722cc47c4a1bc6dbd9ccf85e\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.unsignedbasicauth.expected.json b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.unsignedbasicauth.expected.json index c2e8d28a0d35f..b1a9048f14dc1 100644 --- a/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.unsignedbasicauth.expected.json +++ b/packages/@aws-cdk/aws-opensearchservice/test/integ.opensearch.unsignedbasicauth.expected.json @@ -8,7 +8,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"admin\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "Domain66AC69E0": { "Type": "AWS::OpenSearchService::Domain", @@ -191,7 +193,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3BucketF2AC65B6" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E" }, "S3Key": { "Fn::Join": [ @@ -204,7 +206,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -217,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5" + "Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632" } ] } @@ -243,17 +245,17 @@ } }, "Parameters": { - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3BucketF2AC65B6": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": { "Type": "String", - "Description": "S3 bucket for asset \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dS3VersionKey8025D5E5": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": { "Type": "String", - "Description": "S3 key for asset version \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" }, - "AssetParameters5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7dArtifactHash30B93F3B": { + "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": { "Type": "String", - "Description": "Artifact hash for asset \"5d4cb1694db181d27240b1dabc7e4d79a7e1051022a41b773652782433c07f7d\"" + "Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\"" } } } \ 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 0933f51bad400..a1b33b0f27bb7 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-opsworkscm/package.json b/packages/@aws-cdk/aws-opsworkscm/package.json index 95389c6c13394..585aa90ea1aa8 100644 --- a/packages/@aws-cdk/aws-opsworkscm/package.json +++ b/packages/@aws-cdk/aws-opsworkscm/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-panorama/.eslintrc.js b/packages/@aws-cdk/aws-panorama/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-panorama/.gitignore b/packages/@aws-cdk/aws-panorama/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/.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-panorama/.npmignore b/packages/@aws-cdk/aws-panorama/.npmignore new file mode 100644 index 0000000000000..f931fede67c44 --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/.npmignore @@ -0,0 +1,29 @@ +# 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/ +!*.lit.ts diff --git a/packages/@aws-cdk/aws-panorama/LICENSE b/packages/@aws-cdk/aws-panorama/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/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-2021 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-panorama/NOTICE b/packages/@aws-cdk/aws-panorama/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-panorama/README.md b/packages/@aws-cdk/aws-panorama/README.md new file mode 100644 index 0000000000000..334264b4ea581 --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/README.md @@ -0,0 +1,20 @@ +# AWS::Panorama 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]) are always stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib + +--- + + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import aws-panorama = require('@aws-cdk/aws-panorama'); +``` diff --git a/packages/@aws-cdk/aws-panorama/jest.config.js b/packages/@aws-cdk/aws-panorama/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-panorama/lib/index.ts b/packages/@aws-cdk/aws-panorama/lib/index.ts new file mode 100644 index 0000000000000..1ef8b1378379a --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Panorama CloudFormation Resources: +export * from './panorama.generated'; diff --git a/packages/@aws-cdk/aws-panorama/package.json b/packages/@aws-cdk/aws-panorama/package.json new file mode 100644 index 0000000000000..22a1909e75848 --- /dev/null +++ b/packages/@aws-cdk/aws-panorama/package.json @@ -0,0 +1,105 @@ +{ + "name": "@aws-cdk/aws-panorama", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Panorama", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Panorama", + "packageId": "Amazon.CDK.AWS.Panorama", + "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.panorama", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "panorama" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-panorama", + "module": "aws_cdk.aws_panorama" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-panorama" + }, + "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": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "cloudformation": "AWS::Panorama", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Panorama", + "aws-panorama" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^27.0.2" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-iot/test/iot.test.ts b/packages/@aws-cdk/aws-panorama/test/aws-panorama.test.ts similarity index 100% rename from packages/@aws-cdk/aws-iot/test/iot.test.ts rename to packages/@aws-cdk/aws-panorama/test/aws-panorama.test.ts diff --git a/packages/@aws-cdk/aws-pinpoint/package.json b/packages/@aws-cdk/aws-pinpoint/package.json index e095a349312ac..22a56b42fa12c 100644 --- a/packages/@aws-cdk/aws-pinpoint/package.json +++ b/packages/@aws-cdk/aws-pinpoint/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-pinpointemail/package.json b/packages/@aws-cdk/aws-pinpointemail/package.json index f95e74754de4c..5b6ce5aa42d53 100644 --- a/packages/@aws-cdk/aws-pinpointemail/package.json +++ b/packages/@aws-cdk/aws-pinpointemail/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-qldb/package.json b/packages/@aws-cdk/aws-qldb/package.json index 519381dd6de63..17a656d77cf56 100644 --- a/packages/@aws-cdk/aws-qldb/package.json +++ b/packages/@aws-cdk/aws-qldb/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-quicksight/package.json b/packages/@aws-cdk/aws-quicksight/package.json index a611643089d80..95786e26228ea 100644 --- a/packages/@aws-cdk/aws-quicksight/package.json +++ b/packages/@aws-cdk/aws-quicksight/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-ram/package.json b/packages/@aws-cdk/aws-ram/package.json index cab6b01c59df3..233fbad9667e2 100644 --- a/packages/@aws-cdk/aws-ram/package.json +++ b/packages/@aws-cdk/aws-ram/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index cad785735dfb6..9f350d3dd1f5b 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -12,7 +12,7 @@ -```ts +```ts nofixture import * as rds from '@aws-cdk/aws-rds'; ``` @@ -23,6 +23,7 @@ always launch a database in a VPC. Use the `vpcSubnets` attribute to control whe your instances will be launched privately or publicly: ```ts +declare const vpc: ec2.Vpc; const cluster = new rds.DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_08_1 }), credentials: rds.Credentials.fromGeneratedSecret('clusteradmin'), // Optional - will default to 'admin' username and generated password @@ -52,7 +53,8 @@ Your cluster will be empty by default. To add a default database upon constructi Use `DatabaseClusterFromSnapshot` to create a cluster from a snapshot: ```ts -new rds.DatabaseClusterFromSnapshot(stack, 'Database', { +declare const vpc: ec2.Vpc; +new rds.DatabaseClusterFromSnapshot(this, 'Database', { engine: rds.DatabaseClusterEngine.aurora({ version: rds.AuroraEngineVersion.VER_1_22_2 }), instanceProps: { vpc, @@ -68,6 +70,7 @@ always launch a database in a VPC. Use the `vpcSubnets` attribute to control whe your instances will be launched privately or publicly: ```ts +declare const vpc: ec2.Vpc; const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.oracleSe2({ version: rds.OracleEngineVersion.VER_19_0_0_0_2020_04_R1 }), // optional, defaults to m5.large @@ -75,7 +78,7 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { credentials: rds.Credentials.fromGeneratedSecret('syscdk'), // Optional - will default to 'admin' username and generated password vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE + subnetType: ec2.SubnetType.PRIVATE, } }); ``` @@ -95,6 +98,7 @@ This is the upper limit to which RDS can automatically scale the storage. More i Example for max storage configuration: ```ts +declare const vpc: ec2.Vpc; const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), // optional, defaults to m5.large @@ -108,7 +112,8 @@ Use `DatabaseInstanceFromSnapshot` and `DatabaseInstanceReadReplica` to create a a source database respectively: ```ts -new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { +declare const vpc: ec2.Vpc; +new rds.DatabaseInstanceFromSnapshot(this, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }), // optional, defaults to m5.large @@ -116,7 +121,8 @@ new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { vpc, }); -new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { +declare const sourceInstance: rds.DatabaseInstance; +new rds.DatabaseInstanceReadReplica(this, 'ReadReplica', { sourceDatabaseInstance: sourceInstance, instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, @@ -136,8 +142,9 @@ The default value depends on `vpcSubnets`. It will be `true` if `vpcSubnets` is `subnetType: SubnetType.PUBLIC`, `false` otherwise. ```ts +declare const vpc: ec2.Vpc; // Setting public accessibility for DB instance -new rds.DatabaseInstance(stack, 'Instance', { +new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19, }), @@ -149,15 +156,14 @@ new rds.DatabaseInstance(stack, 'Instance', { }); // Setting public accessibility for DB cluster -new rds.DatabaseCluster(stack, 'DatabaseCluster', { - engine: DatabaseClusterEngine.AURORA, +new rds.DatabaseCluster(this, 'DatabaseCluster', { + engine: rds.DatabaseClusterEngine.AURORA, instanceProps: { vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE, }, publiclyAccessible: true, - copyTagsToSnapshot: true, // whether to save the cluster tags when creating the snapshot. Default is 'true' }, }); ``` @@ -168,6 +174,8 @@ To define Amazon CloudWatch event rules for database instances, use the `onEvent method: ```ts +declare const instance: rds.DatabaseInstance; +declare const fn: lambda.Function; const rule = instance.onEvent('InstanceEvent', { target: new targets.LambdaFunction(fn) }); ``` @@ -179,6 +187,7 @@ An alternative username (and password) may be specified for the admin user inste The following examples use a `DatabaseInstance`, but the same usage is applicable to `DatabaseCluster`. ```ts +declare const vpc: ec2.Vpc; const engine = rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }); new rds.DatabaseInstance(this, 'InstanceWithUsername', { engine, @@ -203,7 +212,9 @@ new rds.DatabaseInstance(this, 'InstanceWithSecretLogin', { Secrets generated by `fromGeneratedSecret()` can be customized: ```ts -const myKey = kms.Key(this, 'MyKey'); +declare const vpc: ec2.Vpc; +const engine = rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }); +const myKey = new kms.Key(this, 'MyKey'); new rds.DatabaseInstance(this, 'InstanceWithCustomizedSecret', { engine, @@ -211,7 +222,7 @@ new rds.DatabaseInstance(this, 'InstanceWithCustomizedSecret', { credentials: rds.Credentials.fromGeneratedSecret('postgres', { secretName: 'my-cool-name', encryptionKey: myKey, - excludeCharacters: ['!&*^#@()'], + excludeCharacters: '!&*^#@()', replicaRegions: [{ region: 'eu-west-1' }, { region: 'eu-west-2' }], }), }); @@ -223,19 +234,22 @@ To control who can access the cluster or instance, use the `.connections` attrib a default port, so you don't need to specify the port: ```ts -cluster.connections.allowFromAnyIpv4('Open to the world'); +declare const cluster: rds.DatabaseCluster; +cluster.connections.allowFromAnyIpv4(ec2.Port.allTraffic(), 'Open to the world'); ``` The endpoints to access your database cluster will be available as the `.clusterEndpoint` and `.readerEndpoint` attributes: ```ts +declare const cluster: rds.DatabaseCluster; const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT" ``` For an instance database: ```ts +declare const instance: rds.DatabaseInstance; const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" ``` @@ -244,6 +258,9 @@ const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: ```ts +import * as cdk from '@aws-cdk/core'; + +declare const instance: rds.DatabaseInstance; instance.addRotationSingleUser({ automaticallyAfter: cdk.Duration.days(7), // defaults to 30 days excludeCharacters: '!@#$%^&*', // defaults to the set " %+~`#$&*()|[]{}:;<>?!'/@\"\\" @@ -255,6 +272,8 @@ instance.addRotationSingleUser({ The multi user rotation scheme is also available: ```ts +declare const instance: rds.DatabaseInstance; +declare const myImportedSecret: rds.DatabaseSecret; instance.addRotationMultiUser('MyUser', { secret: myImportedSecret, // This secret must have the `masterarn` key }); @@ -263,6 +282,7 @@ instance.addRotationMultiUser('MyUser', { It's also possible to create user credentials together with the instance/cluster and add rotation: ```ts +declare const instance: rds.DatabaseInstance; const myUserSecret = new rds.DatabaseSecret(this, 'MyUserSecret', { username: 'myuser', secretName: 'my-user-secret', // optional, defaults to a CloudFormation-generated name @@ -279,6 +299,21 @@ instance.addRotationMultiUser('MyUser', { // Add rotation using the multi user s **Note**: This user must be created manually in the database using the master credentials. The rotation will start as soon as this user exists. +Access to the Secrets Manager API is required for the secret rotation. This can be achieved either with +internet connectivity (through NAT) or with a VPC interface endpoint. By default, the rotation Lambda function +is deployed in the same subnets as the instance/cluster. If access to the Secrets Manager API is not possible from +those subnets or using the default API endpoint, use the `vpcSubnets` and/or `endpoint` options: + +```ts +declare const instance: rds.DatabaseInstance; +declare const myEndpoint: ec2.InterfaceVpcEndpoint; + +instance.addRotationSingleUser({ + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, // Place rotation Lambda in private subnets + endpoint: myEndpoint, // Use VPC interface endpoint +}); +``` + See also [@aws-cdk/aws-secretsmanager](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-secretsmanager/README.md) for credentials rotation of existing clusters/instances. ## IAM Authentication @@ -290,30 +325,32 @@ and a list of supported versions and limitations. The following example shows enabling IAM authentication for a database instance and granting connection access to an IAM role. ```ts -const instance = new rds.DatabaseInstance(stack, 'Instance', { +declare const vpc: ec2.Vpc; +const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), vpc, iamAuthentication: true, // Optional - will be automatically set if you call grantConnect(). }); -const role = new Role(stack, 'DBRole', { assumedBy: new AccountPrincipal(stack.account) }); +const role = new iam.Role(this, 'DBRole', { assumedBy: new iam.AccountPrincipal(this.account) }); instance.grantConnect(role); // Grant the role connection access to the DB. ``` The following example shows granting connection access for RDS Proxy to an IAM role. ```ts -const cluster = new rds.DatabaseCluster(stack, 'Database', { +declare const vpc: ec2.Vpc; +const cluster = new rds.DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.AURORA, instanceProps: { vpc }, }); -const proxy = new rds.DatabaseProxy(stack, 'Proxy', { +const proxy = new rds.DatabaseProxy(this, 'Proxy', { proxyTarget: rds.ProxyTarget.fromCluster(cluster), secrets: [cluster.secret!], vpc, }); -const role = new Role(stack, 'DBProxyRole', { assumedBy: new AccountPrincipal(stack.account) }); +const role = new iam.Role(this, 'DBProxyRole', { assumedBy: new iam.AccountPrincipal(this.account) }); proxy.grantConnect(role, 'admin'); // Grant the role connection access to the DB Proxy for database user 'admin'. ``` @@ -330,13 +367,14 @@ The following example shows enabling domain support for a database instance and Directory Services. ```ts -const role = new iam.Role(stack, 'RDSDirectoryServicesRole', { +declare const vpc: ec2.Vpc; +const role = new iam.Role(this, 'RDSDirectoryServicesRole', { assumedBy: new iam.ServicePrincipal('rds.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonRDSDirectoryServiceAccess'), ], }); -const instance = new rds.DatabaseInstance(stack, 'Instance', { +const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), vpc, domain: 'd-????????', // The ID of the domain for the instance to join. @@ -356,13 +394,15 @@ Database instances and clusters both expose metrics (`cloudwatch.Metric`): ```ts // The number of database connections in use (average over 5 minutes) +declare const instance: rds.DatabaseInstance; const dbConnections = instance.metricDatabaseConnections(); // Average CPU utilization over 5 minutes +declare const cluster: rds.DatabaseCluster; const cpuUtilization = cluster.metricCPUUtilization(); // The average amount of time taken per disk I/O operation (average over 1 minute) -const readLatency = instance.metric('ReadLatency', { statistic: 'Average', periodSec: 60 }); +const readLatency = instance.metric('ReadLatency', { statistic: 'Average', period: Duration.seconds(60) }); ``` ## Enabling S3 integration @@ -388,10 +428,14 @@ The following snippet sets up a database cluster with different S3 buckets where ```ts import * as s3 from '@aws-cdk/aws-s3'; +declare const vpc: ec2.Vpc; const importBucket = new s3.Bucket(this, 'importbucket'); const exportBucket = new s3.Bucket(this, 'exportbucket'); new rds.DatabaseCluster(this, 'dbcluster', { - // ... + engine: rds.DatabaseClusterEngine.AURORA, + instanceProps: { + vpc, + }, s3ImportBuckets: [importBucket], s3ExportBuckets: [exportBucket], }); @@ -405,18 +449,13 @@ connections to the database and improve scalability of the application. Learn mo The following code configures an RDS Proxy for a `DatabaseInstance`. ```ts -import * as cdk from '@aws-cdk/core'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as rds from '@aws-cdk/aws-rds'; -import * as secrets from '@aws-cdk/aws-secretsmanager'; - -const vpc: ec2.IVpc = ...; -const securityGroup: ec2.ISecurityGroup = ...; -const secrets: secrets.ISecret[] = [...]; -const dbInstance: rds.IDatabaseInstance = ...; +declare const vpc: ec2.Vpc; +declare const securityGroup: ec2.SecurityGroup; +declare const secrets: secretsmanager.Secret[]; +declare const dbInstance: rds.DatabaseInstance; const proxy = dbInstance.addProxy('proxy', { - connectionBorrowTimeout: cdk.Duration.seconds(30), + borrowTimeout: Duration.seconds(30), maxConnectionsPercent: 50, secrets, vpc, @@ -430,12 +469,18 @@ store the data in highly durable storage, and manage the data with the CloudWatc instances and clusters; the types of logs available depend on the database type and engine being used. ```ts +import * as logs from '@aws-cdk/aws-logs'; +declare const myLogsPublishingRole: iam.Role; +declare const vpc: ec2.Vpc; + // Exporting logs from a cluster const cluster = new rds.DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.aurora({ version: rds.AuroraEngineVersion.VER_1_17_9, // different version class for each engine type + }), + instanceProps: { + vpc, }, - // ... cloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit'], // Export all available MySQL-based logs cloudwatchLogsRetention: logs.RetentionDays.THREE_MONTHS, // Optional - default is to never expire logs cloudwatchLogsRetentionRole: myLogsPublishingRole, // Optional - a role will be created if not provided @@ -447,7 +492,7 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3, }), - // ... + vpc, cloudwatchLogsExports: ['postgresql'], // Export the PostgreSQL logs // ... }); @@ -460,9 +505,10 @@ Amazon RDS uses option groups to enable and configure these features. An option that are available for a particular Amazon RDS DB instance. ```ts -const vpc: ec2.IVpc = ...; -const securityGroup: ec2.ISecurityGroup = ...; -new rds.OptionGroup(stack, 'Options', { +declare const vpc: ec2.Vpc; +declare const securityGroup: ec2.SecurityGroup; + +new rds.OptionGroup(this, 'Options', { engine: rds.DatabaseInstanceEngine.oracleSe2({ version: rds.OracleEngineVersion.VER_19, }), @@ -489,10 +535,7 @@ Aurora Serverless clusters can specify scaling properties which will be used to automatically scale the database cluster seamlessly based on the workload. ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as rds from '@aws-cdk/aws-rds'; - -const vpc = new ec2.Vpc(this, 'myrdsvpc'); +declare const vpc: ec2.Vpc; const cluster = new rds.ServerlessCluster(this, 'AnotherCluster', { engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL, @@ -532,11 +575,7 @@ You can access your Aurora Serverless DB cluster using the built-in Data API. Th The following example shows granting Data API access to a Lamba function. ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as rds from '@aws-cdk/aws-rds'; - -const vpc = new ec2.Vpc(this, 'MyVPC'); +declare const vpc: ec2.Vpc; const cluster = new rds.ServerlessCluster(this, 'AnotherCluster', { engine: rds.DatabaseClusterEngine.AURORA_MYSQL, @@ -544,16 +583,17 @@ const cluster = new rds.ServerlessCluster(this, 'AnotherCluster', { enableDataApi: true, // Optional - will be automatically set if you call grantDataApiAccess() }); +declare const code: lambda.Code; const fn = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', - code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + code, environment: { CLUSTER_ARN: cluster.clusterArn, - SECRET_ARN: cluster.secret.secretArn, + SECRET_ARN: cluster.secret!.secretArn, }, }); -cluster.grantDataApiAccess(fn) +cluster.grantDataApiAccess(fn); ``` **Note**: To invoke the Data API, the resource will need to read the secret associated with the cluster. diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index 606fddd75edbf..26a4743b7e598 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -336,6 +336,8 @@ export class AuroraMysqlEngineVersion { public static readonly VER_2_09_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.2'); /** Version "5.7.mysql_aurora.2.10.0". */ public static readonly VER_2_10_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.0'); + /** Version "5.7.mysql_aurora.2.10.1". */ + public static readonly VER_2_10_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.1'); /** * Create a new AuroraMysqlEngineVersion with an arbitrary version. @@ -451,6 +453,8 @@ export class AuroraPostgresEngineVersion { public static readonly VER_10_14 = AuroraPostgresEngineVersion.of('10.14', '10', { s3Import: true, s3Export: true }); /** Version "10.16". */ public static readonly VER_10_16 = AuroraPostgresEngineVersion.of('10.16', '10', { s3Import: true, s3Export: true }); + /** Version "10.18". */ + public static readonly VER_10_18 = AuroraPostgresEngineVersion.of('10.18', '10', { s3Import: true, s3Export: true }); /** Version "11.4". */ public static readonly VER_11_4 = AuroraPostgresEngineVersion.of('11.4', '11', { s3Import: true }); /** Version "11.6". */ @@ -463,12 +467,18 @@ export class AuroraPostgresEngineVersion { public static readonly VER_11_9 = AuroraPostgresEngineVersion.of('11.9', '11', { s3Import: true, s3Export: true }); /** Version "11.11". */ public static readonly VER_11_11 = AuroraPostgresEngineVersion.of('11.11', '11', { s3Import: true, s3Export: true }); + /** Version "11.13". */ + public static readonly VER_11_13 = AuroraPostgresEngineVersion.of('11.13', '11', { s3Import: true, s3Export: true }); /** Version "12.4". */ public static readonly VER_12_4 = AuroraPostgresEngineVersion.of('12.4', '12', { s3Import: true, s3Export: true }); /** Version "12.6". */ public static readonly VER_12_6 = AuroraPostgresEngineVersion.of('12.6', '12', { s3Import: true, s3Export: true }); + /** Version "12.8". */ + public static readonly VER_12_8 = AuroraPostgresEngineVersion.of('12.8', '12', { s3Import: true, s3Export: true }); /** Version "13.3". */ public static readonly VER_13_3 = AuroraPostgresEngineVersion.of('13.3', '13', { s3Import: true, s3Export: true }); + /** Version "13.4". */ + public static readonly VER_13_4 = AuroraPostgresEngineVersion.of('13.4', '13', { s3Import: true, s3Export: true }); /** * Create a new AuroraPostgresEngineVersion with an arbitrary version. diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index ad5a2201c759c..7324cacb333c2 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -40,6 +40,16 @@ interface DatabaseClusterBaseProps { */ readonly instanceProps: InstanceProps; + /** + * The number of seconds to set a cluster's target backtrack window to. + * This feature is only supported by the Aurora MySQL database engine and + * cannot be enabled on existing clusters. + * + * @see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Managing.Backtrack.html + * @default 0 seconds (no backtrack) + */ + readonly backtrackWindow?: Duration + /** * Backup settings * @@ -327,7 +337,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { }), ]; - let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props); + let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, /* combineRoles */ false); // bind the engine to the Cluster const clusterEngineBindConfig = props.engine.bindToCluster(this, { s3ImportRole, @@ -364,6 +374,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { deletionProtection: defaultDeletionProtection(props.deletionProtection, props.removalPolicy), enableIamDatabaseAuthentication: props.iamAuthentication, // Admin + backtrackWindow: props.backtrackWindow?.toSeconds(), backupRetentionPeriod: props.backup?.retention?.toDays(), preferredBackupWindow: props.backup?.preferredWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index 3ed27351c2196..05d045e56c8f3 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -497,6 +497,8 @@ export class MysqlEngineVersion { public static readonly VER_8_0_23 = MysqlEngineVersion.of('8.0.23', '8.0'); /** Version "8.0.25". */ public static readonly VER_8_0_25 = MysqlEngineVersion.of('8.0.25', '8.0'); + /** Version "8.0.26". */ + public static readonly VER_8_0_26 = MysqlEngineVersion.of('8.0.26', '8.0'); /** * Create a new MysqlEngineVersion with an arbitrary version. @@ -769,9 +771,14 @@ export class PostgresEngineVersion { public static readonly VER_9_6_21 = PostgresEngineVersion.of('9.6.21', '9.6'); /** * Version "9.6.22". - * @deprecated PostgreSQL 9.6 will reach end of life in November 2022 + * @deprecated PostgreSQL 9.6 will reach end of life in November 2021 */ public static readonly VER_9_6_22 = PostgresEngineVersion.of('9.6.22', '9.6'); + /** + * Version "9.6.23". + * @deprecated PostgreSQL 9.6 will reach end of life in November 2021 + */ + public static readonly VER_9_6_23 = PostgresEngineVersion.of('9.6.23', '9.6'); /** Version "10" (only a major version, without a specific minor version). */ public static readonly VER_10 = PostgresEngineVersion.of('10', '10'); @@ -805,6 +812,8 @@ export class PostgresEngineVersion { public static readonly VER_10_16 = PostgresEngineVersion.of('10.16', '10', { s3Import: true, s3Export: true }); /** Version "10.17". */ public static readonly VER_10_17 = PostgresEngineVersion.of('10.17', '10', { s3Import: true, s3Export: true }); + /** Version "10.18". */ + public static readonly VER_10_18 = PostgresEngineVersion.of('10.18', '10', { s3Import: true, s3Export: true }); /** Version "11" (only a major version, without a specific minor version). */ public static readonly VER_11 = PostgresEngineVersion.of('11', '11', { s3Import: true }); @@ -830,6 +839,8 @@ export class PostgresEngineVersion { public static readonly VER_11_11 = PostgresEngineVersion.of('11.11', '11', { s3Import: true, s3Export: true }); /** Version "11.12". */ public static readonly VER_11_12 = PostgresEngineVersion.of('11.12', '11', { s3Import: true, s3Export: true }); + /** Version "11.13". */ + public static readonly VER_11_13 = PostgresEngineVersion.of('11.13', '11', { s3Import: true, s3Export: true }); /** Version "12" (only a major version, without a specific minor version). */ public static readonly VER_12 = PostgresEngineVersion.of('12', '12', { s3Import: true }); @@ -845,6 +856,8 @@ export class PostgresEngineVersion { public static readonly VER_12_6 = PostgresEngineVersion.of('12.6', '12', { s3Import: true, s3Export: true }); /** Version "12.7". */ public static readonly VER_12_7 = PostgresEngineVersion.of('12.7', '12', { s3Import: true, s3Export: true }); + /** Version "12.8". */ + public static readonly VER_12_8 = PostgresEngineVersion.of('12.8', '12', { s3Import: true, s3Export: true }); /** Version "13" (only a major version, without a specific minor version). */ public static readonly VER_13 = PostgresEngineVersion.of('13', '13', { s3Import: true, s3Export: true }); @@ -854,6 +867,8 @@ export class PostgresEngineVersion { public static readonly VER_13_2 = PostgresEngineVersion.of('13.2', '13', { s3Import: true, s3Export: true }); /** Version "13.3". */ public static readonly VER_13_3 = PostgresEngineVersion.of('13.3', '13', { s3Import: true, s3Export: true }); + /** Version "13.4". */ + public static readonly VER_13_4 = PostgresEngineVersion.of('13.4', '13', { s3Import: true, s3Export: 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 8d285b7375006..f592e5f4528be 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,7 +5,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { ArnComponents, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '@aws-cdk/core'; +import { ArnComponents, ArnFormat, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { DatabaseSecret } from './database-secret'; @@ -190,7 +190,7 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase const commonAnComponents: ArnComponents = { service: 'rds', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }; const localArn = Stack.of(this).formatArn({ ...commonAnComponents, @@ -576,7 +576,6 @@ export interface DatabaseInstanceNewProps { /** * Role that will be associated with this DB instance to enable S3 export. - * This feature is only supported by the Microsoft SQL Server and Oracle engines. * * This property must not be used if `s3ExportBuckets` is used. * @@ -591,7 +590,6 @@ export interface DatabaseInstanceNewProps { /** * S3 buckets that you want to load data into. - * This feature is only supported by the Microsoft SQL Server and Oracle engines. * * This property must not be used if `s3ExportRole` is used. * @@ -847,7 +845,10 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa this.multiUserRotationApplication = props.engine.multiUserRotationApplication; this.engine = props.engine; - let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, true); + const engineType = props.engine.engineType; + // only Oracle and SQL Server require the import and export Roles to be the same + const combineRoles = engineType.startsWith('oracle-') || engineType.startsWith('sqlserver-'); + let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, combineRoles); const engineConfig = props.engine.bindToInstance(this, { ...props, s3ImportRole, @@ -866,8 +867,8 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa if (!engineFeatures?.s3Export) { throw new Error(`Engine '${engineDescription(props.engine)}' does not support S3 export`); } - // Only add the export role and feature if they are different from the import role & feature. - if (s3ImportRole !== s3ExportRole || engineFeatures.s3Import !== engineFeatures?.s3Export) { + // only add the export feature if it's different from the import feature + if (engineFeatures.s3Import !== engineFeatures?.s3Export) { instanceAssociatedRoles.push({ roleArn: s3ExportRole.roleArn, featureName: engineFeatures?.s3Export }); } } @@ -883,7 +884,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, - engine: props.engine.engineType, + engine: engineType, engineVersion: props.engine.engineVersion?.fullVersion, licenseModel: props.licenseModel, timezone: props.timezone, diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index c142a903bad7a..1664647c92bd8 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -31,14 +31,14 @@ export interface DatabaseS3ImportExportProps { * Validates the S3 import/export props and returns the import/export roles, if any. * If `combineRoles` is true, will reuse the import role for export (or vice versa) if possible. * - * Notably, `combineRoles` is (by default) set to true for instances, but false for clusters. + * Notably, `combineRoles` is set to true for instances, but false for clusters. * This is because the `combineRoles` functionality is most applicable to instances and didn't exist * for the initial clusters implementation. To maintain backwards compatibility, it is set to false for clusters. */ export function setupS3ImportExport( scope: Construct, props: DatabaseS3ImportExportProps, - combineRoles?: boolean): { s3ImportRole?: iam.IRole, s3ExportRole?: iam.IRole } { + combineRoles: boolean): { s3ImportRole?: iam.IRole, s3ExportRole?: iam.IRole } { let s3ImportRole = props.s3ImportRole; let s3ExportRole = props.s3ExportRole; diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index e9ed3c126cd65..75f8402b16d68 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -158,7 +158,6 @@ export interface CredentialsBaseOptions { /** * Options for creating Credentials from a username. - * @deprecated supporting API `fromUsername()` has been deprecated. See deprecation notice of the API. */ export interface CredentialsFromUsernameOptions extends CredentialsBaseOptions { /** @@ -202,10 +201,6 @@ export abstract class Credentials { /** * Creates Credentials for the given username, and optional password and key. * If no password is provided, one will be generated and stored in Secrets Manager. - * - * @deprecated use `fromGeneratedSecret()` or `fromPassword()` for new Clusters and Instances. - * Note that switching from `fromUsername()` to `fromGeneratedSecret()` or `fromPassword()` for already deployed - * Clusters or Instances will result in their replacement! */ public static fromUsername(username: string, options: CredentialsFromUsernameOptions = {}): Credentials { return { @@ -468,6 +463,25 @@ interface CommonRotationUserOptions { * @default " %+~`#$&*()|[]{}:;<>?!'/@\"\\" */ readonly excludeCharacters?: string; + + /** + * Where to place the rotation Lambda function + * + * @default - same placement as instance or cluster + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * The VPC interface endpoint to use for the Secrets Manager API + * + * If you enable private DNS hostnames for your VPC private endpoint (the default), you don't + * need to specify an endpoint. The standard Secrets Manager DNS hostname the Secrets Manager + * CLI and SDKs use by default (https://secretsmanager..amazonaws.com) automatically + * resolves to your VPC endpoint. + * + * @default https://secretsmanager..amazonaws.com + */ + readonly endpoint?: ec2.IInterfaceVpcEndpoint; } /** diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index fa50b891fca80..1fbb225b47086 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -346,12 +346,12 @@ abstract class DatabaseProxyBase extends cdk.Resource implements IDatabaseProxy throw new Error('For imported Database Proxies, the dbUser is required in grantConnect()'); } const scopeStack = cdk.Stack.of(this); - const proxyGeneratedId = scopeStack.parseArn(this.dbProxyArn, ':').resourceName; + const proxyGeneratedId = scopeStack.splitArn(this.dbProxyArn, cdk.ArnFormat.COLON_RESOURCE_NAME).resourceName; const userArn = scopeStack.formatArn({ service: 'rds-db', resource: 'dbuser', resourceName: `${proxyGeneratedId}/${dbUser}`, - sep: ':', + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, }); return iam.Grant.addToPrincipal({ grantee, diff --git a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts index 8375948441f4c..0bebd4d2505c9 100644 --- a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts @@ -2,7 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack, Lazy, FeatureFlags } from '@aws-cdk/core'; +import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack, Lazy, FeatureFlags, ArnFormat } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { IClusterEngine } from './cluster-engine'; @@ -314,7 +314,7 @@ abstract class ServerlessClusterBase extends Resource implements IServerlessClus return Stack.of(this).formatArn({ service: 'rds', resource: 'cluster', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.clusterIdentifier, }); } @@ -447,7 +447,7 @@ export class ServerlessCluster extends ServerlessClusterBase { engine: props.engine.engineType, engineVersion: props.engine.engineVersion?.fullVersion, engineMode: 'serverless', - enableHttpEndpoint: Lazy.anyValue({ produce: () => this.enableDataApi }), + enableHttpEndpoint: Lazy.any({ produce: () => this.enableDataApi }), kmsKeyId: props.storageEncryptionKey?.keyArn, masterUsername: credentials.username, masterUserPassword: credentials.password?.toString(), diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index e2add95207135..a9c66bfb3f081 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -80,8 +87,8 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-rds/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-rds/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..7e78fde1372a1 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/rosetta/default.ts-fixture @@ -0,0 +1,18 @@ +// Fixture with packages imported, but nothing else +import { Duration, SecretValue, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import ec2 = require('@aws-cdk/aws-ec2'); +import rds = require('@aws-cdk/aws-rds'); +import targets = require('@aws-cdk/aws-events-targets'); +import lambda = require('@aws-cdk/aws-lambda'); +import kms = require('@aws-cdk/aws-kms'); +import iam = require('@aws-cdk/aws-iam'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index 97d5548611fcd..b3dbdfc81d007 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -5,9 +5,9 @@ import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, CfnDBCluster, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup, DatabaseSecret, @@ -722,6 +722,197 @@ describe('cluster', () => { }); + test('addRotationSingleUser()', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + // WHEN + cluster.addRotationSingleUser(); + + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'DatabaseSecretAttachmentE5D1B020', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUser65F55654', + 'Outputs.RotationLambdaARN', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + }); + + test('addRotationMultiUser()', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + const userSecret = new DatabaseSecret(stack, 'UserSecret', { username: 'user' }); + cluster.addRotationMultiUser('user', { secret: userSecret.attach(cluster) }); + + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'UserSecretAttachment16ACBE6D', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'DatabaseuserECD1FB0C', + 'Outputs.RotationLambdaARN', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Parameters: { + masterSecretArn: { + Ref: 'DatabaseSecretAttachmentE5D1B020', + }, + }, + }); + }); + + test('addRotationSingleUser() with options', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpcWithIsolated = new ec2.Vpc(stack, 'Vpc', { + subnetConfiguration: [ + { name: 'public', subnetType: ec2.SubnetType.PUBLIC }, + { name: 'private', subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + { name: 'isolated', subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + ], + }); + + // WHEN + // DB in isolated subnet (no internet connectivity) + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc: vpcWithIsolated, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }, + }); + + // Rotation in private subnet (internet via NAT) + cluster.addRotationSingleUser({ + automaticallyAfter: cdk.Duration.days(15), + excludeCharacters: '°_@', + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + RotationRules: { + AutomaticallyAfterDays: 15, + }, + }); + + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': ['', [ + 'https://secretsmanager.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + functionName: 'DatabaseRotationSingleUser458A45BE', + vpcSubnetIds: { + 'Fn::Join': ['', [ + { Ref: 'VpcprivateSubnet1SubnetCEAD3716' }, + ',', + { Ref: 'VpcprivateSubnet2Subnet2DE7549C' }, + ]], + }, + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUserSecurityGroupAC6E0E73', + 'GroupId', + ], + }, + excludeCharacters: '°_@', + }, + }); + }); + + + test('addRotationSingleUser() with VPC interface endpoint', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpcIsolatedOnly = new ec2.Vpc(stack, 'Vpc', { natGateways: 0 }); + + const endpoint = new ec2.InterfaceVpcEndpoint(stack, 'Endpoint', { + service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, + vpc: vpcIsolatedOnly, + subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }); + + // DB in isolated subnet (no internet connectivity) + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc: vpcIsolatedOnly, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }, + }); + + // Rotation in isolated subnet with access to Secrets Manager API via endpoint + cluster.addRotationSingleUser({ endpoint }); + + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': ['', [ + 'https://', + { Ref: 'EndpointEEF1FD8F' }, + '.secretsmanager.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + functionName: 'DatabaseRotationSingleUser458A45BE', + vpcSubnetIds: { + 'Fn::Join': ['', [ + { Ref: 'VpcIsolatedSubnet1SubnetE48C5737' }, + ',', + { Ref: 'VpcIsolatedSubnet2Subnet16364B91' }, + ]], + }, + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUserSecurityGroupAC6E0E73', + 'GroupId', + ], + }, + excludeCharacters: " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + }, + }); + }); + test('throws when trying to add rotation to a cluster without secret', () => { // GIVEN const stack = new cdk.Stack(); @@ -2032,6 +2223,26 @@ describe('cluster', () => { CopyTagsToSnapshot: true, }); }); + + test('cluster has BacktrackWindow in seconds', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + instanceProps: { + vpc, + }, + backtrackWindow: cdk.Duration.days(1), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + BacktrackWindow: 24 * 60 * 60, + }); + }); }); test.each([ diff --git a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts index cb8057cc464f5..135704a893f75 100644 --- a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts +++ b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts @@ -10,7 +10,7 @@ describe('database secret manager', () => { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); - const existingSecret = secretsmanager.Secret.fromSecretName(stack, 'DBSecret', 'myDBLoginInfo'); + const existingSecret = secretsmanager.Secret.fromSecretNameV2(stack, 'DBSecret', 'myDBLoginInfo'); // WHEN new ServerlessCluster(stack, 'ServerlessDatabase', { @@ -29,8 +29,30 @@ describe('database secret manager', () => { Ref: 'ServerlessDatabaseSubnets5643CD76', }, EngineMode: 'serverless', - MasterUsername: '{{resolve:secretsmanager:myDBLoginInfo:SecretString:username::}}', - MasterUserPassword: '{{resolve:secretsmanager:myDBLoginInfo:SecretString:password::}}', + MasterUsername: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:arn:', + { + Ref: 'AWS::Partition', + }, + ':secretsmanager:us-test-1:12345:secret:myDBLoginInfo:SecretString:username::}}', + ], + ], + }, + MasterUserPassword: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:arn:', + { + Ref: 'AWS::Partition', + }, + ':secretsmanager:us-test-1:12345:secret:myDBLoginInfo:SecretString:password::}}', + ], + ], + }, StorageEncrypted: true, VpcSecurityGroupIds: [ { diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 7364599e4ed25..65f8ae8112d46 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -7,9 +7,9 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as rds from '../lib'; let stack: cdk.Stack; @@ -258,8 +258,8 @@ describe('instance', () => { }), credentials: rds.Credentials.fromUsername('syscdk'), vpc, - vpcPlacement: { - subnetType: ec2.SubnetType.PRIVATE, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); @@ -305,7 +305,7 @@ describe('instance', () => { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), vpc, - credentials: rds.SnapshotCredentials.fromGeneratedPassword('admin', { + credentials: rds.SnapshotCredentials.fromGeneratedSecret('admin', { excludeCharacters: '"@/\\', }), }); @@ -313,7 +313,11 @@ describe('instance', () => { expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { MasterUsername: ABSENT, MasterUserPassword: { - 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'InstanceSecret478E0A47' }, ':SecretString:password::}}']], + 'Fn::Join': ['', [ + '{{resolve:secretsmanager:', + { Ref: 'InstanceSecretB6DFA6BE8ee0a797cad8a68dbeb85f8698cdb5bb' }, + ':SecretString:password::}}', + ]], }, }); expect(stack).toHaveResource('AWS::SecretsManager::Secret', { @@ -738,6 +742,184 @@ describe('instance', () => { }); + test('addRotationSingleUser()', () => { + // GIVEN + const instance = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_10 }), + vpc, + }); + + // WHEN + instance.addRotationSingleUser(); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'DatabaseSecretAttachmentE5D1B020', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUser65F55654', + 'Outputs.RotationLambdaARN', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + }); + + test('addRotationMultiUser()', () => { + // GIVEN + const instance = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_10 }), + vpc, + }); + + // WHEN + const userSecret = new rds.DatabaseSecret(stack, 'UserSecret', { username: 'user' }); + instance.addRotationMultiUser('user', { secret: userSecret.attach(instance) }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'UserSecretAttachment16ACBE6D', + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'DatabaseuserECD1FB0C', + 'Outputs.RotationLambdaARN', + ], + }, + RotationRules: { + AutomaticallyAfterDays: 30, + }, + }); + + expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Parameters: { + masterSecretArn: { + Ref: 'DatabaseSecretAttachmentE5D1B020', + }, + }, + }); + }); + + test('addRotationSingleUser() with options', () => { + // GIVEN + const vpcWithIsolated = new ec2.Vpc(stack, 'Vpc', { + subnetConfiguration: [ + { name: 'public', subnetType: ec2.SubnetType.PUBLIC }, + { name: 'private', subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + { name: 'isolated', subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + ], + }); + + // WHEN + // DB in isolated subnet (no internet connectivity) + const instance = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_10 }), + vpc: vpcWithIsolated, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }); + + // Rotation in private subnet (internet via NAT) + instance.addRotationSingleUser({ + automaticallyAfter: cdk.Duration.days(15), + excludeCharacters: '°_@', + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + }); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + RotationRules: { + AutomaticallyAfterDays: 15, + }, + }); + + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': ['', [ + 'https://secretsmanager.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + functionName: 'DatabaseRotationSingleUser458A45BE', + vpcSubnetIds: { + 'Fn::Join': ['', [ + { Ref: 'VpcprivateSubnet1SubnetCEAD3716' }, + ',', + { Ref: 'VpcprivateSubnet2Subnet2DE7549C' }, + ]], + }, + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUserSecurityGroupAC6E0E73', + 'GroupId', + ], + }, + excludeCharacters: '°_@', + }, + }); + }); + + + test('addRotationSingleUser() with VPC interface endpoint', () => { + // GIVEN + const vpcIsolatedOnly = new ec2.Vpc(stack, 'Vpc', { natGateways: 0 }); + + const endpoint = new ec2.InterfaceVpcEndpoint(stack, 'Endpoint', { + service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, + vpc: vpcIsolatedOnly, + subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }); + + // WHEN + // DB in isolated subnet (no internet connectivity) + const instance = new rds.DatabaseInstance(stack, 'Database', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_10 }), + vpc: vpcIsolatedOnly, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, + }); + + // Rotation in isolated subnet with access to Secrets Manager API via endpoint + instance.addRotationSingleUser({ endpoint }); + + // THEN + expect(stack).toHaveResource('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': ['', [ + 'https://', + { Ref: 'EndpointEEF1FD8F' }, + '.secretsmanager.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + functionName: 'DatabaseRotationSingleUser458A45BE', + vpcSubnetIds: { + 'Fn::Join': ['', [ + { Ref: 'VpcIsolatedSubnet1SubnetE48C5737' }, + ',', + { Ref: 'VpcIsolatedSubnet2Subnet16364B91' }, + ]], + }, + vpcSecurityGroupIds: { + 'Fn::GetAtt': [ + 'DatabaseRotationSingleUserSecurityGroupAC6E0E73', + 'GroupId', + ], + }, + excludeCharacters: " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + }, + }); + }); + test('throws when trying to add rotation to an instance without secret', () => { const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SQL_SERVER_EE, diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json index 91b189023f587..19fbeabb515b1 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json @@ -599,7 +599,9 @@ "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"admin\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "DatabaseSecretAttachmentE5D1B020": { "Type": "AWS::SecretsManager::SecretTargetAttachment", diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-from-generated-password.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance-from-generated-password.expected.json index ca2ee08c781ad..219202e5fa587 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance-from-generated-password.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-from-generated-password.expected.json @@ -95,15 +95,15 @@ "VpcPublicSubnet1NATGateway4D7517AA": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet1EIPD7E02669", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet1Subnet5C2D37C4" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VpcPublicSubnet2NATGateway9182C01D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet2EIP3C605A87", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet2Subnet691E08A3" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "VpcPublicSubnet3NATGateway7640CD1D": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, "AllocationId": { "Fn::GetAtt": [ "VpcPublicSubnet3EIP3A666A23", "AllocationId" ] }, - "SubnetId": { - "Ref": "VpcPublicSubnet3SubnetBE12F0B6" - }, "Tags": [ { "Key": "Name", @@ -567,7 +567,9 @@ "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"admin\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "InstanceSecretAttachment83BEE581": { "Type": "AWS::SecretsManager::SecretTargetAttachment", diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json new file mode 100644 index 0000000000000..c623afaf92464 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json @@ -0,0 +1,603 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "ImportBucketBAF3A8E9": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ExportBucket4E99310E": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "InstanceSubnetGroupF2CBA54F": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for Instance database", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "InstanceSecurityGroupB4E5FA83": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for Instance database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "InstanceS3ImportRole30959D06": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "rds.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "InstanceS3ImportRoleDefaultPolicy297F292A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ImportBucketBAF3A8E9", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ImportBucketBAF3A8E9", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceS3ImportRoleDefaultPolicy297F292A", + "Roles": [ + { + "Ref": "InstanceS3ImportRole30959D06" + } + ] + } + }, + "InstanceS3ExportRole9891C2F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "rds.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "InstanceS3ExportRoleDefaultPolicy62C930BC": { + "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": [ + "ExportBucket4E99310E", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ExportBucket4E99310E", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceS3ExportRoleDefaultPolicy62C930BC", + "Roles": [ + { + "Ref": "InstanceS3ExportRole9891C2F7" + } + ] + } + }, + "InstanceSecret478E0A47": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "Description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "GenerateSecretString": { + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"postgres\"}" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "InstanceSecretAttachment83BEE581": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "InstanceSecret478E0A47" + }, + "TargetId": { + "Ref": "InstanceC1063A87" + }, + "TargetType": "AWS::RDS::DBInstance" + } + }, + "InstanceC1063A87": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.m5.large", + "AllocatedStorage": "100", + "AssociatedRoles": [ + { + "FeatureName": "s3Import", + "RoleArn": { + "Fn::GetAtt": [ + "InstanceS3ImportRole30959D06", + "Arn" + ] + } + }, + { + "FeatureName": "s3Export", + "RoleArn": { + "Fn::GetAtt": [ + "InstanceS3ExportRole9891C2F7", + "Arn" + ] + } + } + ], + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "InstanceSubnetGroupF2CBA54F" + }, + "EnableIAMDatabaseAuthentication": true, + "Engine": "postgres", + "EngineVersion": "11.12", + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:password::}}" + ] + ] + }, + "MultiAZ": false, + "PubliclyAccessible": true, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroupB4E5FA83", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts new file mode 100644 index 0000000000000..e07501039549b --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts @@ -0,0 +1,20 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as rds from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-rds-instance-s3-postgres-integ'); + +new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_11_12 }), + vpc: new ec2.Vpc(stack, 'VPC', { maxAzs: 2, natGateways: 1 }), + multiAz: false, + publiclyAccessible: true, + iamAuthentication: true, + s3ImportBuckets: [new s3.Bucket(stack, 'ImportBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY })], + s3ExportBuckets: [new s3.Bucket(stack, 'ExportBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY })], + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json index 2b144dedcc3aa..5b851784caef0 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.expected.json @@ -95,15 +95,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -535,7 +535,9 @@ "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"admin\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "DatabaseSecretAttachmentE5D1B020": { "Type": "AWS::SecretsManager::SecretTargetAttachment", @@ -570,7 +572,7 @@ "Ref": "DatabaseSubnetGroup7D60F180" }, "Engine": "sqlserver-se", - "EngineVersion": "14.00.3192.2.v1", + "EngineVersion": "14.00", "LicenseModel": "license-included", "MasterUsername": { "Fn::Join": [ diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts index 876ac2251e8fc..317e3c74abb57 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3.ts @@ -12,7 +12,7 @@ const importBucket = new s3.Bucket(stack, 'ImportBucket', { removalPolicy: cdk.R const exportBucket = new s3.Bucket(stack, 'ExportBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY }); new DatabaseInstance(stack, 'Database', { - engine: DatabaseInstanceEngine.sqlServerSe({ version: SqlServerEngineVersion.VER_14_00_3192_2_V1 }), + engine: DatabaseInstanceEngine.sqlServerSe({ version: SqlServerEngineVersion.VER_14 }), vpc, licenseModel: LicenseModel.LICENSE_INCLUDED, s3ImportBuckets: [importBucket], diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index 5214516968486..7b4328ccc3341 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -96,15 +96,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "Tags": [ { "Key": "Name", @@ -193,15 +193,15 @@ "VPCPublicSubnet2NATGateway3C070193": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet2EIP4947BC00", "AllocationId" ] }, - "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" - }, "Tags": [ { "Key": "Name", @@ -554,7 +554,9 @@ "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"syscdk\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "InstanceSecretAttachment83BEE581": { "Type": "AWS::SecretsManager::SecretTargetAttachment", @@ -1006,7 +1008,7 @@ "Runtime": "nodejs14.x", "Code": { "S3Bucket": { - "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3BucketAE1150B3" + "Ref": "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9S3BucketE7DA8D4B" }, "S3Key": { "Fn::Join": [ @@ -1019,7 +1021,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1" + "Ref": "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9S3VersionKey534293E7" } ] } @@ -1032,7 +1034,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1" + "Ref": "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9S3VersionKey534293E7" } ] } @@ -1118,27 +1120,13 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs10.x" + "Runtime": "nodejs12.x" }, "DependsOn": [ "FunctionServiceRole675BB04A" ] } }, - "Parameters": { - "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3BucketAE1150B3": { - "Type": "String", - "Description": "S3 bucket for asset \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" - }, - "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1": { - "Type": "String", - "Description": "S3 key for asset version \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" - }, - "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147ArtifactHash717FC602": { - "Type": "String", - "Description": "Artifact hash for asset \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" - } - }, "Mappings": { "InstanceRotationSingleUserSARMappingFEA0C86E": { "aws": { @@ -1150,5 +1138,19 @@ "semanticVersion": "1.1.37" } } + }, + "Parameters": { + "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9S3BucketE7DA8D4B": { + "Type": "String", + "Description": "S3 bucket for asset \"dd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9\"" + }, + "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9S3VersionKey534293E7": { + "Type": "String", + "Description": "S3 key for asset version \"dd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9\"" + }, + "AssetParametersdd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9ArtifactHash3CB520C3": { + "Type": "String", + "Description": "Artifact hash for asset \"dd4b26cf376ea5894e31041be239fc518713becdafb8f2894b069a53984fafe9\"" + } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index eafe7a2953735..3e869950a0a46 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -85,7 +85,7 @@ class DatabaseInstanceStack extends cdk.Stack { const fn = new lambda.Function(this, 'Function', { code: lambda.Code.fromInline('exports.handler = (event) => console.log(event);'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_12_X, }); const availabilityRule = instance.onEvent('Availability', { target: new targets.LambdaFunction(fn) }); diff --git a/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json b/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json index a18c5f5843512..8ec23ce0fe7db 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json @@ -95,15 +95,15 @@ "vpcPublicSubnet1NATGateway9C16659E": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, "AllocationId": { "Fn::GetAtt": [ "vpcPublicSubnet1EIPDA49DCBE", "AllocationId" ] }, - "SubnetId": { - "Ref": "vpcPublicSubnet1Subnet2E65531E" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "vpcPublicSubnet2NATGateway9B8AE11A": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, "AllocationId": { "Fn::GetAtt": [ "vpcPublicSubnet2EIP9B3743B1", "AllocationId" ] }, - "SubnetId": { - "Ref": "vpcPublicSubnet2Subnet009B674F" - }, "Tags": [ { "Key": "Name", @@ -436,7 +436,9 @@ "PasswordLength": 30, "SecretStringTemplate": "{\"username\":\"master\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "dbInstanceSecretAttachment88CFBDAE": { "Type": "AWS::SecretsManager::SecretTargetAttachment", diff --git a/packages/@aws-cdk/aws-rds/test/option-group.test.ts b/packages/@aws-cdk/aws-rds/test/option-group.test.ts index 77a394a1fa6a7..a0ab91ac0dfdf 100644 --- a/packages/@aws-cdk/aws-rds/test/option-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/option-group.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion, OracleLegacyEngineVersion } from '../lib'; +import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion } from '../lib'; describe('option group', () => { test('create an option group', () => { @@ -10,8 +10,8 @@ describe('option group', () => { // WHEN new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe1({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -22,9 +22,8 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se1', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se1 11.2', + EngineName: 'oracle-se2', + MajorEngineVersion: '12.1', OptionConfigurations: [ { OptionName: 'XMLDB', @@ -42,8 +41,8 @@ describe('option group', () => { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -57,9 +56,6 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', @@ -103,8 +99,8 @@ describe('option group', () => { // WHEN const securityGroup = new ec2.SecurityGroup(stack, 'CustomSecurityGroup', { vpc }); new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -118,9 +114,6 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', diff --git a/packages/@aws-cdk/aws-rds/test/proxy.test.ts b/packages/@aws-cdk/aws-rds/test/proxy.test.ts index 5e2a65f1f0352..9cd48e7686dd9 100644 --- a/packages/@aws-cdk/aws-rds/test/proxy.test.ts +++ b/packages/@aws-cdk/aws-rds/test/proxy.test.ts @@ -22,7 +22,9 @@ describe('proxy', () => { test('create a DB proxy from an instance', () => { // GIVEN const instance = new rds.DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.MYSQL, + engine: rds.DatabaseInstanceEngine.mysql({ + version: rds.MysqlEngineVersion.VER_5_7, + }), vpc, }); diff --git a/packages/@aws-cdk/aws-redshift/README.md b/packages/@aws-cdk/aws-redshift/README.md index 8ff734a6be255..7fd769670d808 100644 --- a/packages/@aws-cdk/aws-redshift/README.md +++ b/packages/@aws-cdk/aws-redshift/README.md @@ -167,6 +167,34 @@ new Table(this, 'Table', { }); ``` +The table can be configured to have distStyle attribute and a distKey column: + +```ts fixture=cluster +new Table(this, 'Table', { + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: true }, + { name: 'col2', dataType: 'float' }, + ], + cluster: cluster, + databaseName: 'databaseName', + distStyle: TableDistStyle.KEY, +}); +``` + +The table can also be configured to have sortStyle attribute and sortKey columns: + +```ts fixture=cluster +new Table(this, 'Table', { + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', sortKey: true }, + { name: 'col2', dataType: 'float', sortKey: true }, + ], + cluster: cluster, + databaseName: 'databaseName', + sortStyle: TableSortStyle.COMPOUND, +}); +``` + ### Granting Privileges You can give a user privileges to perform certain actions on a table by using the diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index a94bfa31b7fb5..a43b6f6993c72 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -483,7 +483,7 @@ export class Cluster extends ClusterBase { dbName: props.defaultDatabaseName || 'default_db', publiclyAccessible: props.publiclyAccessible || false, // Encryption - kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, + kmsKeyId: props.encryptionKey?.keyId, encrypted: props.encrypted ?? true, }); diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts index 9f2064d0e5e5a..d95a9f9d97395 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts @@ -1,7 +1,9 @@ /* eslint-disable-next-line import/no-unresolved */ import * as AWSLambda from 'aws-lambda'; import { TablePrivilege, UserTablePrivilegesHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement, makePhysicalId } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps } from './types'; +import { makePhysicalId } from './util'; export async function handler(props: UserTablePrivilegesHandlerProps & ClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { const username = props.username; diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts new file mode 100644 index 0000000000000..45bf6d9810b98 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts @@ -0,0 +1,34 @@ +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import * as RedshiftData from 'aws-sdk/clients/redshiftdata'; +import { ClusterProps } from './types'; + +const redshiftData = new RedshiftData(); + +export async function executeStatement(statement: string, clusterProps: ClusterProps): Promise { + const executeStatementProps = { + ClusterIdentifier: clusterProps.clusterName, + Database: clusterProps.databaseName, + SecretArn: clusterProps.adminUserArn, + Sql: statement, + }; + const executedStatement = await redshiftData.executeStatement(executeStatementProps).promise(); + if (!executedStatement.Id) { + throw new Error('Service error: Statement execution did not return a statement ID'); + } + await waitForStatementComplete(executedStatement.Id); +} + +const waitTimeout = 100; +async function waitForStatementComplete(statementId: string): Promise { + await new Promise((resolve: (value: void) => void) => { + setTimeout(() => resolve(), waitTimeout); + }); + const statement = await redshiftData.describeStatement({ Id: statementId }).promise(); + if (statement.Status !== 'FINISHED' && statement.Status !== 'FAILED' && statement.Status !== 'ABORTED') { + return waitForStatementComplete(statementId); + } else if (statement.Status === 'FINISHED') { + return; + } else { + throw new Error(`Statement status was ${statement.Status}: ${statement.Error}`); + } +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts index a2e2a4dc4bee9..0716477eb54fe 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts @@ -1,20 +1,21 @@ /* eslint-disable-next-line import/no-unresolved */ import * as AWSLambda from 'aws-lambda'; import { Column } from '../../table'; -import { TableHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps, TableAndClusterProps, TableSortStyle } from './types'; +import { areColumnsEqual, getDistKeyColumn, getSortKeyColumns } from './util'; -export async function handler(props: TableHandlerProps & ClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { +export async function handler(props: TableAndClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { const tableNamePrefix = props.tableName.prefix; - const tableNameSuffix = props.tableName.generateSuffix ? `${event.RequestId.substring(0, 8)}` : ''; + const tableNameSuffix = props.tableName.generateSuffix === 'true' ? `${event.RequestId.substring(0, 8)}` : ''; const tableColumns = props.tableColumns; - const clusterProps = props; + const tableAndClusterProps = props; if (event.RequestType === 'Create') { - const tableName = await createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + const tableName = await createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); return { PhysicalResourceId: tableName }; } else if (event.RequestType === 'Delete') { - await dropTable(event.PhysicalResourceId, clusterProps); + await dropTable(event.PhysicalResourceId, tableAndClusterProps); return; } else if (event.RequestType === 'Update') { const tableName = await updateTable( @@ -22,8 +23,8 @@ export async function handler(props: TableHandlerProps & ClusterProps, event: AW tableNamePrefix, tableNameSuffix, tableColumns, - clusterProps, - event.OldResourceProperties as TableHandlerProps & ClusterProps, + tableAndClusterProps, + event.OldResourceProperties as TableAndClusterProps, ); return { PhysicalResourceId: tableName }; } else { @@ -32,10 +33,33 @@ export async function handler(props: TableHandlerProps & ClusterProps, event: AW } } -async function createTable(tableNamePrefix: string, tableNameSuffix: string, tableColumns: Column[], clusterProps: ClusterProps): Promise { +async function createTable( + tableNamePrefix: string, + tableNameSuffix: string, + tableColumns: Column[], + tableAndClusterProps: TableAndClusterProps, +): Promise { const tableName = tableNamePrefix + tableNameSuffix; const tableColumnsString = tableColumns.map(column => `${column.name} ${column.dataType}`).join(); - await executeStatement(`CREATE TABLE ${tableName} (${tableColumnsString})`, clusterProps); + + let statement = `CREATE TABLE ${tableName} (${tableColumnsString})`; + + if (tableAndClusterProps.distStyle) { + statement += ` DISTSTYLE ${tableAndClusterProps.distStyle}`; + } + + const distKeyColumn = getDistKeyColumn(tableColumns); + if (distKeyColumn) { + statement += ` DISTKEY(${distKeyColumn.name})`; + } + + const sortKeyColumns = getSortKeyColumns(tableColumns); + if (sortKeyColumns.length > 0) { + const sortKeyColumnsString = getSortKeyColumnsString(sortKeyColumns); + statement += ` ${tableAndClusterProps.sortStyle} SORTKEY(${sortKeyColumnsString})`; + } + + await executeStatement(statement, tableAndClusterProps); return tableName; } @@ -48,28 +72,79 @@ async function updateTable( tableNamePrefix: string, tableNameSuffix: string, tableColumns: Column[], - clusterProps: ClusterProps, - oldResourceProperties: TableHandlerProps & ClusterProps, + tableAndClusterProps: TableAndClusterProps, + oldResourceProperties: TableAndClusterProps, ): Promise { + const alterationStatements: string[] = []; + const oldClusterProps = oldResourceProperties; - if (clusterProps.clusterName !== oldClusterProps.clusterName || clusterProps.databaseName !== oldClusterProps.databaseName) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + if (tableAndClusterProps.clusterName !== oldClusterProps.clusterName || tableAndClusterProps.databaseName !== oldClusterProps.databaseName) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } const oldTableNamePrefix = oldResourceProperties.tableName.prefix; if (tableNamePrefix !== oldTableNamePrefix) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } const oldTableColumns = oldResourceProperties.tableColumns; if (!oldTableColumns.every(oldColumn => tableColumns.some(column => column.name === oldColumn.name && column.dataType === oldColumn.dataType))) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } - const additions = tableColumns.filter(column => { + const columnAdditions = tableColumns.filter(column => { return !oldTableColumns.some(oldColumn => column.name === oldColumn.name && column.dataType === oldColumn.dataType); }).map(column => `ADD ${column.name} ${column.dataType}`); - await Promise.all(additions.map(addition => executeStatement(`ALTER TABLE ${tableName} ${addition}`, clusterProps))); + if (columnAdditions.length > 0) { + alterationStatements.push(...columnAdditions.map(addition => `ALTER TABLE ${tableName} ${addition}`)); + } + + const oldDistStyle = oldResourceProperties.distStyle; + if ((!oldDistStyle && tableAndClusterProps.distStyle) || + (oldDistStyle && !tableAndClusterProps.distStyle)) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + } else if (oldDistStyle !== tableAndClusterProps.distStyle) { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER DISTSTYLE ${tableAndClusterProps.distStyle}`); + } + + const oldDistKey = getDistKeyColumn(oldTableColumns)?.name; + const newDistKey = getDistKeyColumn(tableColumns)?.name; + if ((!oldDistKey && newDistKey ) || (oldDistKey && !newDistKey)) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + } else if (oldDistKey !== newDistKey) { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER DISTKEY ${newDistKey}`); + } + + const oldSortKeyColumns = getSortKeyColumns(oldTableColumns); + const newSortKeyColumns = getSortKeyColumns(tableColumns); + const oldSortStyle = oldResourceProperties.sortStyle; + const newSortStyle = tableAndClusterProps.sortStyle; + if ((oldSortStyle === newSortStyle && !areColumnsEqual(oldSortKeyColumns, newSortKeyColumns)) + || (oldSortStyle !== newSortStyle)) { + switch (newSortStyle) { + case TableSortStyle.INTERLEAVED: + // INTERLEAVED sort key addition requires replacement. + // https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + + case TableSortStyle.COMPOUND: { + const sortKeyColumnsString = getSortKeyColumnsString(newSortKeyColumns); + alterationStatements.push(`ALTER TABLE ${tableName} ALTER ${newSortStyle} SORTKEY(${sortKeyColumnsString})`); + break; + } + + case TableSortStyle.AUTO: { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER SORTKEY ${newSortStyle}`); + break; + } + } + } + + await Promise.all(alterationStatements.map(statement => executeStatement(statement, tableAndClusterProps))); return tableName; } + +function getSortKeyColumnsString(sortKeyColumns: Column[]) { + return sortKeyColumns.map(column => column.name).join(); +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts new file mode 100644 index 0000000000000..6d80398b7f41b --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts @@ -0,0 +1,26 @@ +import { DatabaseQueryHandlerProps, TableHandlerProps } from '../handler-props'; + +export type ClusterProps = Omit; +export type TableAndClusterProps = TableHandlerProps & ClusterProps; + +/** + * The sort style of a table. + * This has been duplicated here to exporting private types. + */ +export enum TableSortStyle { + /** + * Amazon Redshift assigns an optimal sort key based on the table data. + */ + AUTO = 'AUTO', + + /** + * Specifies that the data is sorted using a compound key made up of all of the listed columns, + * in the order they are listed. + */ + COMPOUND = 'COMPOUND', + + /** + * Specifies that the data is sorted using an interleaved sort key. + */ + INTERLEAVED = 'INTERLEAVED', +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts index 707af78714e43..ae19440230ae9 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts @@ -3,7 +3,9 @@ import * as AWSLambda from 'aws-lambda'; /* eslint-disable-next-line import/no-extraneous-dependencies */ import * as SecretsManager from 'aws-sdk/clients/secretsmanager'; import { UserHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement, makePhysicalId } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps } from './types'; +import { makePhysicalId } from './util'; const secretsManager = new SecretsManager(); diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts index d834cd474f986..c6fe3709bd136 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts @@ -1,40 +1,33 @@ -/* eslint-disable-next-line import/no-extraneous-dependencies */ -import * as RedshiftData from 'aws-sdk/clients/redshiftdata'; -import { DatabaseQueryHandlerProps } from '../handler-props'; +import { Column } from '../../table'; +import { ClusterProps } from './types'; -const redshiftData = new RedshiftData(); +export function makePhysicalId(resourceName: string, clusterProps: ClusterProps, requestId: string): string { + return `${clusterProps.clusterName}:${clusterProps.databaseName}:${resourceName}:${requestId}`; +} -export type ClusterProps = Omit; +export function getDistKeyColumn(columns: Column[]): Column | undefined { + // string comparison is required for custom resource since everything is passed as string + const distKeyColumns = columns.filter(column => column.distKey === true || (column.distKey as unknown as string) === 'true'); -export async function executeStatement(statement: string, clusterProps: ClusterProps): Promise { - const executeStatementProps = { - ClusterIdentifier: clusterProps.clusterName, - Database: clusterProps.databaseName, - SecretArn: clusterProps.adminUserArn, - Sql: statement, - }; - const executedStatement = await redshiftData.executeStatement(executeStatementProps).promise(); - if (!executedStatement.Id) { - throw new Error('Service error: Statement execution did not return a statement ID'); + if (distKeyColumns.length === 0) { + return undefined; + } else if (distKeyColumns.length > 1) { + throw new Error('Multiple dist key columns found'); } - await waitForStatementComplete(executedStatement.Id); + + return distKeyColumns[0]; } -const waitTimeout = 100; -async function waitForStatementComplete(statementId: string): Promise { - await new Promise((resolve: (value: void) => void) => { - setTimeout(() => resolve(), waitTimeout); - }); - const statement = await redshiftData.describeStatement({ Id: statementId }).promise(); - if (statement.Status !== 'FINISHED' && statement.Status !== 'FAILED' && statement.Status !== 'ABORTED') { - return waitForStatementComplete(statementId); - } else if (statement.Status === 'FINISHED') { - return; - } else { - throw new Error(`Statement status was ${statement.Status}: ${statement.Error}`); - } +export function getSortKeyColumns(columns: Column[]): Column[] { + // string comparison is required for custom resource since everything is passed as string + return columns.filter(column => column.sortKey === true || (column.sortKey as unknown as string) === 'true'); } -export function makePhysicalId(resourceName: string, clusterProps: ClusterProps, requestId: string): string { - return `${clusterProps.clusterName}:${clusterProps.databaseName}:${resourceName}:${requestId}`; +export function areColumnsEqual(columnsA: Column[], columnsB: Column[]): boolean { + if (columnsA.length !== columnsB.length) { + return false; + } + return columnsA.every(columnA => { + return columnsB.find(column => column.name === columnA.name && column.dataType === columnA.dataType); + }); } diff --git a/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts b/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts index b00cc667a2ced..97089078f00a2 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts @@ -1,4 +1,4 @@ -import { Column } from '../table'; +import { Column, TableDistStyle, TableSortStyle } from '../table'; export interface DatabaseQueryHandlerProps { readonly handler: string; @@ -15,9 +15,11 @@ export interface UserHandlerProps { export interface TableHandlerProps { readonly tableName: { readonly prefix: string; - readonly generateSuffix: boolean; + readonly generateSuffix: string; }; readonly tableColumns: Column[]; + readonly distStyle?: TableDistStyle; + readonly sortStyle: TableSortStyle; } export interface TablePrivilege { diff --git a/packages/@aws-cdk/aws-redshift/lib/table.ts b/packages/@aws-cdk/aws-redshift/lib/table.ts index 337abdedd00a1..c9bf7c4c46ac2 100644 --- a/packages/@aws-cdk/aws-redshift/lib/table.ts +++ b/packages/@aws-cdk/aws-redshift/lib/table.ts @@ -4,6 +4,7 @@ import { ICluster } from './cluster'; import { DatabaseOptions } from './database-options'; import { DatabaseQuery } from './private/database-query'; import { HandlerName } from './private/database-query-provider/handler-name'; +import { getDistKeyColumn, getSortKeyColumns } from './private/database-query-provider/util'; import { TableHandlerProps } from './private/handler-props'; import { IUser } from './user'; @@ -66,6 +67,20 @@ export interface Column { * The data type of the column. */ readonly dataType: string; + + /** + * Boolean value that indicates whether the column is to be configured as DISTKEY. + * + * @default - column is not DISTKEY + */ + readonly distKey?: boolean; + + /** + * Boolean value that indicates whether the column is to be configured as SORTKEY. + * + * @default - column is not a SORTKEY + */ + readonly sortKey?: boolean; } /** @@ -84,6 +99,20 @@ export interface TableProps extends DatabaseOptions { */ readonly tableColumns: Column[]; + /** + * The distribution style of the table. + * + * @default TableDistStyle.AUTO + */ + readonly distStyle?: TableDistStyle; + + /** + * The sort style of the table. + * + * @default TableSortStyle.AUTO if no sort key is specified, TableSortStyle.COMPOUND if a sort key is specified + */ + readonly sortStyle?: TableSortStyle; + /** * The policy to apply when this resource is removed from the application. * @@ -183,6 +212,14 @@ export class Table extends TableBase { constructor(scope: Construct, id: string, props: TableProps) { super(scope, id); + this.validateDistKeyColumns(props.tableColumns); + if (props.distStyle) { + this.validateDistStyle(props.distStyle, props.tableColumns); + } + if (props.sortStyle) { + this.validateSortStyle(props.sortStyle, props.tableColumns); + } + this.tableColumns = props.tableColumns; this.cluster = props.cluster; this.databaseName = props.databaseName; @@ -194,9 +231,11 @@ export class Table extends TableBase { properties: { tableName: { prefix: props.tableName ?? cdk.Names.uniqueId(this), - generateSuffix: !props.tableName, + generateSuffix: !props.tableName ? 'true' : 'false', }, tableColumns: this.tableColumns, + distStyle: props.distStyle, + sortStyle: props.sortStyle ?? this.getDefaultSortStyle(props.tableColumns), }, }); @@ -219,4 +258,83 @@ export class Table extends TableBase { public applyRemovalPolicy(policy: cdk.RemovalPolicy): void { this.resource.applyRemovalPolicy(policy); } + + private validateDistKeyColumns(columns: Column[]): void { + try { + getDistKeyColumn(columns); + } catch (err) { + throw new Error('Only one column can be configured as distKey.'); + } + } + + private validateDistStyle(distStyle: TableDistStyle, columns: Column[]): void { + const distKeyColumn = getDistKeyColumn(columns); + if (distKeyColumn && distStyle !== TableDistStyle.KEY) { + throw new Error(`Only 'TableDistStyle.KEY' can be configured when distKey is also configured. Found ${distStyle}`); + } + if (!distKeyColumn && distStyle === TableDistStyle.KEY) { + throw new Error('distStyle of "TableDistStyle.KEY" can only be configured when distKey is also configured.'); + } + } + + private validateSortStyle(sortStyle: TableSortStyle, columns: Column[]): void { + const sortKeyColumns = getSortKeyColumns(columns); + if (sortKeyColumns.length === 0 && sortStyle !== TableSortStyle.AUTO) { + throw new Error(`sortStyle of '${sortStyle}' can only be configured when sortKey is also configured.`); + } + if (sortKeyColumns.length > 0 && sortStyle === TableSortStyle.AUTO) { + throw new Error(`sortStyle of '${TableSortStyle.AUTO}' cannot be configured when sortKey is also configured.`); + } + } + + private getDefaultSortStyle(columns: Column[]): TableSortStyle { + const sortKeyColumns = getSortKeyColumns(columns); + return (sortKeyColumns.length === 0) ? TableSortStyle.AUTO : TableSortStyle.COMPOUND; + } +} + +/** + * The data distribution style of a table. + */ +export enum TableDistStyle { + /** + * Amazon Redshift assigns an optimal distribution style based on the table data + */ + AUTO = 'AUTO', + + /** + * The data in the table is spread evenly across the nodes in a cluster in a round-robin distribution. + */ + EVEN = 'EVEN', + + /** + * The data is distributed by the values in the DISTKEY column. + */ + KEY = 'KEY', + + /** + * A copy of the entire table is distributed to every node. + */ + ALL = 'ALL', +} + +/** + * The sort style of a table. + */ +export enum TableSortStyle { + /** + * Amazon Redshift assigns an optimal sort key based on the table data. + */ + AUTO = 'AUTO', + + /** + * Specifies that the data is sorted using a compound key made up of all of the listed columns, + * in the order they are listed. + */ + COMPOUND = 'COMPOUND', + + /** + * Specifies that the data is sorted using an interleaved sort key. + */ + INTERLEAVED = 'INTERLEAVED', } diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index d6ed1ff2f96ca..67c6a888bfa71 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -32,7 +32,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": true + "strict": false } } } @@ -84,9 +84,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture b/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture index 82d98ca3e381e..4c7ab6ccdb771 100644 --- a/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture +++ b/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture @@ -1,7 +1,7 @@ // Fixture with cluster already created import { Construct, SecretValue, Stack } from '@aws-cdk/core'; import { Vpc } from '@aws-cdk/aws-ec2'; -import { Cluster, Table, TableAction, User } from '@aws-cdk/aws-redshift'; +import { Cluster, Table, TableAction, TableDistStyle, TableSortStyle, User } from '@aws-cdk/aws-redshift'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts index a1091815a30c1..d771e8e66b0de 100644 --- a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts @@ -249,10 +249,7 @@ test('create an encrypted cluster with custom KMS key', () => { // THEN Template.fromStack(stack).hasResourceProperties('AWS::Redshift::Cluster', { KmsKeyId: { - 'Fn::GetAtt': [ - 'Key961B73FD', - 'Arn', - ], + Ref: 'Key961B73FD', }, }); }); diff --git a/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts b/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts index 956efca1ab81f..7c5534d59a785 100644 --- a/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts @@ -1,18 +1,30 @@ /* eslint-disable-next-line import/no-unresolved */ import type * as AWSLambda from 'aws-lambda'; +const mockExecuteStatement = jest.fn(() => ({ promise: jest.fn(() => ({ Id: 'statementId' })) })); +jest.mock('aws-sdk/clients/redshiftdata', () => class { + executeStatement = mockExecuteStatement; + describeStatement = () => ({ promise: jest.fn(() => ({ Status: 'FINISHED' })) }); +}); +import { Column, TableDistStyle, TableSortStyle } from '../../lib'; +import { handler as manageTable } from '../../lib/private/database-query-provider/table'; +import { TableAndClusterProps } from '../../lib/private/database-query-provider/types'; + +type ResourcePropertiesType = TableAndClusterProps & { ServiceToken: string }; + const tableNamePrefix = 'tableNamePrefix'; const tableColumns = [{ name: 'col1', dataType: 'varchar(1)' }]; const clusterName = 'clusterName'; const adminUserArn = 'adminUserArn'; const databaseName = 'databaseName'; const physicalResourceId = 'PhysicalResourceId'; -const resourceProperties = { +const resourceProperties: ResourcePropertiesType = { tableName: { prefix: tableNamePrefix, - generateSuffix: true, + generateSuffix: 'true', }, tableColumns, + sortStyle: TableSortStyle.AUTO, clusterName, adminUserArn, databaseName, @@ -30,13 +42,6 @@ const genericEvent: AWSLambda.CloudFormationCustomResourceEventCommon = { ResourceType: '', }; -const mockExecuteStatement = jest.fn(() => ({ promise: jest.fn(() => ({ Id: 'statementId' })) })); -jest.mock('aws-sdk/clients/redshiftdata', () => class { - executeStatement = mockExecuteStatement; - describeStatement = () => ({ promise: jest.fn(() => ({ Status: 'FINISHED' })) }); -}); -import { handler as manageTable } from '../../lib/private/database-query-provider/table'; - beforeEach(() => { jest.clearAllMocks(); }); @@ -64,7 +69,7 @@ describe('create', () => { ...resourceProperties, tableName: { ...resourceProperties.tableName, - generateSuffix: false, + generateSuffix: 'false', }, }; @@ -75,6 +80,60 @@ describe('create', () => { Sql: `CREATE TABLE ${tableNamePrefix} (col1 varchar(1))`, })); }); + + test('serializes distKey and distStyle in statement', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + distStyle: TableDistStyle.KEY, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTSTYLE KEY DISTKEY(col1)`, + })); + }); + + test('serializes sortKeys and sortStyle in statement', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)', sortKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + { name: 'col3', dataType: 'varchar(1)', sortKey: true }, + ], + sortStyle: TableSortStyle.COMPOUND, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1),col3 varchar(1)) COMPOUND SORTKEY(col1,col3)`, + })); + }); + + test('serializes distKey and sortKeys as string booleans', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: 'true' as unknown as boolean }, + { name: 'col2', dataType: 'float', sortKey: 'true' as unknown as boolean }, + { name: 'col3', dataType: 'float', sortKey: 'true' as unknown as boolean }, + ], + distStyle: TableDistStyle.KEY, + sortStyle: TableSortStyle.COMPOUND, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(4),col2 float,col3 float) DISTSTYLE KEY DISTKEY(col1) COMPOUND SORTKEY(col2,col3)`, + })); + }); }); describe('delete', () => { @@ -199,4 +258,251 @@ describe('update', () => { Sql: `ALTER TABLE ${physicalResourceId} ADD ${newTableColumnName} ${newTableColumnDataType}`, })); }); + + describe('distStyle and distKey', () => { + test('replaces if distStyle is added', async () => { + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + distStyle: TableDistStyle.EVEN, + }; + + await expect(manageTable(newResourceProperties, event)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTSTYLE EVEN`, + })); + }); + + test('replaces if distStyle is removed', async () => { + const newEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + distStyle: TableDistStyle.EVEN, + }, + }; + const newResourceProperties = { + ...resourceProperties, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1))`, + })); + }); + + test('does not replace if distStyle is changed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + distStyle: TableDistStyle.EVEN, + }, + }; + const newDistStyle = TableDistStyle.ALL; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + distStyle: newDistStyle, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER DISTSTYLE ${newDistStyle}`, + })); + }); + + test('replaces if distKey is added', async () => { + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + }; + + await expect(manageTable(newResourceProperties, event)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTKEY(col1)`, + })); + }); + + test('replaces if distKey is removed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1))`, + })); + }); + + test('does not replace if distKey is changed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)', distKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + ], + }, + }; + const newDistKey = 'col2'; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)' }, + { name: 'col2', dataType: 'varchar(1)', distKey: true }, + ], + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER DISTKEY ${newDistKey}`, + })); + }); + }); + + describe('sortStyle and sortKeys', () => { + const oldTableColumnsWithSortKeys: Column[] = [ + { name: 'col1', dataType: 'varchar(1)', sortKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + ]; + const newTableColumnsWithSortKeys: Column[] = [ + { name: 'col1', dataType: 'varchar(1)' }, + { name: 'col2', dataType: 'varchar(1)', sortKey: true }, + ]; + + test('replaces when same sortStyle, different sortKey columns: INTERLEAVED', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: newTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1)) INTERLEAVED SORTKEY(col2)`, + })); + }); + + test('replaces when differnt sortStyle: INTERLEAVED', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1)) INTERLEAVED SORTKEY(col1)`, + })); + }); + + test('does not replace when same sortStyle, different sortKey columns: COMPOUND', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: newTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER COMPOUND SORTKEY(col2)`, + })); + }); + + test('does not replace when differnt sortStyle: COMPOUND', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER COMPOUND SORTKEY(col1)`, + })); + }); + + test('does not replace when differnt sortStyle: AUTO', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER SORTKEY AUTO`, + })); + }); + }); + }); diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json index 16d60bb08c0d0..6e909192a7f3d 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json @@ -580,6 +580,41 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "customkmskey377C6F9A": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "ClusterSubnetsDCFA5CB7": { "Type": "AWS::Redshift::ClusterSubnetGroup", "Properties": { @@ -680,6 +715,9 @@ "Ref": "ClusterSubnetsDCFA5CB7" }, "Encrypted": true, + "KmsKeyId": { + "Ref": "customkmskey377C6F9A" + }, "NumberOfNodes": 2, "PubliclyAccessible": true, "VpcSecurityGroupIds": [ @@ -1129,7 +1167,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3Bucket3B967306" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3Bucket0B347C2E" }, "S3Key": { "Fn::Join": [ @@ -1142,7 +1180,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479" } ] } @@ -1155,7 +1193,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479" } ] } @@ -1331,35 +1369,44 @@ "databaseName": "my_db", "tableName": { "prefix": "awscdkredshiftclusterdatabaseTable24923533", - "generateSuffix": true + "generateSuffix": "true" }, "tableColumns": [ { "name": "col1", - "dataType": "varchar(4)" + "dataType": "varchar(4)", + "distKey": true }, { "name": "col2", - "dataType": "float" + "dataType": "float", + "sortKey": true + }, + { + "name": "col3", + "dataType": "float", + "sortKey": true } - ] + ], + "distStyle": "KEY", + "sortStyle": "INTERLEAVED" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } }, "Parameters": { - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3Bucket3B967306": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3Bucket0B347C2E": { "Type": "String", - "Description": "S3 bucket for asset \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "S3 bucket for asset \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479": { "Type": "String", - "Description": "S3 key for asset version \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "S3 key for asset version \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fArtifactHash0EE8CD3D": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066ArtifactHash78689978": { "Type": "String", - "Description": "Artifact hash for asset \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "Artifact hash for asset \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { "Type": "String", @@ -1374,4 +1421,4 @@ "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.ts b/packages/@aws-cdk/aws-redshift/test/integ.database.ts index 3a3b955a2b5aa..d5079b83f0c1b 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.ts +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node /// !cdk-integ pragma:ignore-assets import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as redshift from '../lib'; @@ -28,6 +29,7 @@ const cluster = new redshift.Cluster(stack, 'Cluster', { }, defaultDatabaseName: databaseName, publiclyAccessible: true, + encryptionKey: new kms.Key(stack, 'custom-kms-key'), }); const databaseOptions = { @@ -37,7 +39,13 @@ const databaseOptions = { const user = new redshift.User(stack, 'User', databaseOptions); const table = new redshift.Table(stack, 'Table', { ...databaseOptions, - tableColumns: [{ name: 'col1', dataType: 'varchar(4)' }, { name: 'col2', dataType: 'float' }], + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: true }, + { name: 'col2', dataType: 'float', sortKey: true }, + { name: 'col3', dataType: 'float', sortKey: true }, + ], + distStyle: redshift.TableDistStyle.KEY, + sortStyle: redshift.TableSortStyle.INTERLEAVED, }); table.grant(user, redshift.TableAction.INSERT, redshift.TableAction.DELETE); diff --git a/packages/@aws-cdk/aws-redshift/test/table.test.ts b/packages/@aws-cdk/aws-redshift/test/table.test.ts index 97f66b57042f5..571a87fff5227 100644 --- a/packages/@aws-cdk/aws-redshift/test/table.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/table.test.ts @@ -5,7 +5,10 @@ import * as redshift from '../lib'; describe('cluster table', () => { const tableName = 'tableName'; - const tableColumns = [{ name: 'col1', dataType: 'varchar(4)' }, { name: 'col2', dataType: 'float' }]; + const tableColumns: redshift.Column[] = [ + { name: 'col1', dataType: 'varchar(4)' }, + { name: 'col2', dataType: 'float' }, + ]; let stack: cdk.Stack; let vpc: ec2.Vpc; @@ -40,7 +43,7 @@ describe('cluster table', () => { Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { tableName: { prefix: 'Table', - generateSuffix: true, + generateSuffix: 'true', }, tableColumns, }); @@ -67,7 +70,7 @@ describe('cluster table', () => { Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { tableName: { prefix: tableName, - generateSuffix: false, + generateSuffix: 'false', }, }); }); @@ -135,4 +138,110 @@ describe('cluster table', () => { DeletionPolicy: 'Delete', }); }); + + describe('distKey and distStyle', () => { + it('throws if more than one distKeys are configured', () => { + const updatedTableColumns: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', distKey: true }, + { name: 'col4', dataType: 'float', distKey: true }, + ]; + + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: updatedTableColumns, + }), + ).toThrow(/Only one column can be configured as distKey./); + }); + + it('throws if distStyle other than KEY is configured with configured distKey column', () => { + const updatedTableColumns: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', distKey: true }, + ]; + + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: updatedTableColumns, + distStyle: redshift.TableDistStyle.EVEN, + }), + ).toThrow(`Only 'TableDistStyle.KEY' can be configured when distKey is also configured. Found ${redshift.TableDistStyle.EVEN}`); + }); + + it('throws if KEY distStyle is configired with no distKey column', () => { + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns, + distStyle: redshift.TableDistStyle.KEY, + }), + ).toThrow('distStyle of "TableDistStyle.KEY" can only be configured when distKey is also configured.'); + }); + }); + + describe('sortKeys and sortStyle', () => { + it('configures default sortStyle based on sortKeys if no sortStyle is passed: AUTO', () => { + // GIVEN + const tableColumnsWithoutSortKey = tableColumns; + + // WHEN + new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithoutSortKey, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { + sortStyle: redshift.TableSortStyle.AUTO, + }); + }); + + it('configures default sortStyle based on sortKeys if no sortStyle is passed: COMPOUND', () => { + // GIVEN + const tableColumnsWithSortKey: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', sortKey: true }, + ]; + + // WHEN + new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithSortKey, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { + sortStyle: redshift.TableSortStyle.COMPOUND, + }); + }); + + it('throws if sortStlye other than AUTO is passed with no configured sortKeys', () => { + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns, + sortStyle: redshift.TableSortStyle.COMPOUND, + }), + ).toThrow(`sortStyle of '${redshift.TableSortStyle.COMPOUND}' can only be configured when sortKey is also configured.`); + }); + + it('throws if sortStlye of AUTO is passed with some configured sortKeys', () => { + // GIVEN + const tableColumnsWithSortKey: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', sortKey: true }, + ]; + + // THEN + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithSortKey, + sortStyle: redshift.TableSortStyle.AUTO, + }), + ).toThrow(`sortStyle of '${redshift.TableSortStyle.AUTO}' cannot be configured when sortKey is also configured.`); + }); + }); }); diff --git a/packages/@aws-cdk/aws-rekognition/.eslintrc.js b/packages/@aws-cdk/aws-rekognition/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-rekognition/.gitignore b/packages/@aws-cdk/aws-rekognition/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/.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-rekognition/.npmignore b/packages/@aws-cdk/aws-rekognition/.npmignore new file mode 100644 index 0000000000000..f931fede67c44 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/.npmignore @@ -0,0 +1,29 @@ +# 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/ +!*.lit.ts diff --git a/packages/@aws-cdk/aws-rekognition/LICENSE b/packages/@aws-cdk/aws-rekognition/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/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-2021 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-rekognition/NOTICE b/packages/@aws-cdk/aws-rekognition/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-rekognition/README.md b/packages/@aws-cdk/aws-rekognition/README.md new file mode 100644 index 0000000000000..9e158afddf5de --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/README.md @@ -0,0 +1,20 @@ +# AWS::Rekognition 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]) are always stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib + +--- + + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import aws-rekognition = require('@aws-cdk/aws-rekognition'); +``` diff --git a/packages/@aws-cdk/aws-rekognition/jest.config.js b/packages/@aws-cdk/aws-rekognition/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-rekognition/lib/index.ts b/packages/@aws-cdk/aws-rekognition/lib/index.ts new file mode 100644 index 0000000000000..a0fee8d99d632 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Rekognition CloudFormation Resources: +export * from './rekognition.generated'; diff --git a/packages/@aws-cdk/aws-rekognition/package.json b/packages/@aws-cdk/aws-rekognition/package.json new file mode 100644 index 0000000000000..af21be79fbf82 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/package.json @@ -0,0 +1,105 @@ +{ + "name": "@aws-cdk/aws-rekognition", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Rekognition", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Rekognition", + "packageId": "Amazon.CDK.AWS.Rekognition", + "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.rekognition", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "rekognition" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-rekognition", + "module": "aws_cdk.aws_rekognition" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-rekognition" + }, + "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": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "cloudformation": "AWS::Rekognition", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Rekognition", + "aws-rekognition" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^27.0.2" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-rekognition/test/aws-rekognition.test.ts b/packages/@aws-cdk/aws-rekognition/test/aws-rekognition.test.ts new file mode 100644 index 0000000000000..465c7bdea0693 --- /dev/null +++ b/packages/@aws-cdk/aws-rekognition/test/aws-rekognition.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assertions'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-resourcegroups/package.json b/packages/@aws-cdk/aws-resourcegroups/package.json index 2f9b4f2ee4163..b36c4174ef01e 100644 --- a/packages/@aws-cdk/aws-resourcegroups/package.json +++ b/packages/@aws-cdk/aws-resourcegroups/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-robomaker/package.json b/packages/@aws-cdk/aws-robomaker/package.json index 000b195520ffd..5c8ad4dae7731 100644 --- a/packages/@aws-cdk/aws-robomaker/package.json +++ b/packages/@aws-cdk/aws-robomaker/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53-patterns/README.md b/packages/@aws-cdk/aws-route53-patterns/README.md index 4fa852aaad777..a8c9c6b3874e0 100644 --- a/packages/@aws-cdk/aws-route53-patterns/README.md +++ b/packages/@aws-cdk/aws-route53-patterns/README.md @@ -41,13 +41,13 @@ must be in US East (N. Virginia). The following example creates an HTTPS redirect from `foo.example.com` to `bar.example.com` As an existing certificate is not provided, one will be created in `us-east-1` by the CDK. - ```ts - new HttpsRedirect(stack, 'Redirect', { - recordNames: ['foo.example.com'], - targetDomain: 'bar.example.com', - zone: HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'ID', - zoneName: 'example.com', - }) - }); - ``` +```ts +new patterns.HttpsRedirect(this, 'Redirect', { + recordNames: ['foo.example.com'], + targetDomain: 'bar.example.com', + zone: route53.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', { + hostedZoneId: 'ID', + zoneName: 'example.com', + }), +}); +``` diff --git a/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts b/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts index db63b7327a75d..06e53777cd277 100644 --- a/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts +++ b/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts @@ -4,7 +4,7 @@ import { CloudFrontWebDistribution, OriginProtocolPolicy, PriceClass, ViewerCert import { ARecord, AaaaRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { CloudFrontTarget } from '@aws-cdk/aws-route53-targets'; import { BlockPublicAccess, Bucket, RedirectProtocol } from '@aws-cdk/aws-s3'; -import { RemovalPolicy, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -62,7 +62,7 @@ export class HttpsRedirect extends CoreConstruct { const domainNames = props.recordNames ?? [props.zone.zoneName]; if (props.certificate) { - const certificateRegion = Stack.of(this).parseArn(props.certificate.certificateArn).region; + const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region; if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { throw new Error(`The certificate must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`); } diff --git a/packages/@aws-cdk/aws-route53-patterns/package.json b/packages/@aws-cdk/aws-route53-patterns/package.json index 02e94d1139a5d..d8c12460322a6 100644 --- a/packages/@aws-cdk/aws-route53-patterns/package.json +++ b/packages/@aws-cdk/aws-route53-patterns/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -70,8 +77,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53-patterns/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-route53-patterns/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..1ec9038bfff0b --- /dev/null +++ b/packages/@aws-cdk/aws-route53-patterns/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as patterns from '@aws-cdk/aws-route53-patterns'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index 07be5b2e363d0..73269abb6d6ed 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -14,9 +14,14 @@ This library contains Route53 Alias Record targets for: * API Gateway custom domains ```ts + import * as apigw from '@aws-cdk/aws-apigateway'; + + declare const zone: route53.HostedZone; + declare const restApi: apigw.LambdaRestApi; + new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.ApiGateway(restApi)), + target: route53.RecordTarget.fromAlias(new targets.ApiGateway(restApi)), // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), }); ``` @@ -24,38 +29,57 @@ This library contains Route53 Alias Record targets for: * API Gateway V2 custom domains ```ts + import * as apigwv2 from '@aws-cdk/aws-apigatewayv2'; + + declare const zone: route53.HostedZone; + declare const domainName: apigwv2.DomainName; new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.ApiGatewayv2DomainProperties(domainName.regionalDomainName, domainName.regionalHostedZoneId)), + target: route53.RecordTarget.fromAlias(new targets.ApiGatewayv2DomainProperties(domainName.regionalDomainName, domainName.regionalHostedZoneId)), }); ``` * CloudFront distributions ```ts + import * as cloudfront from '@aws-cdk/aws-cloudfront'; + + declare const zone: route53.HostedZone; + declare const distribution: cloudfront.CloudFrontWebDistribution; + new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.CloudFrontTarget(distribution)), + target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)), }); ``` * ELBv2 load balancers ```ts + import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; + + declare const zone: route53.HostedZone; + declare const lb: elbv2.ApplicationLoadBalancer; + new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.LoadBalancerTarget(elbv2)), - // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), + target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(lb)), + // or - route53.RecordTarget.fromAlias(new targets.ApiGatewayDomain(domainName)), }); ``` * Classic load balancers ```ts + import * as elb from '@aws-cdk/aws-elasticloadbalancing'; + + declare const zone: route53.HostedZone; + declare const lb: elb.LoadBalancer; + new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.ClassicLoadBalancerTarget(elb)), + target: route53.RecordTarget.fromAlias(new targets.ClassicLoadBalancerTarget(lb)), // or - route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(domainName)), }); ``` @@ -67,7 +91,12 @@ For example, if the Amazon-provided DNS for the load balancer is `ALB-xxxxxxx.us * GlobalAccelerator ```ts - new route53.ARecord(stack, 'AliasRecord', { + import * as globalaccelerator from '@aws-cdk/aws-globalaccelerator'; + + declare const zone: route53.HostedZone; + declare const accelerator: globalaccelerator.Accelerator; + + new route53.ARecord(this, 'AliasRecord', { zone, target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget(accelerator)), // or - route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorDomainTarget('xyz.awsglobalaccelerator.com')), @@ -82,9 +111,14 @@ See [the documentation on DNS addressing](https://docs.aws.amazon.com/global-acc **Important:** Based on the CFN docs for VPCEndpoints - [see here](attrDnsEntries) - the attributes returned for DnsEntries in CloudFormation is a combination of the hosted zone ID and the DNS name. The entries are ordered as follows: regional public DNS, zonal public DNS, private DNS, and wildcard DNS. This order is not enforced for AWS Marketplace services, and therefore this CDK construct is ONLY guaranteed to work with non-marketplace services. ```ts - new route53.ARecord(stack, "AliasRecord", { + import * as ec2 from '@aws-cdk/aws-ec2'; + + declare const zone: route53.HostedZone; + declare const interfaceVpcEndpoint: ec2.InterfaceVpcEndpoint; + + new route53.ARecord(this, "AliasRecord", { zone, - target: route53.RecordTarget.fromAlias(new alias.InterfaceVpcEndpointTarget(interfaceVpcEndpoint)) + target: route53.RecordTarget.fromAlias(new targets.InterfaceVpcEndpointTarget(interfaceVpcEndpoint)), }); ``` @@ -94,39 +128,62 @@ See [the documentation on DNS addressing](https://docs.aws.amazon.com/global-acc See [the Developer Guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/getting-started.html) for more info. ```ts - const [recordName, domainName] = ['www', 'example.com']; + import * as s3 from '@aws-cdk/aws-s3'; + + const recordName = 'www'; + const domainName = 'example.com'; - const bucketWebsite = new Bucket(this, 'BucketWebsite', { + const bucketWebsite = new s3.Bucket(this, 'BucketWebsite', { bucketName: [recordName, domainName].join('.'), // www.example.com publicReadAccess: true, websiteIndexDocument: 'index.html', }); - const zone = HostedZone.fromLookup(this, 'Zone', {domainName}); // example.com + const zone = route53.HostedZone.fromLookup(this, 'Zone', {domainName}); // example.com new route53.ARecord(this, 'AliasRecord', { zone, recordName, // www - target: route53.RecordTarget.fromAlias(new alias.BucketWebsiteTarget(bucket)), + target: route53.RecordTarget.fromAlias(new targets.BucketWebsiteTarget(bucketWebsite)), }); ``` * User pool domain ```ts + import * as cognito from '@aws-cdk/aws-cognito'; + + declare const zone: route53.HostedZone; + declare const domain: cognito.UserPoolDomain; new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.UserPoolDomainTarget(domain)), + target: route53.RecordTarget.fromAlias(new targets.UserPoolDomainTarget(domain)), }); ``` * Route 53 record ```ts + declare const zone: route53.HostedZone; + declare const record: route53.ARecord; new route53.ARecord(this, 'AliasRecord', { zone, target: route53.RecordTarget.fromAlias(new targets.Route53RecordTarget(record)), }); ``` +* Elastic Beanstalk environment: + +**Important:** Only supports Elastic Beanstalk environments created after 2016 that have a regional endpoint. + +```ts +declare const zone: route53.HostedZone; +declare const ebsEnvironmentUrl: string; + +new route53.ARecord(this, 'AliasRecord', { + zone, + target: route53.RecordTarget.fromAlias(new targets.ElasticBeanstalkEnvironmentEndpointTarget(ebsEnvironmentUrl)), +}); +``` + See the documentation of `@aws-cdk/aws-route53` for more information. diff --git a/packages/@aws-cdk/aws-route53-targets/jest.config.js b/packages/@aws-cdk/aws-route53-targets/jest.config.js index 3a2fd93a1228a..53d601eeeff94 100644 --- a/packages/@aws-cdk/aws-route53-targets/jest.config.js +++ b/packages/@aws-cdk/aws-route53-targets/jest.config.js @@ -1,2 +1,10 @@ const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); -module.exports = baseConfig; +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + branches: 70, + statements: 80, + } + } +}; diff --git a/packages/@aws-cdk/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts new file mode 100644 index 0000000000000..ef48c2845c233 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts @@ -0,0 +1,33 @@ +import * as route53 from '@aws-cdk/aws-route53'; +import * as cdk from '@aws-cdk/core'; +import { RegionInfo } from '@aws-cdk/region-info'; + +/** + * Use an Elastic Beanstalk environment URL as an alias record target. + * E.g. mysampleenvironment.xyz.us-east-1.elasticbeanstalk.com + * + * Only supports Elastic Beanstalk environments created after 2016 that have a regional endpoint. + */ +export class ElasticBeanstalkEnvironmentEndpointTarget implements route53.IAliasRecordTarget { + constructor(private readonly environmentEndpoint: string) { + } + + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + if (cdk.Token.isUnresolved(this.environmentEndpoint)) { + throw new Error('Cannot use an EBS alias as `environmentEndpoint`. You must find your EBS environment endpoint via the AWS console. See the Elastic Beanstalk developer guide: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customdomains.html'); + } + + const dnsName = this.environmentEndpoint; + const region = cdk.Fn.select(2, cdk.Fn.split('.', dnsName)); + const { ebsEnvEndpointHostedZoneId: hostedZoneId } = RegionInfo.get(region); + + if (!hostedZoneId || !dnsName) { + throw new Error(`Elastic Beanstalk environment target is not supported for the "${region}" region.`); + } + + return { + hostedZoneId, + dnsName, + }; + } +} diff --git a/packages/@aws-cdk/aws-route53-targets/lib/index.ts b/packages/@aws-cdk/aws-route53-targets/lib/index.ts index 92f1d9ee08d25..714719322b883 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/index.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/index.ts @@ -1,6 +1,7 @@ export * from './api-gateway-domain-name'; export * from './api-gatewayv2-domain-name'; export * from './bucket-website-target'; +export * from './elastic-beanstalk-environment-target'; export * from './classic-load-balancer-target'; export * from './cloudfront-target'; export * from './load-balancer-target'; diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index 8da79638f150b..a65e4a0372abf 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -72,8 +79,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53-targets/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-route53-targets/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6274dd0805499 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as targets from '@aws-cdk/aws-route53-targets'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-route53-targets/test/bucket-website-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/bucket-website-target.test.ts index eebb53b2d488a..ff1938e66447b 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/bucket-website-target.test.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/bucket-website-target.test.ts @@ -74,10 +74,10 @@ test('throws if region agnostic', () => { }).toThrow(/Cannot use an S3 record alias in region-agnostic stacks/); }); -test('throws if bucket website hosting is unavailable (cn-northwest-1)', () => { +test('throws if bucket website hosting is unavailable (cn-north-1)', () => { // GIVEN const app = new App(); - const stack = new Stack(app, 'test', { env: { region: 'cn-northwest-1' } }); + const stack = new Stack(app, 'test', { env: { region: 'cn-north-1' } }); const bucketWebsite = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-route53-targets/test/elastic-beanstalk-environment-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/elastic-beanstalk-environment-target.test.ts new file mode 100644 index 0000000000000..ed18f49362118 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/elastic-beanstalk-environment-target.test.ts @@ -0,0 +1,25 @@ +import '@aws-cdk/assert-internal/jest'; +import * as route53 from '@aws-cdk/aws-route53'; +import { Stack } from '@aws-cdk/core'; +import * as targets from '../lib'; + +test('use EBS environment as record target', () => { + // GIVEN + const stack = new Stack(); + const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' }); + + // WHEN + new route53.ARecord(stack, 'Alias', { + zone, + recordName: '_foo', + target: route53.RecordTarget.fromAlias(new targets.ElasticBeanstalkEnvironmentEndpointTarget('mysampleenvironment.xyz.us-east-1.elasticbeanstalk.com')), + }); + + // THEN + expect(stack).toHaveResource('AWS::Route53::RecordSet', { + AliasTarget: { + DNSName: 'mysampleenvironment.xyz.us-east-1.elasticbeanstalk.com', + HostedZoneId: 'Z117KPS5GTRQ2G', + }, + }); +}); diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index d4376f335d848..46dce3574a130 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -14,10 +14,8 @@ To add a public hosted zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; - new route53.PublicHostedZone(this, 'HostedZone', { - zoneName: 'fully.qualified.domain.com' + zoneName: 'fully.qualified.domain.com', }); ``` @@ -26,14 +24,11 @@ To add a private hosted zone, use `PrivateHostedZone`. Note that VPC you're configuring for private hosted zones. ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as route53 from '@aws-cdk/aws-route53'; - -const vpc = new ec2.Vpc(this, 'VPC'); +declare const vpc: ec2.Vpc; const zone = new route53.PrivateHostedZone(this, 'HostedZone', { zoneName: 'fully.qualified.domain.com', - vpc // At least one VPC has to be added to a Private Hosted Zone. + vpc, // At least one VPC has to be added to a Private Hosted Zone. }); ``` @@ -44,7 +39,7 @@ Additional VPCs can be added with `zone.addVpc()`. To add a TXT record to your zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; +declare const myZone: route53.HostedZone; new route53.TxtRecord(this, 'TXTRecord', { zone: myZone, @@ -54,7 +49,7 @@ new route53.TxtRecord(this, 'TXTRecord', { // Defaults to zone root if not specified. values: [ // Will be quoted for you, and " will be escaped automatically. 'Bar!', - 'Baz?' + 'Baz?', ], ttl: Duration.minutes(90), // Optional - default is 30 minutes }); @@ -63,14 +58,14 @@ new route53.TxtRecord(this, 'TXTRecord', { To add a NS record to your zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; +declare const myZone: route53.HostedZone; new route53.NsRecord(this, 'NSRecord', { zone: myZone, recordName: 'foo', values: [ 'ns-1.awsdns.co.uk.', - 'ns-2.awsdns.com.' + 'ns-2.awsdns.com.', ], ttl: Duration.minutes(90), // Optional - default is 30 minutes }); @@ -79,7 +74,7 @@ new route53.NsRecord(this, 'NSRecord', { To add a DS record to your zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; +declare const myZone: route53.HostedZone; new route53.DsRecord(this, 'DSRecord', { zone: myZone, @@ -94,44 +89,41 @@ new route53.DsRecord(this, 'DSRecord', { To add an A record to your zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; +declare const myZone: route53.HostedZone; new route53.ARecord(this, 'ARecord', { zone: myZone, - target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8') + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), }); ``` To add an A record for an EC2 instance with an Elastic IP (EIP) to your zone: ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as route53 from '@aws-cdk/aws-route53'; - -const instance = new ec2.Instance(this, 'Instance', { - // ... -}); +declare const instance: ec2.Instance; const elasticIp = new ec2.CfnEIP(this, 'EIP', { domain: 'vpc', - instanceId: instance.instanceId + instanceId: instance.instanceId, }); +declare const myZone: route53.HostedZone; new route53.ARecord(this, 'ARecord', { zone: myZone, - target: route53.RecordTarget.fromIpAddresses(elasticIp.ref) + target: route53.RecordTarget.fromIpAddresses(elasticIp.ref), }); ``` To add an AAAA record pointing to a CloudFront distribution: ```ts -import * as route53 from '@aws-cdk/aws-route53'; -import * as targets from '@aws-cdk/aws-route53-targets'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +declare const myZone: route53.HostedZone; +declare const distribution: cloudfront.CloudFrontWebDistribution; new route53.AaaaRecord(this, 'Alias', { zone: myZone, - target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)) + target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)), }); ``` @@ -145,8 +137,6 @@ To add a NS record to a HostedZone in different account you can do the following In the account containing the parent hosted zone: ```ts -import * as route53 from '@aws-cdk/aws-route53'; - const parentZone = new route53.PublicHostedZone(this, 'HostedZone', { zoneName: 'someexample.com', crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('12345678901'), @@ -157,11 +147,8 @@ const parentZone = new route53.PublicHostedZone(this, 'HostedZone', { In the account containing the child zone to be delegated: ```ts -import * as iam from '@aws-cdk/aws-iam'; -import * as route53 from '@aws-cdk/aws-route53'; - const subZone = new route53.PublicHostedZone(this, 'SubZone', { - zoneName: 'sub.someexample.com' + zoneName: 'sub.someexample.com', }); // import the delegation role by constructing the roleArn @@ -188,8 +175,8 @@ If you don't know the ID of the Hosted Zone to import, you can use the `HostedZone.fromLookup`: ```ts -HostedZone.fromLookup(this, 'MyZone', { - domainName: 'example.com' +route53.HostedZone.fromLookup(this, 'MyZone', { + domainName: 'example.com', }); ``` @@ -198,18 +185,19 @@ out the [documentation](https://docs.aws.amazon.com/cdk/latest/guide/environment automatically looks into your `~/.aws/config` file for the `[default]` profile. If you want to specify a different account run `cdk deploy --profile [profile]`. -```ts +```text new MyDevStack(app, 'dev', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION -}}); + region: process.env.CDK_DEFAULT_REGION, + }, +}); ``` If you know the ID and Name of a Hosted Zone, you can import it directly: ```ts -const zone = HostedZone.fromHostedZoneAttributes(this, 'MyZone', { +const zone = route53.HostedZone.fromHostedZoneAttributes(this, 'MyZone', { zoneName: 'example.com', hostedZoneId: 'ZOJJZC49E0EPZ', }); @@ -219,7 +207,7 @@ Alternatively, use the `HostedZone.fromHostedZoneId` to import hosted zones if you know the ID and the retrieval for the `zoneName` is undesirable. ```ts -const zone = HostedZone.fromHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ'); +const zone = route53.HostedZone.fromHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ'); ``` ## VPC Endpoint Service Private DNS @@ -245,22 +233,22 @@ Assuming your account has ownership of the particular domain/subdomain, this construct sets up the private DNS configuration on the endpoint service, creates all the necessary Route53 entries, and verifies domain ownership. -```ts +```ts nofixture import { Stack } from '@aws-cdk/core'; import { Vpc, VpcEndpointService } from '@aws-cdk/aws-ec2'; import { NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2'; -import { PublicHostedZone } from '@aws-cdk/aws-route53'; +import { PublicHostedZone, VpcEndpointServiceDomainName } from '@aws-cdk/aws-route53'; -stack = new Stack(); -vpc = new Vpc(stack, 'VPC'); -nlb = new NetworkLoadBalancer(stack, 'NLB', { +const stack = new Stack(); +const vpc = new Vpc(stack, 'VPC'); +const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc, }); -vpces = new VpcEndpointService(stack, 'VPCES', { +const vpces = new VpcEndpointService(stack, 'VPCES', { vpcEndpointServiceLoadBalancers: [nlb], }); // You must use a public hosted zone so domain ownership can be verified -zone = new PublicHostedZone(stack, 'PHZ', { +const zone = new PublicHostedZone(stack, 'PHZ', { zoneName: 'aws-cdk.dev', }); new VpcEndpointServiceDomainName(stack, 'EndpointDomain', { diff --git a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts index 9098503b625f4..862a63e26e6d1 100644 --- a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts +++ b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts @@ -46,6 +46,11 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { // Track all domain names created, so someone doesn't accidentally associate two domains with a single service private static readonly endpointServicesMap: { [endpointService: string]: string} = {}; + /** + * The domain name associated with the private DNS configuration + */ + public domainName: string; + // The way this class works is by using three custom resources and a TxtRecord in conjunction // The first custom resource tells the VPC endpoint service to use the given DNS name // The VPC endpoint service will then say: @@ -58,16 +63,16 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { const serviceUniqueId = Names.nodeUniqueId(props.endpointService.node); const serviceId = props.endpointService.vpcEndpointServiceId; - const privateDnsName = props.domainName; + this.domainName = props.domainName; // Make sure a user doesn't accidentally add multiple domains this.validateProps(props); - VpcEndpointServiceDomainName.endpointServicesMap[serviceUniqueId] = privateDnsName; + VpcEndpointServiceDomainName.endpointServicesMap[serviceUniqueId] = this.domainName; VpcEndpointServiceDomainName.endpointServices.push(props.endpointService); // Enable Private DNS on the endpoint service and retrieve the AWS-generated configuration - const privateDnsConfiguration = this.getPrivateDnsConfiguration(serviceUniqueId, serviceId, privateDnsName); + const privateDnsConfiguration = this.getPrivateDnsConfiguration(serviceUniqueId, serviceId, this.domainName); // Tell AWS to verify that this account owns the domain attached to the service this.verifyPrivateDnsConfiguration(privateDnsConfiguration, props.publicHostedZone); diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 2c775b5bd89d8..aabd22c95363b 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,10 +84,10 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-route53/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6473005fda521 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as route53 from '@aws-cdk/aws-route53'; +import * as targets from '@aws-cdk/aws-route53-targets'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts index 35b7ac9c67596..506a25e2a26df 100644 --- a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts +++ b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts @@ -264,4 +264,20 @@ test('throws if creating multiple domains for a single service', () => { publicHostedZone: zone, }); }).toThrow(/Cannot create a VpcEndpointServiceDomainName for service/); +}); + + +test('endpoint domain name property equals input domain name', () => { + // GIVEN + vpces = new VpcEndpointService(stack, 'NameTest', { + vpcEndpointServiceLoadBalancers: [nlb], + }); + + const dn = new VpcEndpointServiceDomainName(stack, 'EndpointDomain', { + endpointService: vpces, + domainName: 'name-test.aws-cdk.dev', + publicHostedZone: zone, + }); + expect(dn.domainName).toEqual('name-test.aws-cdk.dev'); + }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53recoverycontrol/package.json b/packages/@aws-cdk/aws-route53recoverycontrol/package.json index 71c0d5c3e12b9..9c1c49bad1887 100644 --- a/packages/@aws-cdk/aws-route53recoverycontrol/package.json +++ b/packages/@aws-cdk/aws-route53recoverycontrol/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-route53recoveryreadiness/package.json b/packages/@aws-cdk/aws-route53recoveryreadiness/package.json index 386a35690f2dc..46ded25804f91 100644 --- a/packages/@aws-cdk/aws-route53recoveryreadiness/package.json +++ b/packages/@aws-cdk/aws-route53recoveryreadiness/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index 74d7bd95b363b..8a9ff2e0cbc1a 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 8ab66aee891bb..d7df92e0c25b4 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -75,7 +75,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index ccc93159714ff..cf0a5c7bc03af 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -25,7 +25,7 @@ test('simple use case', () => { // verify that metadata contains an "aws:cdk:asset" entry with // the correct information - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // verify that now the template contains parameters for this asset @@ -75,7 +75,7 @@ test('"file" assets', () => { const stack = new cdk.Stack(); const filePath = path.join(__dirname, 'file-asset.txt'); new Asset(stack, 'MyAsset', { path: filePath }); - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // synthesize first so "prepare" is called diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 102b6e90ffc00..c9f0d392e22e4 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -83,8 +83,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudfront": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts index 7f799b2f9ff65..3713b99ae3159 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts @@ -4,9 +4,9 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import { testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as s3deploy from '../lib'; /* eslint-disable max-len */ @@ -277,7 +277,13 @@ test('deploy from a local .zip file when efs is enabled', () => { }); }); -test('honors passed asset options', () => { +testDeprecated('honors passed asset options', () => { + // The 'exclude' property is deprecated and not deprecated in AssetOptions interface. + // The interface through a complex set of inheritance chain has a 'exclude' prop that is deprecated + // and another 'exclude' prop that is not deprecated. + // Using 'testDeprecated' block here since there's no way to work around this craziness. + // When the deprecated property is removed from source, this block can be dropped. + // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Dest'); @@ -764,7 +770,6 @@ test('deploy without deleting missing files from destination', () => { }); test('deploy with excluded files from destination', () => { - // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Dest'); diff --git a/packages/@aws-cdk/aws-s3-notifications/README.md b/packages/@aws-cdk/aws-s3-notifications/README.md index 8efbb5149f627..f054708f437fb 100644 --- a/packages/@aws-cdk/aws-s3-notifications/README.md +++ b/packages/@aws-cdk/aws-s3-notifications/README.md @@ -12,7 +12,7 @@ This module includes integration classes for using Topics, Queues or Lambdas as S3 Notification Destinations. -## Example +## Examples The following example shows how to send a notification to an SNS topic when an object is created in an S3 bucket: @@ -25,3 +25,18 @@ const topic = new sns.Topic(stack, 'Topic'); bucket.addEventNotification(s3.EventType.OBJECT_CREATED_PUT, new s3n.SnsDestination(topic)); ``` + +The following example shows how to send a notification to a Lambda function when an object is created in an S3 bucket: + +```ts +import * as s3n from '@aws-cdk/aws-s3-notifications'; + +const bucket = new s3.Bucket(stack, 'Bucket'); +const fn = new Function(this, 'MyFunction', { + runtime: Runtime.NODEJS_12_X, + handler: 'index.handler', + code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), +}); + +bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(fn)); +``` diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index 2ffab562221db..e49109b6c0a6d 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -68,8 +68,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index 43922fb54cc5d..2bfe968f99279 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -336,7 +336,7 @@ describe('CloudWatch Events', () => { test('onCloudTrailPutObject contains the Bucket ARN itself when path is undefined', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'MyBucket', + bucketName: 'mybucket', }); bucket.onCloudTrailPutObject('PutRule', { target: { @@ -363,7 +363,7 @@ describe('CloudWatch Events', () => { { 'Ref': 'AWS::Partition', }, - ':s3:::MyBucket', + ':s3:::mybucket', ], ], }, @@ -378,7 +378,7 @@ describe('CloudWatch Events', () => { test("onCloudTrailPutObject contains the path when it's provided", () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'MyBucket', + bucketName: 'mybucket', }); bucket.onCloudTrailPutObject('PutRule', { target: { @@ -406,7 +406,7 @@ describe('CloudWatch Events', () => { { 'Ref': 'AWS::Partition', }, - ':s3:::MyBucket/my/path.zip', + ':s3:::mybucket/my/path.zip', ], ], }, @@ -421,7 +421,7 @@ describe('CloudWatch Events', () => { test('onCloudTrailWriteObject matches on events CompleteMultipartUpload, CopyObject, and PutObject', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'MyBucket', + bucketName: 'mybucket', }); bucket.onCloudTrailWriteObject('OnCloudTrailWriteObjectRule', { target: { @@ -449,7 +449,7 @@ describe('CloudWatch Events', () => { test('onCloudTrailWriteObject matches on the requestParameter bucketName when the path is not provided', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'MyBucket', + bucketName: 'mybucket', }); bucket.onCloudTrailWriteObject('OnCloudTrailWriteObjectRule', { target: { @@ -476,7 +476,7 @@ describe('CloudWatch Events', () => { test('onCloudTrailWriteObject matches on the requestParameters bucketName and key when the path is provided', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'MyBucket', + bucketName: 'mybucket', }); bucket.onCloudTrailWriteObject('OnCloudTrailWriteObjectRule', { target: { diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 60f97010b9a20..b666ee14b44d5 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -14,7 +14,7 @@ Define an unencrypted S3 bucket. ```ts -new Bucket(this, 'MyFirstBucket'); +const bucket = new s3.Bucket(this, 'MyFirstBucket'); ``` `Bucket` constructs expose the following deploy-time attributes: @@ -43,8 +43,8 @@ new Bucket(this, 'MyFirstBucket'); Define a KMS-encrypted bucket: ```ts -const bucket = new Bucket(this, 'MyEncryptedBucket', { - encryption: BucketEncryption.KMS +const bucket = new s3.Bucket(this, 'MyEncryptedBucket', { + encryption: s3.BucketEncryption.KMS, }); // you can access the encryption key: @@ -56,9 +56,9 @@ You can also supply your own key: ```ts const myKmsKey = new kms.Key(this, 'MyKey'); -const bucket = new Bucket(this, 'MyEncryptedBucket', { - encryption: BucketEncryption.KMS, - encryptionKey: myKmsKey +const bucket = new s3.Bucket(this, 'MyEncryptedBucket', { + encryption: s3.BucketEncryption.KMS, + encryptionKey: myKmsKey, }); assert(bucket.encryptionKey === myKmsKey); @@ -67,19 +67,17 @@ assert(bucket.encryptionKey === myKmsKey); Enable KMS-SSE encryption via [S3 Bucket Keys](https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-key.html): ```ts -const bucket = new Bucket(this, 'MyEncryptedBucket', { - encryption: BucketEncryption.KMS, - bucketKeyEnabled: true +const bucket = new s3.Bucket(this, 'MyEncryptedBucket', { + encryption: s3.BucketEncryption.KMS, + bucketKeyEnabled: true, }); - -assert(bucket.bucketKeyEnabled === true); ``` Use `BucketEncryption.ManagedKms` to use the S3 master KMS key: ```ts -const bucket = new Bucket(this, 'Buck', { - encryption: BucketEncryption.KMS_MANAGED +const bucket = new s3.Bucket(this, 'Buck', { + encryption: s3.BucketEncryption.KMS_MANAGED, }); assert(bucket.encryptionKey == null); @@ -91,7 +89,7 @@ A bucket policy will be automatically created for the bucket upon the first call `addToResourcePolicy(statement)`: ```ts -const bucket = new Bucket(this, 'MyBucket'); +const bucket = new s3.Bucket(this, 'MyBucket'); const result = bucket.addToResourcePolicy(new iam.PolicyStatement({ actions: ['s3:GetObject'], resources: [bucket.arnForObjects('file.txt')], @@ -103,11 +101,13 @@ If you try to add a policy statement to an existing bucket, this method will not do anything: ```ts -const bucket = Bucket.fromBucketName(this, 'existingBucket', 'bucket-name'); +const bucket = s3.Bucket.fromBucketName(this, 'existingBucket', 'bucket-name'); -// Nothing will change here +// No policy statement will be added to the resource const result = bucket.addToResourcePolicy(new iam.PolicyStatement({ - ... + actions: ['s3:GetObject'], + resources: [bucket.arnForObjects('file.txt')], + principals: [new iam.AccountRootPrincipal()], })); ``` @@ -116,7 +116,13 @@ already has a policy attached, let alone to re-use that policy to add more statements to it. We recommend that you always check the result of the call: ```ts -const result = bucket.addToResourcePolicy(...) +const bucket = new s3.Bucket(this, 'MyBucket'); +const result = bucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [bucket.arnForObjects('file.txt')], + principals: [new iam.AccountRootPrincipal()], +})); + if (!result.statementAdded) { // Uh-oh! Someone probably made a mistake here. } @@ -126,7 +132,8 @@ The bucket policy can be directly accessed after creation to add statements or adjust the removal policy. ```ts -bucket.policy?.applyRemovalPolicy(RemovalPolicy.RETAIN); +const bucket = new s3.Bucket(this, 'MyBucket'); +bucket.policy?.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); ``` Most of the time, you won't have to manipulate the bucket policy directly. @@ -134,10 +141,10 @@ Instead, buckets have "grant" methods called to give prepackaged sets of permiss to other resources. For example: ```ts -const lambda = new lambda.Function(this, 'Lambda', { /* ... */ }); +declare const myLambda: lambda.Function; -const bucket = new Bucket(this, 'MyBucket'); -bucket.grantReadWrite(lambda); +const bucket = new s3.Bucket(this, 'MyBucket'); +bucket.grantReadWrite(myLambda); ``` Will give the Lambda's execution role permissions to read and write @@ -150,8 +157,8 @@ from the bucket. To require all requests use Secure Socket Layer (SSL): ```ts -const bucket = new Bucket(this, 'Bucket', { - enforceSSL: true +const bucket = new s3.Bucket(this, 'Bucket', { + enforceSSL: true, }); ``` @@ -168,12 +175,13 @@ factory method. This method accepts `BucketAttributes` which describes the prope existing bucket: ```ts -const bucket = Bucket.fromBucketAttributes(this, 'ImportedBucket', { - bucketArn: 'arn:aws:s3:::my-bucket' +declare const myLambda: lambda.Function; +const bucket = s3.Bucket.fromBucketAttributes(this, 'ImportedBucket', { + bucketArn: 'arn:aws:s3:::my-bucket', }); // now you can just call methods on the bucket -bucket.addEventNotification(EventType.OBJECT_CREATED, ...); +bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(myLambda), {prefix: 'home/myusername/*'}); ``` Alternatively, short-hand factories are available as `Bucket.fromBucketName` and @@ -181,15 +189,15 @@ Alternatively, short-hand factories are available as `Bucket.fromBucketName` and name or ARN respectively: ```ts -const byName = Bucket.fromBucketName(this, 'BucketByName', 'my-bucket'); -const byArn = Bucket.fromBucketArn(this, 'BucketByArn', 'arn:aws:s3:::my-bucket'); +const byName = s3.Bucket.fromBucketName(this, 'BucketByName', 'my-bucket'); +const byArn = s3.Bucket.fromBucketArn(this, 'BucketByArn', 'arn:aws:s3:::my-bucket'); ``` The bucket's region defaults to the current stack's region, but can also be explicitly set in cases where one of the bucket's regional properties needs to contain the correct values. ```ts -const myCrossRegionBucket = Bucket.fromBucketAttributes(this, 'CrossRegionImport', { +const myCrossRegionBucket = s3.Bucket.fromBucketAttributes(this, 'CrossRegionImport', { bucketArn: 'arn:aws:s3:::my-bucket', region: 'us-east-1', }); @@ -209,8 +217,7 @@ these common use cases. The following example will subscribe an SNS topic to be notified of all `s3:ObjectCreated:*` events: ```ts -import * as s3n from '@aws-cdk/aws-s3-notifications'; - +const bucket = new s3.Bucket(this, 'MyBucket'); const topic = new sns.Topic(this, 'MyTopic'); bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); ``` @@ -225,6 +232,8 @@ following example will notify `myQueue` when objects prefixed with `foo/` and have the `.jpg` suffix are removed from the bucket. ```ts +declare const myQueue: sqs.Queue; +const bucket = new s3.Bucket(this, 'MyBucket'); bucket.addEventNotification(s3.EventType.OBJECT_REMOVED, new s3n.SqsDestination(myQueue), { prefix: 'foo/', suffix: '.jpg' }); @@ -233,8 +242,9 @@ bucket.addEventNotification(s3.EventType.OBJECT_REMOVED, Adding notifications on existing buckets: ```ts -const bucket = Bucket.fromBucketAttributes(this, 'ImportedBucket', { - bucketArn: 'arn:aws:s3:::my-bucket' +declare const topic: sns.Topic; +const bucket = s3.Bucket.fromBucketAttributes(this, 'ImportedBucket', { + bucketArn: 'arn:aws:s3:::my-bucket', }); bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); ``` @@ -249,24 +259,24 @@ Use `blockPublicAccess` to specify [block public access settings] on the bucket. Enable all block public access settings: ```ts -const bucket = new Bucket(this, 'MyBlockedBucket', { - blockPublicAccess: BlockPublicAccess.BLOCK_ALL +const bucket = new s3.Bucket(this, 'MyBlockedBucket', { + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); ``` Block and ignore public ACLs: ```ts -const bucket = new Bucket(this, 'MyBlockedBucket', { - blockPublicAccess: BlockPublicAccess.BLOCK_ACLS +const bucket = new s3.Bucket(this, 'MyBlockedBucket', { + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, }); ``` Alternatively, specify the settings manually: ```ts -const bucket = new Bucket(this, 'MyBlockedBucket', { - blockPublicAccess: new BlockPublicAccess({ blockPublicPolicy: true }) +const bucket = new s3.Bucket(this, 'MyBlockedBucket', { + blockPublicAccess: new s3.BlockPublicAccess({ blockPublicPolicy: true }), }); ``` @@ -279,9 +289,9 @@ When `blockPublicPolicy` is set to `true`, `grantPublicRead()` throws an error. Use `serverAccessLogsBucket` to describe where server access logs are to be stored. ```ts -const accessLogsBucket = new Bucket(this, 'AccessLogsBucket'); +const accessLogsBucket = new s3.Bucket(this, 'AccessLogsBucket'); -const bucket = new Bucket(this, 'MyBucket', { +const bucket = new s3.Bucket(this, 'MyBucket', { serverAccessLogsBucket: accessLogsBucket, }); ``` @@ -289,9 +299,11 @@ const bucket = new Bucket(this, 'MyBucket', { It's also possible to specify a prefix for Amazon S3 to assign to all log object keys. ```ts -const bucket = new Bucket(this, 'MyBucket', { +const accessLogsBucket = new s3.Bucket(this, 'AccessLogsBucket'); + +const bucket = new s3.Bucket(this, 'MyBucket', { serverAccessLogsBucket: accessLogsBucket, - serverAccessLogsPrefix: 'logs' + serverAccessLogsPrefix: 'logs', }); ``` @@ -322,8 +334,8 @@ const dataBucket = new s3.Bucket(this, 'DataBucket', { bucket: inventoryBucket, prefix: 'with-all-versions', }, - } - ] + }, + ], }); ``` @@ -356,8 +368,8 @@ You can use the two following properties to specify the bucket [redirection poli You can statically redirect a to a given Bucket URL or any other host name with `websiteRedirect`: ```ts -const bucket = new Bucket(this, 'MyRedirectedBucket', { - websiteRedirect: { hostName: 'www.example.com' } +const bucket = new s3.Bucket(this, 'MyRedirectedBucket', { + websiteRedirect: { hostName: 'www.example.com' }, }); ``` @@ -366,17 +378,17 @@ const bucket = new Bucket(this, 'MyRedirectedBucket', { Alternatively, you can also define multiple `websiteRoutingRules`, to define complex, conditional redirections: ```ts -const bucket = new Bucket(this, 'MyRedirectedBucket', { +const bucket = new s3.Bucket(this, 'MyRedirectedBucket', { websiteRoutingRules: [{ hostName: 'www.example.com', httpRedirectCode: '302', - protocol: RedirectProtocol.HTTPS, - replaceKey: ReplaceKey.prefixWith('test/'), + protocol: s3.RedirectProtocol.HTTPS, + replaceKey: s3.ReplaceKey.prefixWith('test/'), condition: { httpErrorCodeReturnedEquals: '200', keyPrefixEquals: 'prefix', - } - }] + }, + }], }); ``` @@ -397,6 +409,7 @@ We recommend to use Virtual Hosted-Style URL for newly made bucket. You can generate both of them. ```ts +const bucket = new s3.Bucket(this, 'MyBucket'); 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 @@ -440,13 +453,13 @@ To override this and force all objects to get deleted during bucket deletion, enable the`autoDeleteObjects` option. ```ts -const bucket = new Bucket(this, 'MyTempFileBucket', { - removalPolicy: RemovalPolicy.DESTROY, +const bucket = new s3.Bucket(this, 'MyTempFileBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); ``` **Warning** if you have deployed a bucket with `autoDeleteObjects: true`, -switching this to `false` in a CDK version *before* `1.126.0` will lead to all -objects in the bucket being deleted. Be sure to update to version `1.126.0` or -later before switching this value to `false`. +switching this to `false` in a CDK version *before* `1.126.0` will lead to +all objects in the bucket being deleted. Be sure to update your bucket resources +by deploying with CDK version `1.126.0` or later **before** switching this value to `false`. diff --git a/packages/@aws-cdk/aws-s3/lib/auto-delete-objects-handler/index.ts b/packages/@aws-cdk/aws-s3/lib/auto-delete-objects-handler/index.ts index fed602825c6a0..2459d44ab1d18 100644 --- a/packages/@aws-cdk/aws-s3/lib/auto-delete-objects-handler/index.ts +++ b/packages/@aws-cdk/aws-s3/lib/auto-delete-objects-handler/index.ts @@ -58,7 +58,14 @@ async function onDelete(bucketName?: string) { process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); return; } - await emptyBucket(bucketName); + try { + await emptyBucket(bucketName); + } catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } } /** diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index d9065d0719c72..ce8dafd5a69aa 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -105,9 +105,10 @@ export interface IBucket extends IResource { /** * The https URL of an S3 object. 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 + * + * - `https://s3.us-west-1.amazonaws.com/onlybucket` + * - `https://s3.us-west-1.amazonaws.com/bucket/key` + * - `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey` * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -117,10 +118,11 @@ export interface IBucket extends IResource { /** * 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 + * + * - `https://only-bucket.s3.us-west-1.amazonaws.com` + * - `https://bucket.s3.us-west-1.amazonaws.com/key` + * - `https://bucket.s3.amazonaws.com/key` + * - `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. @@ -130,8 +132,8 @@ export interface IBucket extends IResource { /** * The S3 URL of an S3 object. For example: - * @example s3://onlybucket - * @example s3://bucket/key + * - `s3://onlybucket` + * - `s3://bucket/key` * @param key The S3 key of the object. If not specified, the S3 URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -309,7 +311,9 @@ export interface IBucket extends IResource { * * @example * - * bucket.addEventNotification(EventType.OnObjectCreated, myLambda, 'home/myusername/*') + * declare const myLambda: lambda.Function; + * const bucket = new s3.Bucket(this, 'MyBucket'); + * bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(myLambda), {prefix: 'home/myusername/*'}) * * @see * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html @@ -319,7 +323,7 @@ export interface IBucket extends IResource { /** * Subscribes a destination to receive notifications when an object is * created in the bucket. This is identical to calling - * `onEvent(EventType.ObjectCreated)`. + * `onEvent(s3.EventType.OBJECT_CREATED)`. * * @param dest The notification destination (see onEvent) * @param filters Filters (see onEvent) @@ -329,7 +333,7 @@ export interface IBucket extends IResource { /** * Subscribes a destination to receive notifications when an object is * removed from the bucket. This is identical to calling - * `onEvent(EventType.ObjectRemoved)`. + * `onEvent(EventType.OBJECT_REMOVED)`. * * @param dest The notification destination (see onEvent) * @param filters Filters (see onEvent) @@ -601,9 +605,11 @@ export abstract class BucketBase extends Resource implements IBucket { /** * 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 + * + * - `https://s3.us-west-1.amazonaws.com/onlybucket` + * - `https://s3.us-west-1.amazonaws.com/bucket/key` + * - `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey` + * * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -620,10 +626,12 @@ export abstract class BucketBase extends Resource implements IBucket { /** * 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 + * + * - `https://only-bucket.s3.us-west-1.amazonaws.com` + * - `https://bucket.s3.us-west-1.amazonaws.com/key` + * - `https://bucket.s3.amazonaws.com/key` + * - `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. @@ -640,8 +648,10 @@ export abstract class BucketBase extends Resource implements IBucket { /** * The S3 URL of an S3 object. For example: - * @example s3://onlybucket - * @example s3://bucket/key + * + * - `s3://onlybucket` + * - `s3://bucket/key` + * * @param key The S3 key of the object. If not specified, the S3 URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -785,7 +795,9 @@ export abstract class BucketBase extends Resource implements IBucket { * * @example * - * bucket.addEventNotification(EventType.OnObjectCreated, myLambda, 'home/myusername/*') + * declare const myLambda: lambda.Function; + * const bucket = new s3.Bucket(this, 'MyBucket'); + * bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(myLambda), {prefix: 'home/myusername/*'}); * * @see * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html @@ -797,7 +809,7 @@ export abstract class BucketBase extends Resource implements IBucket { /** * Subscribes a destination to receive notifications when an object is * created in the bucket. This is identical to calling - * `onEvent(EventType.ObjectCreated)`. + * `onEvent(EventType.OBJECT_CREATED)`. * * @param dest The notification destination (see onEvent) * @param filters Filters (see onEvent) @@ -809,7 +821,7 @@ export abstract class BucketBase extends Resource implements IBucket { /** * Subscribes a destination to receive notifications when an object is * removed from the bucket. This is identical to calling - * `onEvent(EventType.ObjectRemoved)`. + * `onEvent(EventType.OBJECT_REMOVED)`. * * @param dest The notification destination (see onEvent) * @param filters Filters (see onEvent) @@ -1392,6 +1404,7 @@ export class Bucket extends BucketBase { if (!bucketName) { throw new Error('Bucket name is required'); } + Bucket.validateBucketName(bucketName); const newUrlFormat = attrs.bucketWebsiteNewUrlFormat === undefined ? false @@ -1430,6 +1443,52 @@ export class Bucket extends BucketBase { }); } + /** + * Thrown an exception if the given bucket name is not valid. + * + * @param physicalName name of the bucket. + */ + public static validateBucketName(physicalName: string): void { + const bucketName = physicalName; + if (!bucketName || Token.isUnresolved(bucketName)) { + // the name is a late-bound value, not a defined string, + // so skip validation + return; + } + + const errors: string[] = []; + + // Rules codified from https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html + if (bucketName.length < 3 || bucketName.length > 63) { + errors.push('Bucket name must be at least 3 and no more than 63 characters'); + } + const charsetMatch = bucketName.match(/[^a-z0-9.-]/); + if (charsetMatch) { + errors.push('Bucket name must only contain lowercase characters and the symbols, period (.) and dash (-) ' + + `(offset: ${charsetMatch.index})`); + } + if (!/[a-z0-9]/.test(bucketName.charAt(0))) { + errors.push('Bucket name must start and end with a lowercase character or number ' + + '(offset: 0)'); + } + if (!/[a-z0-9]/.test(bucketName.charAt(bucketName.length - 1))) { + errors.push('Bucket name must start and end with a lowercase character or number ' + + `(offset: ${bucketName.length - 1})`); + } + const consecSymbolMatch = bucketName.match(/\.-|-\.|\.\./); + if (consecSymbolMatch) { + errors.push('Bucket name must not have dash next to period, or period next to dash, or consecutive periods ' + + `(offset: ${consecSymbolMatch.index})`); + } + if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(bucketName)) { + errors.push('Bucket name must not resemble an IP address'); + } + + if (errors.length > 0) { + throw new Error(`Invalid S3 bucket name (value: ${bucketName})${EOL}${errors.join(EOL)}`); + } + } + public readonly bucketArn: string; public readonly bucketName: string; public readonly bucketDomainName: string; @@ -1458,7 +1517,7 @@ export class Bucket extends BucketBase { const { bucketEncryption, encryptionKey } = this.parseEncryption(props); - this.validateBucketName(this.physicalName); + Bucket.validateBucketName(this.physicalName); const websiteConfiguration = this.renderWebsiteConfiguration(props); this.isWebsite = (websiteConfiguration !== undefined); @@ -1596,47 +1655,6 @@ export class Bucket extends BucketBase { this.addToResourcePolicy(statement); } - private validateBucketName(physicalName: string): void { - const bucketName = physicalName; - if (!bucketName || Token.isUnresolved(bucketName)) { - // the name is a late-bound value, not a defined string, - // so skip validation - return; - } - - const errors: string[] = []; - - // Rules codified from https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html - if (bucketName.length < 3 || bucketName.length > 63) { - errors.push('Bucket name must be at least 3 and no more than 63 characters'); - } - const charsetMatch = bucketName.match(/[^a-z0-9.-]/); - if (charsetMatch) { - errors.push('Bucket name must only contain lowercase characters and the symbols, period (.) and dash (-) ' - + `(offset: ${charsetMatch.index})`); - } - if (!/[a-z0-9]/.test(bucketName.charAt(0))) { - errors.push('Bucket name must start and end with a lowercase character or number ' - + '(offset: 0)'); - } - if (!/[a-z0-9]/.test(bucketName.charAt(bucketName.length - 1))) { - errors.push('Bucket name must start and end with a lowercase character or number ' - + `(offset: ${bucketName.length - 1})`); - } - const consecSymbolMatch = bucketName.match(/\.-|-\.|\.\./); - if (consecSymbolMatch) { - errors.push('Bucket name must not have dash next to period, or period next to dash, or consecutive periods ' - + `(offset: ${consecSymbolMatch.index})`); - } - if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(bucketName)) { - errors.push('Bucket name must not resemble an IP address'); - } - - if (errors.length > 0) { - throw new Error(`Invalid S3 bucket name (value: ${bucketName})${EOL}${errors.join(EOL)}`); - } - } - /** * Set up key properties and return the Bucket encryption property from the * user's configuration. diff --git a/packages/@aws-cdk/aws-s3/lib/util.ts b/packages/@aws-cdk/aws-s3/lib/util.ts index 1c45ed899be4b..43bf816be6840 100644 --- a/packages/@aws-cdk/aws-s3/lib/util.ts +++ b/packages/@aws-cdk/aws-s3/lib/util.ts @@ -32,7 +32,7 @@ export function parseBucketName(construct: IConstruct, props: BucketAttributes): // extract bucket name from bucket arn if (props.bucketArn) { - return cdk.Stack.of(construct).parseArn(props.bucketArn).resource; + return cdk.Stack.of(construct).splitArn(props.bucketArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resource; } // no bucket name is okay since it's optional. diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 752032c268f34..f8153d202545d 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,9 +84,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-events": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-s3/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..de01c16af1e9a --- /dev/null +++ b/packages/@aws-cdk/aws-s3/rosetta/default.ts-fixture @@ -0,0 +1,18 @@ +// Fixture with packages imported, but nothing else +import cdk = require('@aws-cdk/core'); +import s3 = require('@aws-cdk/aws-s3'); +import kms = require('@aws-cdk/aws-kms'); +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); +import s3n = require('@aws-cdk/aws-s3-notifications'); +import sns = require('@aws-cdk/aws-sns'); +import sqs = require('@aws-cdk/aws-sqs'); +import assert = require('assert'); + +class Fixture extends cdk.Stack { + constructor(scope: cdk.Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-s3/test/auto-delete-objects-handler.test.ts b/packages/@aws-cdk/aws-s3/test/auto-delete-objects-handler.test.ts index 51cc65b3d5466..b4b7f523faef2 100644 --- a/packages/@aws-cdk/aws-s3/test/auto-delete-objects-handler.test.ts +++ b/packages/@aws-cdk/aws-s3/test/auto-delete-objects-handler.test.ts @@ -287,6 +287,23 @@ test('delete event where bucket has many objects does recurse appropriately', as }); }); +test('does nothing when the bucket does not exist', async () => { + // GIVEN + mockS3Client.promise.mockRejectedValue({ code: 'NoSuchBucket' }); + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + BucketName: 'MyBucket', + }, + }; + await invokeHandler(event); + + expect(mockS3Client.deleteObjects).not.toHaveBeenCalled(); +}); + // helper function to get around TypeScript expecting a complete event object, // even though our tests only need some of the fields async function invokeHandler(event: Partial) { diff --git a/packages/@aws-cdk/aws-s3/test/bucket.test.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts index 3ef166722c1b6..67d263aa60ea5 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -3,9 +3,9 @@ import { EOL } from 'os'; import { ResourcePart, SynthUtils, arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: @@ -103,8 +103,6 @@ describe('bucket', () => { expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: '124.pp--33', })).not.toThrow(); - - }); test('bucket validation skips tokenized values', () => { @@ -746,14 +744,24 @@ describe('bucket', () => { }); const bucket = s3.Bucket.fromBucketAttributes(stack, 'ImportedBucket', { - bucketName: 'myBucket', + bucketName: 'mybucket', region: 'eu-west-1', }); - expect(bucket.bucketRegionalDomainName).toEqual(`myBucket.s3.eu-west-1.${stack.urlSuffix}`); - expect(bucket.bucketWebsiteDomainName).toEqual(`myBucket.s3-website-eu-west-1.${stack.urlSuffix}`); + expect(bucket.bucketRegionalDomainName).toEqual(`mybucket.s3.eu-west-1.${stack.urlSuffix}`); + expect(bucket.bucketWebsiteDomainName).toEqual(`mybucket.s3-website-eu-west-1.${stack.urlSuffix}`); + + }); + + test('import needs to specify a valid bucket name', () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { region: 'us-east-1' }, + }); + expect(() => s3.Bucket.fromBucketAttributes(stack, 'MyBucket3', { + bucketName: 'arn:aws:s3:::example-com', + })).toThrow(); }); }); @@ -2129,11 +2137,11 @@ describe('bucket', () => { const stack = new cdk.Stack(); // WHEN - const bucket = s3.Bucket.fromBucketArn(stack, 'my-bucket', 'arn:aws:s3:::my_corporate_bucket'); + const bucket = s3.Bucket.fromBucketArn(stack, 'my-bucket', 'arn:aws:s3:::my-corporate-bucket'); // THEN - expect(bucket.bucketName).toEqual('my_corporate_bucket'); - expect(bucket.bucketArn).toEqual('arn:aws:s3:::my_corporate_bucket'); + expect(bucket.bucketName).toEqual('my-corporate-bucket'); + expect(bucket.bucketArn).toEqual('arn:aws:s3:::my-corporate-bucket'); }); diff --git a/packages/@aws-cdk/aws-s3objectlambda/package.json b/packages/@aws-cdk/aws-s3objectlambda/package.json index 8890f752cefe4..047a32f22af59 100644 --- a/packages/@aws-cdk/aws-s3objectlambda/package.json +++ b/packages/@aws-cdk/aws-s3objectlambda/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-s3outposts/package.json b/packages/@aws-cdk/aws-s3outposts/package.json index 930f149e5fb6e..7ac9ab32c9976 100644 --- a/packages/@aws-cdk/aws-s3outposts/package.json +++ b/packages/@aws-cdk/aws-s3outposts/package.json @@ -80,7 +80,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index 538e00d0afb5a..254b0c58aeb17 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index 9bd8d5caa4a13..0fa9799b59819 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -77,9 +77,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "@types/jest": "^27.0.2", + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index cce3519d22c62..70bd66f85a880 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index fb52821c88729..7311288279a51 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -245,6 +245,18 @@ export interface SecretRotationProps { * @default - no additional characters are explicitly excluded */ readonly excludeCharacters?: string; + + /** + * The VPC interface endpoint to use for the Secrets Manager API + * + * If you enable private DNS hostnames for your VPC private endpoint (the default), you don't + * need to specify an endpoint. The standard Secrets Manager DNS hostname the Secrets Manager + * CLI and SDKs use by default (https://secretsmanager..amazonaws.com) automatically + * resolves to your VPC endpoint. + * + * @default https://secretsmanager..amazonaws.com + */ + readonly endpoint?: ec2.IInterfaceVpcEndpoint; } /** @@ -272,7 +284,7 @@ export class SecretRotation extends CoreConstruct { props.target.connections.allowDefaultPortFrom(securityGroup); const parameters: { [key: string]: string } = { - endpoint: `https://secretsmanager.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`, + endpoint: `https://${props.endpoint ? `${props.endpoint.vpcEndpointId}.` : ''}secretsmanager.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`, functionName: rotationFunctionName, vpcSubnetIds: props.vpc.selectSubnets(props.vpcSubnets).subnetIds.join(','), vpcSecurityGroupIds: securityGroup.securityGroupId, diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 119b02ccc507d..5537c72b2c0d9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { FeatureFlags, Fn, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, FeatureFlags, Fn, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Construct } from 'constructs'; import { ResourcePolicy } from './policy'; @@ -373,7 +373,7 @@ export class Secret extends SecretBase { service: 'secretsmanager', resource: 'secret', resourceName: this.secretName + '*', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } }(scope, id); @@ -397,7 +397,7 @@ export class Secret extends SecretBase { service: 'secretsmanager', resource: 'secret', resourceName: secretName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } }(scope, id); @@ -467,15 +467,15 @@ export class Secret extends SecretBase { replicaRegions: Lazy.any({ produce: () => this.replicaRegions }, { omitEmptyArray: true }), }); - if (props.removalPolicy) { - resource.applyRemovalPolicy(props.removalPolicy); - } + resource.applyRemovalPolicy(props.removalPolicy, { + default: RemovalPolicy.DESTROY, + }); this.secretArn = this.getResourceArnAttribute(resource.ref, { service: 'secretsmanager', resource: 'secret', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.encryptionKey = props.encryptionKey; @@ -604,8 +604,6 @@ export interface SecretAttachmentTargetProps { /** * Options to add a secret attachment to a secret. - * - * @deprecated use `secret.attach()` instead */ export interface AttachedSecretOptions { /** @@ -758,7 +756,7 @@ export interface SecretStringGenerator { /** Parses the secret name from the ARN. */ function parseSecretName(construct: IConstruct, secretArn: string) { - const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName; + const resourceName = Stack.of(construct).splitArn(secretArn, ArnFormat.COLON_RESOURCE_NAME).resourceName; if (resourceName) { // Can't operate on the token to remove the SecretsManager suffix, so just return the full secret name if (Token.isUnresolved(resourceName)) { @@ -784,7 +782,7 @@ function parseSecretName(construct: IConstruct, secretArn: string) { * explicit between the Secret and wherever the secretName might be used (i.e., using Tokens). */ function parseSecretNameForOwnedSecret(construct: Construct, secretArn: string, secretName?: string) { - const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName; + const resourceName = Stack.of(construct).splitArn(secretArn, ArnFormat.COLON_RESOURCE_NAME).resourceName; if (!resourceName) { throw new Error('invalid ARN format; no secret name provided'); } diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index 9cc6876067018..d71a1cac088e7 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", 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 index be2f63be0aa79..19c9a9d4c7f1e 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.hosted-rotation.expected.json @@ -5,7 +5,9 @@ "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {} - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "SecretSchedule18F2CB66": { "Type": "AWS::SecretsManager::RotationSchedule", diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.lambda-rotation.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.lambda-rotation.expected.json index 12ea99df10a85..f1d540cbd42a7 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.lambda-rotation.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.lambda-rotation.expected.json @@ -165,7 +165,9 @@ "Arn" ] } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "SecretSchedule18F2CB66": { "Type": "AWS::SecretsManager::RotationSchedule", diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json index 99fd65c02e2b4..6483de751a884 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json @@ -9,7 +9,9 @@ "Region": "eu-central-1" } ] - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret-name-parsed.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret-name-parsed.expected.json index b1b91a239d649..01e9e10d4eed9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret-name-parsed.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret-name-parsed.expected.json @@ -4,27 +4,35 @@ "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {} - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "NamedSecret7CD5422D": { "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {}, "Name": "namedSecret" - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "NamedSecretWithHyphen6DC9716A": { "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {}, "Name": "named-secret-1" - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "AReallyLongLogicalIThatWillBeTrimmedBeforeItsUsedInTheName83476586": { "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {} - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "CustomIntegVerificationSecretNameMatchesCustomResourceProviderRole4A6F8B2A": { "Type": "AWS::IAM::Role", diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json index 5411df31be1ba..7a69272a9550f 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json @@ -62,7 +62,9 @@ "Type": "AWS::SecretsManager::Secret", "Properties": { "GenerateSecretString": {} - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "User00B015A1": { "Type": "AWS::IAM::User", @@ -90,7 +92,9 @@ "GenerateStringKey": "password", "SecretStringTemplate": "{\"username\":\"user\"}" } - } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "OtherUser6093621C": { "Type": "AWS::IAM::User", @@ -124,4 +128,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts index 55c7388393619..974aa0c611c4d 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret-rotation.test.ts @@ -345,3 +345,37 @@ test('rotation function name does not exceed 64 chars', () => { }, }); }); + + +test('with interface vpc endpoint', () => { + // GIVEN + const endpoint = new ec2.InterfaceVpcEndpoint(stack, 'SecretsManagerEndpoint', { + service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, + vpc, + }); + + // WHEN + new secretsmanager.SecretRotation(stack, 'SecretRotation', { + application: secretsmanager.SecretRotationApplication.MYSQL_ROTATION_SINGLE_USER, + secret, + target, + vpc, + endpoint, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Parameters: { + endpoint: { + 'Fn::Join': ['', [ + 'https://', + { Ref: 'SecretsManagerEndpoint5E83C66B' }, + '.secretsmanager.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 7f154c77b5f7f..e2ddfb9700350 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -3,8 +3,8 @@ import { expect as assertExpect, ResourcePart } from '@aws-cdk/assert-internal'; 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 { testDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as secretsmanager from '../lib'; let app: cdk.App; @@ -657,7 +657,7 @@ describe('secretName', () => { }); }); -test('import by secretArn', () => { +testDeprecated('import by secretArn', () => { // GIVEN const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; @@ -678,10 +678,12 @@ test('import by secretArn throws if ARN is malformed', () => { const arnWithoutResourceName = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret'; // WHEN - expect(() => secretsmanager.Secret.fromSecretArn(stack, 'Secret1', arnWithoutResourceName)).toThrow(/invalid ARN format/); + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret1', { + secretPartialArn: arnWithoutResourceName, + })).toThrow(/invalid ARN format/); }); -test('import by secretArn supports secret ARNs without suffixes', () => { +testDeprecated('import by secretArn supports secret ARNs without suffixes', () => { // GIVEN const arnWithoutSecretsManagerSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; @@ -693,7 +695,7 @@ test('import by secretArn supports secret ARNs without suffixes', () => { expect(secret.secretName).toBe('MySecret'); }); -test('import by secretArn does not strip suffixes unless the suffix length is six', () => { +testDeprecated('import by secretArn does not strip suffixes unless the suffix length is six', () => { // GIVEN const arnWith5CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token'; const arnWith6CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token-f3gDy9'; @@ -712,7 +714,7 @@ test('import by secretArn supports tokens for ARNs', () => { const secretA = new secretsmanager.Secret(stackA, 'SecretA'); // WHEN - const secretB = secretsmanager.Secret.fromSecretArn(stackB, 'SecretB', secretA.secretArn); + const secretB = secretsmanager.Secret.fromSecretCompleteArn(stackB, 'SecretB', secretA.secretArn); new cdk.CfnOutput(stackB, 'secretBSecretName', { value: secretB.secretName }); // THEN @@ -723,7 +725,7 @@ test('import by secretArn supports tokens for ARNs', () => { }); }); -test('import by secretArn guesses at complete or partial ARN', () => { +testDeprecated('import by secretArn guesses at complete or partial ARN', () => { // GIVEN const secretArnWithSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; const secretArnWithoutSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; @@ -864,7 +866,7 @@ describe('fromSecretAttributes', () => { // WHEN const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { - secretArn, encryptionKey, + secretCompleteArn: secretArn, encryptionKey, }); // THEN @@ -876,7 +878,7 @@ describe('fromSecretAttributes', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); }); - test('throws if secretArn and either secretCompleteArn or secretPartialArn are provided', () => { + testDeprecated('throws if secretArn and either secretCompleteArn or secretPartialArn are provided', () => { const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; const error = /cannot use `secretArn` with `secretCompleteArn` or `secretPartialArn`/; @@ -922,7 +924,7 @@ describe('fromSecretAttributes', () => { }); }); -test('import by secret name', () => { +testDeprecated('import by secret name', () => { // GIVEN const secretName = 'MySecret'; @@ -937,7 +939,7 @@ test('import by secret name', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:password::}}`); }); -test('import by secret name with grants', () => { +testDeprecated('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'); @@ -1135,7 +1137,7 @@ test('equivalence of SecretValue and Secret.fromSecretAttributes', () => { 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 imported = secretsmanager.Secret.fromSecretAttributes(stack, 'Imported', { secretCompleteArn: secretArn }).secretValueFromJson('password'); const value = cdk.SecretValue.secretsManager(secretArn, { jsonField: 'password' }); // THEN diff --git a/packages/@aws-cdk/aws-securityhub/README.md b/packages/@aws-cdk/aws-securityhub/README.md index 831f2af57d18b..c4b1bebcf6e6c 100644 --- a/packages/@aws-cdk/aws-securityhub/README.md +++ b/packages/@aws-cdk/aws-securityhub/README.md @@ -15,6 +15,6 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. -```ts +```ts nofixture import * as securityhub from '@aws-cdk/aws-securityhub'; ``` diff --git a/packages/@aws-cdk/aws-securityhub/package.json b/packages/@aws-cdk/aws-securityhub/package.json index b4102e4918385..bb36e474a4b74 100644 --- a/packages/@aws-cdk/aws-securityhub/package.json +++ b/packages/@aws-cdk/aws-securityhub/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index 4bb6885c601eb..bbc82e13e2d7b 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -30,6 +30,8 @@ enables organizations to create and manage catalogs of products for their end us - [Granting access to a portfolio](#granting-access-to-a-portfolio) - [Sharing a portfolio with another AWS account](#sharing-a-portfolio-with-another-aws-account) - [Product](#product) + - [Creating a product from a local asset](#creating-a-product-from-local-asset) + - [Creating a product from a stack](#creating-a-product-from-a-stack) - [Adding a product to a portfolio](#adding-a-product-to-a-portfolio) - [TagOptions](#tag-options) - [Constraints](#constraints) @@ -125,10 +127,12 @@ const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromUrl( 'https://raw.githubusercontent.com/awslabs/aws-cloudformation-templates/master/aws/services/ServiceCatalog/Product.yaml'), }, - ] + ], }); ``` +### Creating a product from a local asset + A `CloudFormationProduct` can also be created using a Cloudformation template from an Asset. Assets are files that are uploaded to an S3 Bucket before deployment. `CloudFormationTemplate.fromAsset` can be utilized to create a Product by passing the path to a local template file on your disk: @@ -149,7 +153,38 @@ const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', productVersionName: "v2", cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'development-environment.template.json')), }, - ] + ], +}); +``` + +### Creating a product from a stack + +You can define a service catalog `CloudFormationProduct` entirely within CDK using a service catalog `ProductStack`. +A separate child stack for your product is created and you can add resources like you would for any other CDK stack, +such as an S3 Bucket, IAM roles, and EC2 instances. This stack is passed in as a product version to your +product. This will not create a separate stack during deployment. + +```ts +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; + +class S3BucketProduct extends servicecatalog.ProductStack { + constructor(scope: cdk.Construct, id: string) { + super(scope, id); + + new s3.Bucket(this, 'BucketProduct'); + } +} + +const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', { + productName: "My Product", + owner: "Product Owner", + productVersions: [ + { + productVersionName: "v1", + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new S3BucketProduct(this, 'S3BucketProduct')), + }, + ], }); ``` @@ -272,8 +307,36 @@ const launchRole = new iam.Role(this, 'LaunchRole', { portfolio.setLaunchRole(product, launchRole); ``` +You can also set the launch role using just the name of a role which is locally deployed in end user accounts. +This is useful for when roles and users are separately managed outside of the CDK. +The given role must exist in both the account that creates the launch role constraint, +as well as in any end user accounts that wish to provision a product with the launch role. + +You can do this by passing in the role with an explicitly set name: + +```ts fixture=portfolio-product +import * as iam from '@aws-cdk/aws-iam'; + +const launchRole = new iam.Role(this, 'LaunchRole', { + roleName: 'MyRole', + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), +}); + +portfolio.setLocalLaunchRole(product, launchRole); +``` + +Or you can simply pass in a role name and CDK will create a role with that name that trusts service catalog in the account: + +```ts fixture=portfolio-product +import * as iam from '@aws-cdk/aws-iam'; + +const roleName = 'MyRole'; + +const launchRole: iam.IRole = portfolio.setLocalLaunchRoleName(product, roleName); +``` + See [Launch Constraint](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints-launch.html) documentation -to understand permissions roles need. +to understand the permissions roles need. ### Deploy with StackSets diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts b/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts index 670ff1bd4de1f..4086db6655fda 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts @@ -1,4 +1,6 @@ import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import { hashValues } from './private/util'; +import { ProductStack } from './product-stack'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -25,6 +27,13 @@ export abstract class CloudFormationTemplate { return new CloudFormationAssetTemplate(path, options); } + /** + * Creates a product with the resources defined in the given product stack. + */ + public static fromProductStack(productStack: ProductStack): CloudFormationTemplate { + return new CloudFormationProductStackTemplate(productStack); + } + /** * Called when the product is initialized to allow this object to bind * to the stack, add resources and have fun. @@ -76,7 +85,7 @@ class CloudFormationAssetTemplate extends CloudFormationTemplate { public bind(scope: Construct): CloudFormationTemplateConfig { // If the same AssetCode is used multiple times, retain only the first instantiation. if (!this.asset) { - this.asset = new s3_assets.Asset(scope, 'Template', { + this.asset = new s3_assets.Asset(scope, `Template${hashValues(this.path)}`, { path: this.path, ...this.options, }); @@ -87,3 +96,21 @@ class CloudFormationAssetTemplate extends CloudFormationTemplate { }; } } + +/** + * Template from a CDK defined product stack. + */ +class CloudFormationProductStackTemplate extends CloudFormationTemplate { + /** + * @param stack A service catalog product stack. + */ + constructor(public readonly productStack: ProductStack) { + super(); + } + + public bind(_scope: Construct): CloudFormationTemplateConfig { + return { + httpUrl: this.productStack._getTemplateUrl(), + }; + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/index.ts b/packages/@aws-cdk/aws-servicecatalog/lib/index.ts index 8a7b0f0ff9ac6..cc26e880fdd2e 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/index.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/index.ts @@ -3,6 +3,7 @@ export * from './constraints'; export * from './cloudformation-template'; export * from './portfolio'; export * from './product'; +export * from './product-stack'; export * from './tag-options'; // AWS::ServiceCatalog CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 3056a48e19777..36d267d022519 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -112,6 +112,9 @@ export interface IPortfolio extends cdk.IResource { /** * Force users to assume a certain role when launching a product. + * This sets the launch role using the role arn which is tied to the account this role exists in. + * This is useful if you will be provisioning products from the account where this role exists. + * If you intend to share the portfolio across accounts, use a local launch role. * * @param product A service catalog product. * @param launchRole The IAM role a user must assume when provisioning the product. @@ -120,7 +123,30 @@ export interface IPortfolio extends cdk.IResource { setLaunchRole(product: IProduct, launchRole: iam.IRole, options?: CommonConstraintOptions): void; /** - * Configure deployment options using AWS Cloudformaiton StackSets + * Force users to assume a certain role when launching a product. + * The role will be referenced by name in the local account instead of a static role arn. + * A role with this name will automatically be created and assumable by Service Catalog in this account. + * This is useful when sharing the portfolio with multiple accounts. + * + * @param product A service catalog product. + * @param launchRoleName The name of the IAM role a user must assume when provisioning the product. A role with this name must exist in the account where the portolio is created and the accounts it is shared with. + * @param options options for the constraint. + */ + setLocalLaunchRoleName(product: IProduct, launchRoleName: string, options?: CommonConstraintOptions): iam.IRole; + + /** + * Force users to assume a certain role when launching a product. + * The role name will be referenced by in the local account and must be set explicitly. + * This is useful when sharing the portfolio with multiple accounts. + * + * @param product A service catalog product. + * @param launchRole The IAM role a user must assume when provisioning the product. A role with this name must exist in the account where the portolio is created and the accounts it is shared with. The role name must be set explicitly. + * @param options options for the constraint. + */ + setLocalLaunchRole(product: IProduct, launchRole: iam.IRole, options?: CommonConstraintOptions): void; + + /** + * Configure deployment options using AWS Cloudformation StackSets * * @param product A service catalog product. * @param options Configuration options for the constraint. @@ -179,6 +205,20 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.setLaunchRole(this, product, launchRole, options); } + public setLocalLaunchRoleName(product: IProduct, launchRoleName: string, options: CommonConstraintOptions = {}): iam.IRole { + const launchRole: iam.IRole = new iam.Role(this, `LaunchRole${launchRoleName}`, { + roleName: launchRoleName, + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), + }); + AssociationManager.setLocalLaunchRoleName(this, product, launchRole.roleName, options); + return launchRole; + } + + public setLocalLaunchRole(product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions = {}): void { + InputValidator.validateRoleNameSetForLocalLaunchRole(launchRole); + AssociationManager.setLocalLaunchRoleName(this, product, launchRole.roleName, options); + } + public deployWithStackSets(product: IProduct, options: StackSetsConstraintOptions) { AssociationManager.deployWithStackSets(this, product, options); } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index bf5a68a8e70d3..b92fb2483ad54 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -100,27 +100,15 @@ export class AssociationManager { } public static setLaunchRole(portfolio: IPortfolio, product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions): void { - const association = this.associateProductWithPortfolio(portfolio, product, options); - // Check if a stackset deployment constraint has already been configured. - if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { - throw new Error(`Cannot set launch role when a StackSet rule is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); - } - - const constructId = this.launchRoleConstraintLogicalId(association.associationKey); - if (!portfolio.node.tryFindChild(constructId)) { - const constraint = new CfnLaunchRoleConstraint(portfolio as unknown as cdk.Resource, constructId, { - acceptLanguage: options.messageLanguage, - description: options.description, - portfolioId: portfolio.portfolioId, - productId: product.productId, - roleArn: launchRole.roleArn, - }); + this.setLaunchRoleConstraint(portfolio, product, options, { + roleArn: launchRole.roleArn, + }); + } - // Add dependsOn to force proper order in deployment. - constraint.addDependsOn(association.cfnPortfolioProductAssociation); - } else { - throw new Error(`Cannot set multiple launch roles for association ${this.prettyPrintAssociation(portfolio, product)}`); - } + public static setLocalLaunchRoleName(portfolio: IPortfolio, product: IProduct, launchRoleName: string, options: CommonConstraintOptions): void { + this.setLaunchRoleConstraint(portfolio, product, options, { + localRoleName: launchRoleName, + }); } public static deployWithStackSets(portfolio: IPortfolio, product: IProduct, options: StackSetsConstraintOptions) { @@ -179,6 +167,34 @@ export class AssociationManager { }; } + private static setLaunchRoleConstraint( + portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions, + roleOptions: LaunchRoleConstraintRoleOptions, + ): void { + const association = this.associateProductWithPortfolio(portfolio, product, options); + // Check if a stackset deployment constraint has already been configured. + if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { + throw new Error(`Cannot set launch role when a StackSet rule is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); + } + + const constructId = this.launchRoleConstraintLogicalId(association.associationKey); + if (!portfolio.node.tryFindChild(constructId)) { + const constraint = new CfnLaunchRoleConstraint(portfolio as unknown as cdk.Resource, constructId, { + acceptLanguage: options.messageLanguage, + description: options.description, + portfolioId: portfolio.portfolioId, + productId: product.productId, + roleArn: roleOptions.roleArn, + localRoleName: roleOptions.localRoleName, + }); + + // Add dependsOn to force proper order in deployment. + constraint.addDependsOn(association.cfnPortfolioProductAssociation); + } else { + throw new Error(`Cannot set multiple launch roles for association ${this.prettyPrintAssociation(portfolio, product)}`); + } + } + private static stackSetConstraintLogicalId(associationKey: string): string { return `StackSetConstraint${associationKey}`; } @@ -213,3 +229,14 @@ export class AssociationManager { }; } +interface LaunchRoleArnOption { + readonly roleArn: string, + readonly localRoleName?: never, +} + +interface LaunchRoleNameOption { + readonly localRoleName: string, + readonly roleArn?: never, +} + +type LaunchRoleConstraintRoleOptions = LaunchRoleArnOption | LaunchRoleNameOption; diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts new file mode 100644 index 0000000000000..4840a7e756fe5 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts @@ -0,0 +1,34 @@ +import * as cdk from '@aws-cdk/core'; + +/** + * Deployment environment for an AWS Service Catalog product stack. + * + * Interoperates with the StackSynthesizer of the parent stack. + */ +export class ProductStackSynthesizer extends cdk.StackSynthesizer { + private stack?: cdk.Stack; + + public bind(stack: cdk.Stack): void { + if (this.stack !== undefined) { + throw new Error('A Stack Synthesizer can only be bound once, create a new instance to use with a different Stack'); + } + this.stack = stack; + } + + public addFileAsset(_asset: cdk.FileAssetSource): cdk.FileAssetLocation { + throw new Error('Service Catalog Product Stacks cannot use Assets'); + } + + public addDockerImageAsset(_asset: cdk.DockerImageAssetSource): cdk.DockerImageAssetLocation { + throw new Error('Service Catalog Product Stacks cannot use Assets'); + } + + public synthesize(session: cdk.ISynthesisSession): void { + if (!this.stack) { + throw new Error('You must call bindStack() first'); + } + // Synthesize the template, but don't emit as a cloud assembly artifact. + // It will be registered as an S3 asset of its parent instead. + this.synthesizeStackTemplate(this.stack, session); + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts index 3beaa42552eff..cd70006fbe373 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; /** @@ -36,6 +37,17 @@ export class InputValidator { this.validateRegex(resourceName, inputName, /^[\w\d.%+\-]+@[a-z\d.\-]+\.[a-z]{2,4}$/i, inputString); } + /** + * Validates that a role being used as a local launch role has the role name set + */ + public static validateRoleNameSetForLocalLaunchRole(role: iam.IRole): void { + if (role.node.defaultChild) { + if (cdk.Token.isUnresolved((role.node.defaultChild as iam.CfnRole).roleName)) { + throw new Error(`Role ${role.node.id} used for Local Launch Role must have roleName explicitly set`); + } + } + } + private static truncateString(string: string, maxLength: number): string { if (string.length > maxLength) { return string.substring(0, maxLength) + '[truncated]'; diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts new file mode 100644 index 0000000000000..b96224a8b2c60 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts @@ -0,0 +1,77 @@ +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import { ProductStackSynthesizer } from './private/product-stack-synthesizer'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from 'constructs'; + +/** + * A Service Catalog product stack, which is similar in form to a Cloudformation nested stack. + * You can add the resources to this stack that you want to define for your service catalog product. + * + * This stack will not be treated as an independent deployment + * artifact (won't be listed in "cdk list" or deployable through "cdk deploy"), + * but rather only synthesized as a template and uploaded as an asset to S3. + * + */ +export class ProductStack extends cdk.Stack { + public readonly templateFile: string; + private _templateUrl?: string; + private _parentStack: cdk.Stack; + + constructor(scope: Construct, id: string) { + super(scope, id, { + synthesizer: new ProductStackSynthesizer(), + }); + + this._parentStack = findParentStack(scope); + + // this is the file name of the synthesized template file within the cloud assembly + this.templateFile = `${cdk.Names.uniqueId(this)}.product.template.json`; + } + + /** + * Fetch the template URL. + * + * @internal + */ + public _getTemplateUrl(): string { + return cdk.Lazy.uncachedString({ produce: () => this._templateUrl }); + } + + /** + * Synthesize the product stack template, overrides the `super` class method. + * + * Defines an asset at the parent stack which represents the template of this + * product stack. + * + * @internal + */ + public _synthesizeTemplate(session: cdk.ISynthesisSession): void { + const cfn = JSON.stringify(this._toCloudFormation(), undefined, 2); + const templateHash = crypto.createHash('sha256').update(cfn).digest('hex'); + + this._templateUrl = this._parentStack.synthesizer.addFileAsset({ + packaging: cdk.FileAssetPackaging.FILE, + sourceHash: templateHash, + fileName: this.templateFile, + }).httpUrl; + + fs.writeFileSync(path.join(session.assembly.outdir, this.templateFile), cfn); + } +} + +/** + * Validates the scope for a product stack, which must be defined within the scope of another `Stack`. + */ +function findParentStack(scope: Construct): cdk.Stack { + try { + const parentStack = cdk.Stack.of(scope); + return parentStack as cdk.Stack; + } catch (e) { + throw new Error('Product stacks must be defined within scope of another non-product stack'); + } +} diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index de3bfcfb667a5..23561d640ded2 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -77,7 +77,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", @@ -104,7 +104,8 @@ "resource-attribute:@aws-cdk/aws-servicecatalog.CloudFormationProduct.cloudFormationProductProvisioningArtifactNames", "props-physical-name:@aws-cdk/aws-servicecatalog.CloudFormationProductProps", "resource-attribute:@aws-cdk/aws-servicecatalog.Portfolio.portfolioName", - "props-physical-name:@aws-cdk/aws-servicecatalog.PortfolioProps" + "props-physical-name:@aws-cdk/aws-servicecatalog.PortfolioProps", + "props-physical-name:@aws-cdk/aws-servicecatalog.ProductStack" ] }, "maturity": "experimental", diff --git a/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture b/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture index d8925e6645aa7..3029872ea1f0d 100644 --- a/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture +++ b/packages/@aws-cdk/aws-servicecatalog/rosetta/basic-portfolio.ts-fixture @@ -1,9 +1,9 @@ // Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; +import * as cdk from '@aws-cdk/core'; import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; -class Fixture extends Stack { - constructor(scope: Construct, id: string) { +class Fixture extends cdk.Stack { + constructor(scope: cdk.Construct, id: string) { super(scope, id); const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.expected.json index 91767f1182eb6..f54d640e1d0ca 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.expected.json @@ -62,6 +62,159 @@ ] } } + }, + { + "DisableTemplateValidation": false, + "Info": { + "LoadTemplateFromURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5S3Bucket85C3FF42" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5S3VersionKey34A02667" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5S3VersionKey34A02667" + } + ] + } + ] + } + ] + ] + } + } + }, + { + "DisableTemplateValidation": false, + "Info": { + "LoadTemplateFromURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3BucketB4751C98" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3VersionKeyEB38C6F9" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3VersionKeyEB38C6F9" + } + ] + } + ] + } + ] + ] + } + } + }, + { + "DisableTemplateValidation": false, + "Info": { + "LoadTemplateFromURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3BucketB4751C98" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3VersionKeyEB38C6F9" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3VersionKeyEB38C6F9" + } + ] + } + ] + } + ] + ] + } + } } ] } @@ -79,6 +232,30 @@ "AssetParametersb59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09eArtifactHashB9EF04B2": { "Type": "String", "Description": "Artifact hash for asset \"b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e\"" + }, + "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5S3Bucket85C3FF42": { + "Type": "String", + "Description": "S3 bucket for asset \"6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5\"" + }, + "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5S3VersionKey34A02667": { + "Type": "String", + "Description": "S3 key for asset version \"6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5\"" + }, + "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5ArtifactHashDC26AFAC": { + "Type": "String", + "Description": "Artifact hash for asset \"6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5\"" + }, + "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3BucketB4751C98": { + "Type": "String", + "Description": "S3 bucket for asset \"dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f\"" + }, + "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3VersionKeyEB38C6F9": { + "Type": "String", + "Description": "S3 key for asset version \"dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f\"" + }, + "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fArtifactHash5C1F9228": { + "Type": "String", + "Description": "Artifact hash for asset \"dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts index 578f85e5e1d68..7a88c98a466d1 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts @@ -1,10 +1,19 @@ import * as path from 'path'; +import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as servicecatalog from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'integ-servicecatalog-product'); +class TestProductStack extends servicecatalog.ProductStack { + constructor(scope: any, id: string) { + super(scope, id); + + new sns.Topic(this, 'TopicProduct'); + } +} + new servicecatalog.CloudFormationProduct(stack, 'TestProduct', { productName: 'testProduct', owner: 'testOwner', @@ -15,7 +24,16 @@ new servicecatalog.CloudFormationProduct(stack, 'TestProduct', { 'https://awsdocs.s3.amazonaws.com/servicecatalog/development-environment.template'), }, { - cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'development-environment.template.json')), + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product1.template.json')), + }, + { + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product2.template.json')), + }, + { + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'SNSTopicProduct1')), + }, + { + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'SNSTopicProduct2')), }, ], }); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 8f9a27a96a940..43a283c157f80 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -568,6 +568,7 @@ describe('portfolio associations and product constraints', () => { assumedBy: new iam.AccountRootPrincipal(), }); launchRole = new iam.Role(stack, 'LaunchRole', { + roleName: 'LaunchRole', assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), }); }), @@ -591,6 +592,59 @@ describe('portfolio associations and product constraints', () => { }); }), + test('set a launch role constraint using local role name', () => { + portfolio.addProduct(product); + + portfolio.setLocalLaunchRoleName(product, 'LocalLaunchRole', { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: { Ref: 'MyPortfolioLaunchRoleLocalLaunchRoleB2E6E22A' }, + }); + }), + + test('set a launch role constraint using local role', () => { + portfolio.addProduct(product); + + portfolio.setLocalLaunchRole(product, launchRole, { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: { Ref: 'LaunchRole2CFB2E44' }, + }); + }), + + test('set a launch role constraint using imported local role', () => { + portfolio.addProduct(product); + + const importedLaunchRole = iam.Role.fromRoleArn(portfolio.stack, 'ImportedLaunchRole', 'arn:aws:iam::123456789012:role/ImportedLaunchRole'); + + portfolio.setLocalLaunchRole(product, importedLaunchRole, { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: 'ImportedLaunchRole', + }); + }), + test('set launch role constraint still adds without explicit association', () => { portfolio.setLaunchRole(product, launchRole); @@ -606,7 +660,57 @@ describe('portfolio associations and product constraints', () => { expect(() => { portfolio.setLaunchRole(product, otherLaunchRole); - }).toThrowError(/Cannot set multiple launch roles for association/); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('local launch role must have roleName explicitly set', () => { + const otherLaunchRole = new iam.Role(stack, 'otherLaunchRole', { + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), + }); + + expect(() => { + portfolio.setLocalLaunchRole(product, otherLaunchRole); + }).toThrow(/Role otherLaunchRole used for Local Launch Role must have roleName explicitly set/); + }), + + test('fails to add multiple set launch roles - local launch role first', () => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + + expect(() => { + portfolio.setLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set local launch roles - local launch role first', () => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + + expect(() => { + portfolio.setLocalLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set local launch roles - local launch role name first', () => { + portfolio.setLocalLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set launch roles - local launch role second', () => { + portfolio.setLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set launch roles - local launch role second', () => { + portfolio.setLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + }).toThrow(/Cannot set multiple launch roles for association/); }), test('fails to set launch role if stackset rule is already defined', () => { diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts new file mode 100644 index 0000000000000..e024421d724dc --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts @@ -0,0 +1,88 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as servicecatalog from '../lib'; + +/* eslint-disable quote-props */ +describe('ProductStack', () => { + test('fails to add asset to a product stack', () => { + // GIVEN + const app = new cdk.App(); + const mainStack = new cdk.Stack(app, 'MyStack'); + const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack'); + + // THEN + expect(() => { + new s3_assets.Asset(productStack, 'testAsset', { + path: path.join(__dirname, 'product1.template.json'), + }); + }).toThrow(/Service Catalog Product Stacks cannot use Assets/); + }), + + test('fails if defined at root', () => { + // GIVEN + const app = new cdk.App(); + + // THEN + expect(() => { + new servicecatalog.ProductStack(app, 'ProductStack'); + }).toThrow(/must be defined within scope of another non-product stack/); + }), + + test('fails if defined without a parent stack', () => { + // GIVEN + const app = new cdk.App(); + const group = new cdk.Construct(app, 'group'); + + // THEN + expect(() => { + new servicecatalog.ProductStack(group, 'ProductStack'); + }).toThrow(/must be defined within scope of another non-product stack/); + }), + + test('can be defined as a direct child or an indirect child of a Stack', () => { + // GIVEN + const parent = new cdk.Stack(); + + // THEN + expect(() => { + new servicecatalog.ProductStack(parent, 'direct'); + }).not.toThrow(); + }); + + test('product stack is not synthesized as a stack artifact into the assembly', () => { + // GIVEN + const app = new cdk.App(); + const parentStack = new cdk.Stack(app, 'ParentStack'); + new servicecatalog.ProductStack(parentStack, 'ProductStack'); + + // WHEN + const assembly = app.synth(); + + // THEN + expect(assembly.artifacts.length).toEqual(2); + }); + + test('the template of the product stack is synthesized into the cloud assembly', () => { + // GIVEN + const app = new cdk.App(); + const parent = new cdk.Stack(app, 'ParentStack'); + const productStack = new servicecatalog.ProductStack(parent, 'ProductStack'); + new sns.Topic(productStack, 'SNSTopicProduct'); + + // WHEN + const assembly = app.synth(); + + // THEN + const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, productStack.templateFile), 'utf-8')); + expect(template).toEqual({ + Resources: { + SNSTopicProduct20605D98: { + Type: 'AWS::SNS::Topic', + }, + }, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/product.test.ts index 57f4e50bda0c0..f399a79dcdb83 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.test.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import { Match, Template } from '@aws-cdk/assertions'; +import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as servicecatalog from '../lib'; @@ -70,7 +71,7 @@ describe('Product', () => { owner: 'testOwner', productVersions: [ { - cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'development-environment.template.json')), + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product1.template.json')), }, ], }); @@ -80,6 +81,114 @@ describe('Product', () => { expect(synthesized.assets.length).toEqual(1); }), + test('multiple product versions from Assets', () => { + new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { + productName: 'testProduct', + owner: 'testOwner', + productVersions: [ + { + productVersionName: 'v1', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product1.template.json')), + }, + { + productVersionName: 'v2', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product2.template.json')), + }, + ], + }); + + const assembly = app.synth(); + const synthesized = assembly.stacks[0]; + expect(synthesized.assets.length).toEqual(2); + }), + + test('product test from product stack', () => { + const productStack = new servicecatalog.ProductStack(stack, 'ProductStack'); + + new sns.Topic(productStack, 'SNSTopicProductStack'); + + new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { + productName: 'testProduct', + owner: 'testOwner', + productVersions: [ + { + productVersionName: 'v1', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(productStack), + }, + ], + }); + + const assembly = app.synth(); + expect(assembly.artifacts.length).toEqual(2); + expect(assembly.stacks[0].assets.length).toBe(1); + expect(assembly.stacks[0].assets[0].path).toEqual('ProductStack.product.template.json'); + }), + + test('multiple product versions from product stack', () => { + const productStackVersion1 = new servicecatalog.ProductStack(stack, 'ProductStackV1'); + const productStackVersion2 = new servicecatalog.ProductStack(stack, 'ProductStackV2'); + + new sns.Topic(productStackVersion1, 'SNSTopicProductStack1'); + + new sns.Topic(productStackVersion2, 'SNSTopicProductStack2', { + displayName: 'a test', + }); + + new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { + productName: 'testProduct', + owner: 'testOwner', + productVersions: [ + { + productVersionName: 'v1', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(productStackVersion1), + }, + { + productVersionName: 'v2', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(productStackVersion2), + }, + ], + }); + + const assembly = app.synth(); + + expect(assembly.stacks[0].assets.length).toBe(2); + expect(assembly.stacks[0].assets[0].path).toEqual('ProductStackV1.product.template.json'); + expect(assembly.stacks[0].assets[1].path).toEqual('ProductStackV2.product.template.json'); + }), + + test('identical product versions from product stack creates one asset', () => { + class TestProductStack extends servicecatalog.ProductStack { + constructor(scope: any, id: string) { + super(scope, id); + + new sns.Topic(this, 'TopicProduct'); + } + } + + new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { + productName: 'testProduct', + owner: 'testOwner', + productVersions: [ + { + productVersionName: 'v1', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'v1')), + }, + { + productVersionName: 'v2', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'v2')), + }, + { + productVersionName: 'v3', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'v3')), + }, + ], + }); + + const assembly = app.synth(); + + expect(assembly.stacks[0].assets.length).toBe(1); + }), + test('product test from multiple sources', () => { new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { productName: 'testProduct', @@ -95,7 +204,7 @@ describe('Product', () => { }, { productVersionName: 'v3', - cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'development-environment.template.json')), + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromAsset(path.join(__dirname, 'product1.template.json')), }, ], }); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/development-environment.template.json b/packages/@aws-cdk/aws-servicecatalog/test/product1.template.json similarity index 100% rename from packages/@aws-cdk/aws-servicecatalog/test/development-environment.template.json rename to packages/@aws-cdk/aws-servicecatalog/test/product1.template.json diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product2.template.json b/packages/@aws-cdk/aws-servicecatalog/test/product2.template.json new file mode 100644 index 0000000000000..9785ab36f253f --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product2.template.json @@ -0,0 +1,98 @@ +{ + "AWSTemplateFormatVersion" : "2010-09-09", + + "Description" : "AWS Service Catalog sample template. Creates an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters" : { + "KeyName": { + "Description" : "Name of an existing EC2 key pair for SSH access to the EC2 instance.", + "Type": "AWS::EC2::KeyPair::KeyName" + }, + + "InstanceType" : { + "Description" : "EC2 instance type.", + "Type" : "String", + "Default" : "t2.micro", + "AllowedValues" : [ "t2.micro", "t2.small", "t2.medium"] + }, + + "SSHLocation" : { + "Description" : "The IP address range that can SSH to the EC2 instance.", + "Type": "String", + "MinLength": "9", + "MaxLength": "18", + "Default": "0.0.0.0/0", + "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", + "ConstraintDescription": "Must be a valid IP CIDR range of the form x.x.x.x/x." + } + }, + + "Metadata" : { + "AWS::CloudFormation::Interface" : { + "ParameterGroups" : [{ + "Label" : {"default": "Instance configuration"}, + "Parameters" : ["InstanceType"] + },{ + "Label" : {"default": "Security configuration"}, + "Parameters" : ["KeyName", "SSHLocation"] + }], + "ParameterLabels" : { + "InstanceType": {"default": "Server size:"}, + "KeyName": {"default": "Key pair:"}, + "SSHLocation": {"default": "CIDR range:"} + } + } + }, + + "Mappings" : { + "AWSRegionArch2AMI" : { + "us-east-1" : { "HVM64" : "ami-08842d60" }, + "us-west-2" : { "HVM64" : "ami-8786c6b7" }, + "us-west-1" : { "HVM64" : "ami-cfa8a18a" }, + "eu-west-1" : { "HVM64" : "ami-748e2903" }, + "ap-southeast-1" : { "HVM64" : "ami-d6e1c584" }, + "ap-northeast-1" : { "HVM64" : "ami-35072834" }, + "ap-southeast-2" : { "HVM64" : "ami-fd4724c7" }, + "sa-east-1" : { "HVM64" : "ami-956cc688" }, + "cn-north-1" : { "HVM64" : "ami-ac57c595" }, + "eu-central-1" : { "HVM64" : "ami-b43503a9" } + } + + }, + + "Resources" : { + "EC2Instance" : { + "Type" : "AWS::EC2::Instance", + "Properties" : { + "InstanceType" : { "Ref" : "InstanceType" }, + "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ], + "KeyName" : { "Ref" : "KeyName" }, + "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, "HVM64" ] } + } + }, + + "InstanceSecurityGroup" : { + "Type" : "AWS::EC2::SecurityGroup", + "Properties" : { + "GroupDescription" : "Enable SSH access via port 22", + "SecurityGroupIngress" : [ { + "IpProtocol" : "tcp", + "FromPort" : "22", + "ToPort" : "22", + "CidrIp" : { "Ref" : "SSHLocation"} + } ] + } + } + }, + + "Outputs" : { + "PublicDNSName" : { + "Description" : "Public DNS name of the new EC2 instance", + "Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicDnsName" ] } + }, + "PublicIPAddress" : { + "Description" : "Public IP address of the new EC2 instance", + "Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicIp" ] } + } + } +} diff --git a/packages/@aws-cdk/aws-servicecatalogappregistry/package.json b/packages/@aws-cdk/aws-servicecatalogappregistry/package.json index 440022bde6091..a3293914c1027 100644 --- a/packages/@aws-cdk/aws-servicecatalogappregistry/package.json +++ b/packages/@aws-cdk/aws-servicecatalogappregistry/package.json @@ -81,7 +81,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index 4169aaadae0ca..95acef90ab7cf 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -80,8 +80,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-ses-actions/package.json b/packages/@aws-cdk/aws-ses-actions/package.json index 5f5be5276102c..cee511a94f85e 100644 --- a/packages/@aws-cdk/aws-ses-actions/package.json +++ b/packages/@aws-cdk/aws-ses-actions/package.json @@ -70,8 +70,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index 9948c950af6f6..4fe47750df28b 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -77,9 +77,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/aws-lambda": "^8.10.85", + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-signer/package.json b/packages/@aws-cdk/aws-signer/package.json index 84489e43c845b..dfca263103745 100644 --- a/packages/@aws-cdk/aws-signer/package.json +++ b/packages/@aws-cdk/aws-signer/package.json @@ -78,7 +78,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-sns-subscriptions/README.md b/packages/@aws-cdk/aws-sns-subscriptions/README.md index ca06d08689736..048ca4dd9fdda 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/README.md +++ b/packages/@aws-cdk/aws-sns-subscriptions/README.md @@ -27,8 +27,6 @@ Subscriptions to Amazon SQS and AWS Lambda can be added on topics across regions Create an Amazon SNS Topic to add subscriptions. ```ts -import * as sns from '@aws-cdk/aws-sns'; - const myTopic = new sns.Topic(this, 'MyTopic'); ``` @@ -37,7 +35,7 @@ const myTopic = new sns.Topic(this, 'MyTopic'); Add an HTTP or HTTPS Subscription to your topic: ```ts -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; +const myTopic = new sns.Topic(this, 'MyTopic'); myTopic.addSubscription(new subscriptions.UrlSubscription('https://foobar.com/')); ``` @@ -48,8 +46,10 @@ parameter](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parame following code defines a CloudFormation parameter and uses it in a URL subscription. ```ts +const myTopic = new sns.Topic(this, 'MyTopic'); const url = new CfnParameter(this, 'url-param'); -myTopic.addSubscription(new subscriptions.UrlSubscription(url.valueAsString())); + +myTopic.addSubscription(new subscriptions.UrlSubscription(url.valueAsString)); ``` ### Amazon SQS @@ -57,12 +57,10 @@ myTopic.addSubscription(new subscriptions.UrlSubscription(url.valueAsString())); Subscribe a queue to your topic: ```ts -import * as sqs from '@aws-cdk/aws-sqs'; -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; - const myQueue = new sqs.Queue(this, 'MyQueue'); +const myTopic = new sns.Topic(this, 'MyTopic'); -myTopic.addSubscription(new subscriptions.SqsSubscription(queue)); +myTopic.addSubscription(new subscriptions.SqsSubscription(myQueue)); ``` KMS key permissions will automatically be granted to SNS when a subscription is made to @@ -77,14 +75,9 @@ Subscribe an AWS Lambda function to your topic: ```ts import * as lambda from '@aws-cdk/aws-lambda'; -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; - -const myFunction = new lambda.Function(this, 'Echo', { - handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_12_X, - code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`) -}); +const myTopic = new sns.Topic(this, 'myTopic'); +declare const myFunction: lambda.Function; myTopic.addSubscription(new subscriptions.LambdaSubscription(myFunction)); ``` @@ -93,8 +86,7 @@ myTopic.addSubscription(new subscriptions.LambdaSubscription(myFunction)); Subscribe an email address to your topic: ```ts -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; - +const myTopic = new sns.Topic(this, 'MyTopic'); myTopic.addSubscription(new subscriptions.EmailSubscription('foo@bar.com')); ``` @@ -104,8 +96,10 @@ parameter](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parame following code defines a CloudFormation parameter and uses it in an email subscription. ```ts +const myTopic = new sns.Topic(this, 'Topic'); const emailAddress = new CfnParameter(this, 'email-param'); -myTopic.addSubscription(new subscriptions.EmailSubscription(emailAddress.valueAsString())); + +myTopic.addSubscription(new subscriptions.EmailSubscription(emailAddress.valueAsString)); ``` Note that email subscriptions require confirmation by visiting the link sent to the @@ -116,7 +110,7 @@ email address. Subscribe an sms number to your topic: ```ts -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; +const myTopic = new sns.Topic(this, 'Topic'); myTopic.addSubscription(new subscriptions.SmsSubscription('+15551231234')); ``` @@ -127,6 +121,8 @@ parameter](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parame following code defines a CloudFormation parameter and uses it in an sms subscription. ```ts +const myTopic = new sns.Topic(this, 'Topic'); const smsNumber = new CfnParameter(this, 'sms-param'); -myTopic.addSubscription(new subscriptions.SmsSubscription(smsNumber.valueAsString())); + +myTopic.addSubscription(new subscriptions.SmsSubscription(smsNumber.valueAsString)); ``` diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index aa7581653d5ba..99e2bdb919280 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -36,6 +36,12 @@ export class LambdaSubscription implements sns.ITopicSubscription { principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); + // if the topic and function are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.fn.stack) { + this.fn.stack.addDependency(topic.stack); + } + return { subscriberScope: this.fn, subscriberId: topic.node.id, @@ -50,8 +56,16 @@ export class LambdaSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack. if (topic instanceof sns.Topic) { + if (topic.stack !== this.fn.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.fn.stack.region)) { + return topic.stack.region; + } + } return undefined; } - return Stack.of(topic).parseArn(topic.topicArn).region; + return Stack.of(topic).splitArn(topic.topicArn, ArnFormat.SLASH_RESOURCE_NAME).region; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 6cf89ebc53c60..10c218dd3ef38 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -61,6 +61,12 @@ export class SqsSubscription implements sns.ITopicSubscription { })); } + // if the topic and queue are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.queue.stack) { + this.queue.stack.addDependency(topic.stack); + } + return { subscriberScope: this.queue, subscriberId: Names.nodeUniqueId(topic.node), @@ -76,8 +82,16 @@ export class SqsSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack if (topic instanceof sns.Topic) { + if (topic.stack !== this.queue.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.queue.stack.region)) { + return topic.stack.region; + } + } return undefined; } - return Stack.of(topic).parseArn(topic.topicArn).region; + return Stack.of(topic).splitArn(topic.topicArn, ArnFormat.SLASH_RESOURCE_NAME).region; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/package.json b/packages/@aws-cdk/aws-sns-subscriptions/package.json index 9bc3880a4ba23..6f8a4f5a2b9cc 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/package.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -69,8 +76,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-sns-subscriptions/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-sns-subscriptions/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..8fd7cde78ac48 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { CfnParameter, Duration, Stack } from '@aws-cdk/core'; +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json new file mode 100644 index 0000000000000..de5216b565954 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json @@ -0,0 +1,116 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "EchoServiceRoleBE28060B": { + "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" + ] + ] + } + ] + } + }, + "Echo11F3FB29": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n /* eslint-disable no-console */\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + }, + "Role": { + "Fn::GetAtt": [ + "EchoServiceRoleBE28060B", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "EchoServiceRoleBE28060B" + ] + }, + "EchoAllowInvokeTopicStackMyTopicC43E67AF32CF6EFA": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "EchoMyTopic4CB8819E": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts new file mode 100644 index 0000000000000..cfec9592e3dba --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts @@ -0,0 +1,35 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const functionStack = new cdk.Stack(app, 'FunctionStack', { + env: { region: 'us-east-2' }, +}); +const fction = new lambda.Function(functionStack, 'Echo', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), +}); + +topic.addSubscription(new subs.LambdaSubscription(fction)); + +app.synth(); + +function handler(event: any, _context: any, callback: any) { + /* eslint-disable no-console */ + console.log('===================================================='); + console.log(JSON.stringify(event, undefined, 2)); + console.log('===================================================='); + return callback(undefined, event); +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json new file mode 100644 index 0000000000000..5bbffb5e31628 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json @@ -0,0 +1,90 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyQueuePolicy6BBEDDAC": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "MyQueueE6CA6235" + } + ] + } + }, + "MyQueueTopicStackMyTopicC43E67AFC8DC8B4A": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts new file mode 100644 index 0000000000000..ca53a70194e03 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts @@ -0,0 +1,25 @@ +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +/// !show +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const queueStack = new cdk.Stack(app, 'QueueStack', { + env: { region: 'us-east-2' }, +}); +const queue = new sqs.Queue(queueStack, 'MyQueue'); + +topic.addSubscription(new subs.SqsSubscription(queue)); +/// !hide + +app.synth(); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 8be564b5a9188..671937a3ed01e 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -3,7 +3,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; +import { App, CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; import * as subs from '../lib'; /* eslint-disable quote-props */ @@ -308,6 +308,455 @@ test('queue subscription', () => { }); }); +test('queue subscription cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + +test('queue subscription cross region, env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + 'Outputs': { + 'ExportsOutputRefTopicBFC7AF6ECB4A357A': { + 'Value': { + 'Ref': 'TopicBFC7AF6E', + }, + 'Export': { + 'Name': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, topic env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, queue env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); test('queue subscription with user provided dlq', () => { const queue = new sqs.Queue(stack, 'MyQueue'); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { @@ -712,6 +1161,243 @@ test('lambda subscription', () => { }); }); +test('lambda subscription, cross region env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const lambdaStack = new Stack(app, 'LambdaStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + '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', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('lambda subscription, cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const lambdaStack = new Stack(app, 'LambdaStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + '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', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + test('email subscription', () => { topic.addSubscription(new subs.EmailSubscription('foo@bar.com')); diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 23ee15e4af508..72678ff0f8a3d 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -14,23 +14,19 @@ Add an SNS Topic to your stack: ```ts -import * as sns from '@aws-cdk/aws-sns'; - const topic = new sns.Topic(this, 'Topic', { - displayName: 'Customer subscription topic' + displayName: 'Customer subscription topic', }); ``` Add a FIFO SNS topic with content-based de-duplication to your stack: ```ts -import * as sns from '@aws-cdk/aws-sns'; - const topic = new sns.Topic(this, 'Topic', { - contentBasedDeduplication: true, - displayName: 'Customer subscription topic', - fifo: true, - topicName: 'customerTopic', + contentBasedDeduplication: true, + displayName: 'Customer subscription topic', + fifo: true, + topicName: 'customerTopic', }); ``` @@ -46,17 +42,18 @@ default implementations of which can be found in the Add an HTTPS Subscription to your topic: ```ts -import * as subs from '@aws-cdk/aws-sns-subscriptions'; - const myTopic = new sns.Topic(this, 'MyTopic'); -myTopic.addSubscription(new subs.UrlSubscription('https://foobar.com/')); +myTopic.addSubscription(new subscriptions.UrlSubscription('https://foobar.com/')); ``` Subscribe a queue to the topic: ```ts -myTopic.addSubscription(new subs.SqsSubscription(queue)); +declare const queue: sqs.Queue; +const myTopic = new sns.Topic(this, 'MyTopic'); + +myTopic.addSubscription(new subscriptions.SqsSubscription(queue)); ``` Note that subscriptions of queues in different accounts need to be manually confirmed by @@ -69,44 +66,48 @@ A filter policy can be specified when subscribing an endpoint to a topic. Example with a Lambda subscription: ```ts +import * as lambda from '@aws-cdk/aws-lambda'; + const myTopic = new sns.Topic(this, 'MyTopic'); -const fn = new lambda.Function(this, 'Function', ...); +declare const fn: lambda.Function; // Lambda should receive only message matching the following conditions on attributes: // color: 'red' or 'orange' or begins with 'bl' // size: anything but 'small' or 'medium' // price: between 100 and 200 or greater than 300 // store: attribute must be present -topic.addSubscription(new subs.LambdaSubscription(fn, { - filterPolicy: { - color: sns.SubscriptionFilter.stringFilter({ - allowlist: ['red', 'orange'], - matchPrefixes: ['bl'] - }), - size: sns.SubscriptionFilter.stringFilter({ - denylist: ['small', 'medium'], - }), - price: sns.SubscriptionFilter.numericFilter({ - between: { start: 100, stop: 200 }, - greaterThan: 300 - }), - store: sns.SubscriptionFilter.existsFilter(), - } +myTopic.addSubscription(new subscriptions.LambdaSubscription(fn, { + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + allowlist: ['red', 'orange'], + matchPrefixes: ['bl'], + }), + size: sns.SubscriptionFilter.stringFilter({ + denylist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 }, + greaterThan: 300, + }), + store: sns.SubscriptionFilter.existsFilter(), + }, })); ``` ### Example of Firehose Subscription -```typescript - import { Subscription, SubscriptionProtocol, Topic } from '@aws-cdk/aws-sns'; - import { DeliveryStream } from '@aws-cdk/aws-kinesisfirehose'; - const topic = new Topic(stack, 'Topic'); - const stream = new DeliveryStream(stack, 'DeliveryStream', ...) - new Subscription(stack, 'Subscription', { - endpoint: stream.deliveryStreamArn, - protocol: SubscriptionProtocol.FIREHOSE, - subscriptionRoleArn: "SAMPLE_ARN", //role with permissions to send messages to a firehose delivery stream - }) +```ts +import { DeliveryStream } from '@aws-cdk/aws-kinesisfirehose'; + +const topic = new sns.Topic(this, 'Topic'); +declare const stream: DeliveryStream; + +new sns.Subscription(this, 'Subscription', { + topic, + endpoint: stream.deliveryStreamArn, + protocol: sns.SubscriptionProtocol.FIREHOSE, + subscriptionRoleArn: "SAMPLE_ARN", //role with permissions to send messages to a firehose delivery stream +}); ``` ## DLQ setup for SNS Subscription @@ -117,17 +118,17 @@ See the [SNS DLQ configuration docs](https://docs.aws.amazon.com/sns/latest/dg/s Example of usage with user provided DLQ. ```ts -const topic = new sns.Topic(stack, 'Topic'); -const dlQueue = new Queue(stack, 'DeadLetterQueue', { - queueName: 'MySubscription_DLQ', - retentionPeriod: cdk.Duration.days(14), +const topic = new sns.Topic(this, 'Topic'); +const dlQueue = new sqs.Queue(this, 'DeadLetterQueue', { + queueName: 'MySubscription_DLQ', + retentionPeriod: Duration.days(14), }); -new sns.Subscription(stack, 'Subscription', { - endpoint: 'endpoint', - protocol: sns.SubscriptionProtocol.LAMBDA, - topic, - deadLetterQueue: dlQueue, +new sns.Subscription(this, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.LAMBDA, + topic, + deadLetterQueue: dlQueue, }); ``` @@ -138,9 +139,15 @@ SNS topics can be used as targets for CloudWatch event rules. Use the `@aws-cdk/aws-events-targets.SnsTopic`: ```ts +import * as codecommit from '@aws-cdk/aws-codecommit'; import * as targets from '@aws-cdk/aws-events-targets'; -codeCommitRepository.onCommit(new targets.SnsTopic(myTopic)); +declare const repo: codecommit.Repository; +const myTopic = new sns.Topic(this, 'Topic'); + +repo.onCommit('OnCommit', { + target: new targets.SnsTopic(myTopic), +}); ``` This will result in adding a target to the event rule and will also modify the @@ -153,8 +160,8 @@ one doesn't already exist. Using `addToResourcePolicy` is the simplest way to add policies, but a `TopicPolicy` can also be created manually. ```ts -const topic = new sns.Topic(stack, 'Topic'); -const topicPolicy = new sns.TopicPolicy(stack, 'TopicPolicy', { +const topic = new sns.Topic(this, 'Topic'); +const topicPolicy = new sns.TopicPolicy(this, 'TopicPolicy', { topics: [topic], }); @@ -168,14 +175,14 @@ topicPolicy.document.addStatements(new iam.PolicyStatement({ A policy document can also be passed on `TopicPolicy` construction ```ts -const topic = new sns.Topic(stack, 'Topic'); +const topic = new sns.Topic(this, 'Topic'); const policyDocument = new iam.PolicyDocument({ assignSids: true, statements: [ new iam.PolicyStatement({ actions: ["sns:Subscribe"], principals: [new iam.AnyPrincipal()], - resources: [topic.topicArn] + resources: [topic.topicArn], }), ], }); diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index f4bbfc10cb2ca..aa78dcfee6b80 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,5 +1,5 @@ import { IKey } from '@aws-cdk/aws-kms'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnTopic } from './sns.generated'; import { ITopic, TopicBase } from './topic-base'; @@ -63,7 +63,7 @@ export class Topic extends TopicBase { public static fromTopicArn(scope: Construct, id: string, topicArn: string): ITopic { class Import extends TopicBase { public readonly topicArn = topicArn; - public readonly topicName = Stack.of(scope).parseArn(topicArn).resource; + public readonly topicName = Stack.of(scope).splitArn(topicArn, ArnFormat.NO_RESOURCE_NAME).resource; protected autoCreatePolicy: boolean = false; } diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 8e27308a61703..b5f4039c63744 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -31,7 +31,14 @@ "excludeTypescript": [ "examples" ], - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -81,8 +88,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-sns/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-sns/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..72fda381ecb7a --- /dev/null +++ b/packages/@aws-cdk/aws-sns/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sqs/README.md b/packages/@aws-cdk/aws-sqs/README.md index c824f9410e8ad..11dbb164e5c56 100644 --- a/packages/@aws-cdk/aws-sqs/README.md +++ b/packages/@aws-cdk/aws-sqs/README.md @@ -22,7 +22,7 @@ without losing messages or requiring other services to be available. Import to your project: -```ts +```ts nofixture import * as sqs from '@aws-cdk/aws-sqs'; ``` @@ -44,15 +44,15 @@ can manage yourself. ```ts // Use managed key new sqs.Queue(this, 'Queue', { - encryption: QueueEncryption.KMS_MANAGED, + encryption: sqs.QueueEncryption.KMS_MANAGED, }); // Use custom key const myKey = new kms.Key(this, 'Key'); new sqs.Queue(this, 'Queue', { - encryption: QueueEncryption.KMS, - encryptionMasterKey: myKey + encryption: sqs.QueueEncryption.KMS, + encryptionMasterKey: myKey, }); ``` diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index d3ab48584fd61..af74b209b3931 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,9 +85,9 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-sqs/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..968d51c8ae916 --- /dev/null +++ b/packages/@aws-cdk/aws-sqs/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import sqs = require('@aws-cdk/aws-sqs'); +import kms = require('@aws-cdk/aws-kms'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-ssm/README.md b/packages/@aws-cdk/aws-ssm/README.md index 54e7acab8cec0..23fea31765525 100644 --- a/packages/@aws-cdk/aws-ssm/README.md +++ b/packages/@aws-cdk/aws-ssm/README.md @@ -23,7 +23,7 @@ $ npm i @aws-cdk/aws-ssm Import it into your code: -```ts +```ts nofixture import * as ssm from '@aws-cdk/aws-ssm'; ``` @@ -43,7 +43,7 @@ to provision secrets automatically, use Secrets Manager Secrets (see the `@aws-cdk/aws-secretsmanager` package). ```ts -new ssm.StringParameter(stack, 'Parameter', { +new ssm.StringParameter(this, 'Parameter', { allowedPattern: '.*', description: 'The value Foo', parameterName: 'FooParameter', diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index cbec2073cbe1f..29c0eb8a33f03 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -137,6 +137,13 @@ export interface StringParameterProps extends ParameterOptions { * @default ParameterType.STRING */ readonly type?: ParameterType; + + /** + * The data type of the parameter, such as `text` or `aws:ec2:image`. + * + * @default - undefined + */ + readonly dataType?: ParameterDataType; } /** @@ -217,6 +224,20 @@ export enum ParameterType { AWS_EC2_IMAGE_ID = 'AWS::EC2::Image::Id', } +/** + * SSM parameter data type + */ +export enum ParameterDataType { + /** + * Text + */ + TEXT = 'text', + /** + * Aws Ec2 Image + */ + AWS_EC2_IMAGE = 'aws:ec2:image', +} + /** * SSM parameter tier */ @@ -436,12 +457,17 @@ export class StringParameter extends ParameterBase implements IStringParameter { throw new Error('Description cannot be longer than 1024 characters.'); } + if (props.type && props.type === ParameterType.AWS_EC2_IMAGE_ID) { + throw new Error('The type must either be ParameterType.STRING or ParameterType.STRING_LIST. Did you mean to set dataType: ParameterDataType.AWS_EC2_IMAGE instead?'); + } + const resource = new ssm.CfnParameter(this, 'Resource', { allowedPattern: props.allowedPattern, description: props.description, name: this.physicalName, tier: props.tier, type: props.type || ParameterType.STRING, + dataType: props.dataType, value: props.stringValue, }); diff --git a/packages/@aws-cdk/aws-ssm/lib/util.ts b/packages/@aws-cdk/aws-ssm/lib/util.ts index d023ef902282d..c79a909628629 100644 --- a/packages/@aws-cdk/aws-ssm/lib/util.ts +++ b/packages/@aws-cdk/aws-ssm/lib/util.ts @@ -1,4 +1,4 @@ -import { Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Stack, Token } from '@aws-cdk/core'; import { IConstruct } from 'constructs'; export const AUTOGEN_MARKER = '$$autogen$$'; @@ -22,12 +22,19 @@ export function arnForParameterName(scope: IConstruct, parameterName: string, op throw new Error(`Parameter names must be fully qualified (if they include "/" they must also begin with a "/"): ${nameToValidate}`); } - return Stack.of(scope).formatArn({ - service: 'ssm', - resource: 'parameter', - sep: isSimpleName() ? '/' : '', - resourceName: parameterName, - }); + if (isSimpleName()) { + return Stack.of(scope).formatArn({ + service: 'ssm', + resource: 'parameter', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + resourceName: parameterName, + }); + } else { + return Stack.of(scope).formatArn({ + service: 'ssm', + resource: `parameter${parameterName}`, + }); + } /** * Determines the ARN separator for this parameter: if we have a concrete diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index c30ae7e97c8f1..cdf17bfb69c1c 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -77,8 +84,8 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-ssm/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ssm/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..66143d00d0bff --- /dev/null +++ b/packages/@aws-cdk/aws-ssm/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as ssm from '@aws-cdk/aws-ssm'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-ssm/test/parameter.test.ts b/packages/@aws-cdk/aws-ssm/test/parameter.test.ts index 17b2af748d9ef..45fbc70f9d926 100644 --- a/packages/@aws-cdk/aws-ssm/test/parameter.test.ts +++ b/packages/@aws-cdk/aws-ssm/test/parameter.test.ts @@ -28,6 +28,34 @@ test('creating a String SSM Parameter', () => { }); }); +test('type cannot be specified as AWS_EC2_IMAGE_ID', () => { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + expect(() => new ssm.StringParameter(stack, 'myParam', { + stringValue: 'myValue', + type: ssm.ParameterType.AWS_EC2_IMAGE_ID, + })).toThrow('The type must either be ParameterType.STRING or ParameterType.STRING_LIST. Did you mean to set dataType: ParameterDataType.AWS_EC2_IMAGE instead?'); +}); + +test('dataType can be specified', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ssm.StringParameter(stack, 'myParam', { + stringValue: 'myValue', + dataType: ssm.ParameterDataType.AWS_EC2_IMAGE, + }); + + // THEN + expect(stack).toHaveResource('AWS::SSM::Parameter', { + Value: 'myValue', + DataType: 'aws:ec2:image', + }); +}); + test('expect String SSM Parameter to have tier properly set', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ssmcontacts/README.md b/packages/@aws-cdk/aws-ssmcontacts/README.md index 6418d35f765af..cab7c329a4bab 100644 --- a/packages/@aws-cdk/aws-ssmcontacts/README.md +++ b/packages/@aws-cdk/aws-ssmcontacts/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import ssmcontacts = require('@aws-cdk/aws-ssmcontacts'); +import * as ssmcontacts from '@aws-cdk/aws-ssmcontacts'; ``` diff --git a/packages/@aws-cdk/aws-ssmcontacts/package.json b/packages/@aws-cdk/aws-ssmcontacts/package.json index cc6f3d5c34464..603c082e46845 100644 --- a/packages/@aws-cdk/aws-ssmcontacts/package.json +++ b/packages/@aws-cdk/aws-ssmcontacts/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSMContacts", @@ -80,7 +87,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-ssmincidents/README.md b/packages/@aws-cdk/aws-ssmincidents/README.md index 169151903df0d..411c652fc7d8c 100644 --- a/packages/@aws-cdk/aws-ssmincidents/README.md +++ b/packages/@aws-cdk/aws-ssmincidents/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import ssmincidents = require('@aws-cdk/aws-ssmincidents'); +import * as ssmincidents from '@aws-cdk/aws-ssmincidents'; ``` diff --git a/packages/@aws-cdk/aws-ssmincidents/package.json b/packages/@aws-cdk/aws-ssmincidents/package.json index bbc9103e668fa..dfbe2be486b87 100644 --- a/packages/@aws-cdk/aws-ssmincidents/package.json +++ b/packages/@aws-cdk/aws-ssmincidents/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSMIncidents", @@ -80,7 +87,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sso/README.md b/packages/@aws-cdk/aws-sso/README.md index 8da757ee4994c..d1f2b8988da89 100644 --- a/packages/@aws-cdk/aws-sso/README.md +++ b/packages/@aws-cdk/aws-sso/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import sso = require('@aws-cdk/aws-sso'); +import * as sso from '@aws-cdk/aws-sso'; ``` diff --git a/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sso/package.json b/packages/@aws-cdk/aws-sso/package.json index 12b8dfc9536d2..38e6621ed0fa0 100644 --- a/packages/@aws-cdk/aws-sso/package.json +++ b/packages/@aws-cdk/aws-sso/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSO", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 521052e865b0b..2f5d463067a35 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -33,6 +33,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [API Gateway](#api-gateway) - [Call REST API Endpoint](#call-rest-api-endpoint) - [Call HTTP API Endpoint](#call-http-api-endpoint) + - [AWS SDK](#aws-sdk) - [Athena](#athena) - [StartQueryExecution](#startqueryexecution) - [GetQueryExecution](#getqueryexecution) @@ -109,9 +110,10 @@ The following example provides the field named `input` as the input to the `Task state that runs a Lambda function. ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, - inputPath: '$.input' + inputPath: '$.input', }); ``` @@ -129,9 +131,10 @@ as well as other metadata. The following example assigns the output from the Task to a field named `result` ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, - outputPath: '$.Payload.result' + outputPath: '$.Payload.result', }); ``` @@ -148,6 +151,7 @@ The following example extracts the output payload of a Lambda function Task and it with some static values and the state name from the context object. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, resultSelector: { @@ -158,7 +162,7 @@ new tasks.LambdaInvoke(this, 'Invoke Handler', { }, stateName: sfn.JsonPath.stringAt('$$.State.Name'), }, -}) +}); ``` ### ResultPath @@ -173,9 +177,10 @@ The following example adds the item from calling DynamoDB's `getItem` API to the input and passes it to the next state. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoPutItem(this, 'PutItem', { item: { - MessageId: tasks.DynamoAttributeValue.fromString('message-id') + MessageId: tasks.DynamoAttributeValue.fromString('message-id'), }, table: myTable, resultPath: `$.Item`, @@ -198,6 +203,7 @@ The following example provides the field named `input` as the input to the Lambd and invokes it asynchronously. ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, payload: sfn.TaskInput.fromJsonPathAt('$.input'), @@ -205,7 +211,7 @@ const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { }); ``` -You can also use [intrinsic functions](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html) with `JsonPath.stringAt()`. +You can also use [intrinsic functions](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html) with `JsonPath.stringAt()`. Here is an example of starting an Athena query that is dynamically created using the task input: ```ts @@ -257,14 +263,14 @@ const publishMessage = new tasks.SnsPublish(this, 'Publish message', { }); const wait = new sfn.Wait(this, 'Wait', { - time: sfn.WaitTime.secondsPath('$.waitSeconds') + time: sfn.WaitTime.secondsPath('$.waitSeconds'), }); new sfn.StateMachine(this, 'StateMachine', { definition: convertToSeconds .next(createMessage) .next(publishMessage) - .next(wait) + .next(wait), }); ``` @@ -285,15 +291,13 @@ Previous-generation REST APIs currently offer more features. More details can be The `CallApiGatewayRestApiEndpoint` calls the REST API endpoint. ```ts -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; - -const restApi = new apigateway.RestApi(stack, 'MyRestApi'); +import * as apigateway from '@aws-cdk/aws-apigateway'; +const restApi = new apigateway.RestApi(this, 'MyRestApi'); -const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API', { +const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(this, 'Call REST API', { api: restApi, stageName: 'prod', - method: HttpMethod.GET, + method: tasks.HttpMethod.GET, }); ``` @@ -302,15 +306,51 @@ const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API The `CallApiGatewayHttpApiEndpoint` calls the HTTP API endpoint. ```ts -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; - -const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi'); +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +const httpApi = new apigatewayv2.HttpApi(this, 'MyHttpApi'); -const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', { +const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(this, 'Call HTTP API', { apiId: httpApi.apiId, - apiStack: cdk.Stack.of(httpApi), - method: HttpMethod.GET, + apiStack: Stack.of(httpApi), + method: tasks.HttpMethod.GET, +}); +``` + +### AWS SDK + +Step Functions supports calling [AWS service's API actions](https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html) +through the service integration pattern. + +You can use Step Functions' AWS SDK integrations to call any of the over two hundred AWS services +directly from your state machine, giving you access to over nine thousand API actions. + +```ts +declare const myBucket: s3.Bucket; +const getObject = new tasks.CallAwsService(this, 'GetObject', { + service: 's3', + action: 'getObject', + parameters: { + Bucket: myBucket.bucketName, + Key: sfn.JsonPath.stringAt('$.key') + }, + iamResources: [myBucket.arnForObjects('*')], +}); +``` + +Use camelCase for actions and PascalCase for parameter names. + +The task automatically adds an IAM statement to the state machine role's policy based on the +service and action called. The resources for this statement must be specified in `iamResources`. + +Use the `iamAction` prop to manually specify the IAM action name in the case where the IAM +action name does not match with the API service/action name: + +```ts +const listBuckets = new tasks.CallAwsService(this, 'ListBuckets', { + service: 's3', + action: 'listBuckets', + iamResources: ['*'], + iamAction: 's3:ListAllMyBuckets', }); ``` @@ -378,11 +418,15 @@ Step Functions supports [Batch](https://docs.aws.amazon.com/step-functions/lates The [SubmitJob](https://docs.aws.amazon.com/batch/latest/APIReference/API_SubmitJob.html) API submits an AWS Batch job from a job definition. -```ts fixture=with-batch-job +```ts +import * as batch from '@aws-cdk/aws-batch'; +declare const batchJobDefinition: batch.JobDefinition; +declare const batchQueue: batch.JobQueue; + const task = new tasks.BatchSubmitJob(this, 'Submit Job', { - jobDefinitionArn: batchJobDefinitionArn, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'MyJob', - jobQueueArn: batchQueueArn, + jobQueueArn: batchQueue.jobQueueArn, }); ``` @@ -433,6 +477,7 @@ Read more about calling DynamoDB APIs [here](https://docs.aws.amazon.com/step-fu The [GetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) operation returns a set of attributes for the item with the given primary key. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoGetItem(this, 'Get Item', { key: { messageId: tasks.DynamoAttributeValue.fromString('message-007') }, table: myTable, @@ -444,6 +489,7 @@ new tasks.DynamoGetItem(this, 'Get Item', { The [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) operation creates a new item, or replaces an old item with a new item. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoPutItem(this, 'PutItem', { item: { MessageId: tasks.DynamoAttributeValue.fromString('message-007'), @@ -459,6 +505,7 @@ new tasks.DynamoPutItem(this, 'PutItem', { The [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) operation deletes a single item in a table by primary key. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoDeleteItem(this, 'DeleteItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') }, table: myTable, @@ -472,6 +519,7 @@ The [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/ to the table if it does not already exist. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoUpdateItem(this, 'UpdateItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') @@ -509,8 +557,6 @@ The latest ACTIVE revision of the passed task definition is used for running the The following example runs a job from a task definition on EC2 ```ts -import * as ecs from '@aws-cdk/aws-ecs'; - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { isDefault: true, }); @@ -531,20 +577,20 @@ taskDefinition.addContainer('TheContainer', { }); const runTask = new tasks.EcsRunTask(this, 'Run', { - integrationPattern: sfn.IntegrationPattern.RUN_JOB, - cluster, - taskDefinition, - launchTarget: new tasks.EcsEc2LaunchTarget({ - placementStrategies: [ - ecs.PlacementStrategy.spreadAcrossInstances(), - ecs.PlacementStrategy.packedByCpu(), - ecs.PlacementStrategy.randomly(), - ], - placementConstraints: [ - ecs.PlacementConstraint.memberOf('blieptuut') - ], - }), - }); + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + cluster, + taskDefinition, + launchTarget: new tasks.EcsEc2LaunchTarget({ + placementStrategies: [ + ecs.PlacementStrategy.spreadAcrossInstances(), + ecs.PlacementStrategy.packedByCpu(), + ecs.PlacementStrategy.randomly(), + ], + placementConstraints: [ + ecs.PlacementConstraint.memberOf('blieptuut'), + ], + }), +}); ``` #### Fargate @@ -564,8 +610,6 @@ task definition is used for running the task. Learn more about The following example runs a job from a task definition on Fargate ```ts -import * as ecs from '@aws-cdk/aws-ecs'; - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { isDefault: true, }); @@ -610,7 +654,6 @@ Creates and starts running a cluster (job flow). Corresponds to the [`runJobFlow`](https://docs.aws.amazon.com/emr/latest/APIReference/API_RunJobFlow.html) API in EMR. ```ts - const clusterRole = new iam.Role(this, 'ClusterRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), }); @@ -651,7 +694,8 @@ and 256 inclusive, where the default concurrency of 1 means no step concurrency ```ts new tasks.EmrCreateCluster(this, 'Create Cluster', { - // ... + instances: {}, + name: sfn.TaskInput.fromJsonPathAt('$.ClusterName').value, stepConcurrencyLevel: 10, }); ``` @@ -677,7 +721,7 @@ Corresponds to the [`terminateJobFlows`](https://docs.aws.amazon.com/emr/latest/ ```ts new tasks.EmrTerminateCluster(this, 'Task', { - clusterId: 'ClusterId' + clusterId: 'ClusterId', }); ``` @@ -688,10 +732,10 @@ Corresponds to the [`addJobFlowSteps`](https://docs.aws.amazon.com/emr/latest/AP ```ts new tasks.EmrAddStep(this, 'Task', { - clusterId: 'ClusterId', - name: 'StepName', - jar: 'Jar', - actionOnFailure: tasks.ActionOnFailure.CONTINUE, + clusterId: 'ClusterId', + name: 'StepName', + jar: 'Jar', + actionOnFailure: tasks.ActionOnFailure.CONTINUE, }); ``` @@ -755,17 +799,15 @@ The following code snippet includes a Task state that uses eks:call to list the ```ts import * as eks from '@aws-cdk/aws-eks'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; const myEksCluster = new eks.Cluster(this, 'my sample cluster', { - version: eks.KubernetesVersion.V1_18, - clusterName: 'myEksCluster', - }); + version: eks.KubernetesVersion.V1_18, + clusterName: 'myEksCluster', +}); -new tasks.EksCall(stack, 'Call a EKS Endpoint', { +new tasks.EksCall(this, 'Call a EKS Endpoint', { cluster: myEksCluster, - httpMethod: MethodType.GET, + httpMethod: tasks.HttpMethods.GET, httpPath: '/api/v1/namespaces/default/pods', }); ``` @@ -786,14 +828,12 @@ The following code snippet includes a Task state that uses events:putevents to s ```ts import * as events from '@aws-cdk/aws-events'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -const myEventBus = events.EventBus(stack, 'EventBus', { +const myEventBus = new events.EventBus(this, 'EventBus', { eventBusName: 'MyEventBus1', }); -new tasks.EventBridgePutEvents(stack, 'Send an event to EventBridge', { +new tasks.EventBridgePutEvents(this, 'Send an event to EventBridge', { entries: [{ detail: sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions!', @@ -817,8 +857,8 @@ new tasks.GlueStartJobRun(this, 'Task', { arguments: sfn.TaskInput.fromObject({ key: 'value', }), - timeout: cdk.Duration.minutes(30), - notifyDelayAfter: cdk.Duration.minutes(5), + timeout: Duration.minutes(30), + notifyDelayAfter: Duration.minutes(5), }); ``` @@ -846,6 +886,7 @@ The following snippet invokes a Lambda Function with the state input as the payl by referencing the `$` path. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with state input', { lambdaFunction: fn, }); @@ -861,6 +902,7 @@ The following snippet invokes a Lambda Function by referencing the `$.Payload` p to reference the output of a Lambda executed before it. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with empty object as payload', { lambdaFunction: fn, payload: sfn.TaskInput.fromObject({}), @@ -877,6 +919,7 @@ The following snippet invokes a Lambda and sets the task output to only include the Lambda function response. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke and set function response as task output', { lambdaFunction: fn, outputPath: '$.Payload', @@ -889,6 +932,7 @@ Lambda function ARN directly in the "Resource" string, but it conflicts with the integrationPattern, invocationType, clientContext, and qualifier properties. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke and combine function response with task input', { lambdaFunction: fn, payloadResponseOnly: true, @@ -907,6 +951,7 @@ The following snippet invokes a Lambda with the task token as part of the input to the Lambda. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with callback', { lambdaFunction: fn, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, @@ -931,6 +976,10 @@ disable this behavior. Step Functions supports [AWS SageMaker](https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html) through the service integration pattern. +If your training job or model uses resources from AWS Marketplace, +[network isolation is required](https://docs.aws.amazon.com/sagemaker/latest/dg/mkt-algo-model-internet-free.html). +To do so, set the `enableNetworkIsolation` property to `true` for `SageMakerCreateModel` or `SageMakerCreateTrainingJob`. + ### Create Training Job You can call the [`CreateTrainingJob`](https://docs.aws.amazon.com/sagemaker/latest/dg/API_CreateTrainingJob.html) API from a `Task` state. @@ -956,11 +1005,11 @@ new tasks.SageMakerCreateTrainingJob(this, 'TrainSagemaker', { }, resourceConfig: { instanceCount: 1, - instanceType: new ec2.InstanceType(JsonPath.stringAt('$.InstanceType')), - volumeSize: cdk.Size.gibibytes(50), + instanceType: new ec2.InstanceType(sfn.JsonPath.stringAt('$.InstanceType')), + volumeSize: Size.gibibytes(50), }, // optional: default is 1 instance of EC2 `M4.XLarge` with `10GB` volume stoppingCondition: { - maxRuntime: cdk.Duration.hours(2), + maxRuntime: Duration.hours(2), }, // optional: default is 1 hour }); ``` @@ -975,7 +1024,7 @@ new tasks.SageMakerCreateTransformJob(this, 'Batch Inference', { modelName: 'MyModelName', modelClientOptions: { invocationsMaxRetries: 3, // default is 0 - invocationsTimeout: cdk.Duration.minutes(5), // default is 60 seconds + invocationsTimeout: Duration.minutes(5), // default is 60 seconds }, transformInput: { transformDataSource: { @@ -1017,9 +1066,9 @@ new tasks.SageMakerCreateEndpointConfig(this, 'SagemakerEndpointConfig', { productionVariants: [{ initialInstanceCount: 2, instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE), - modelName: 'MyModel', - variantName: 'awesome-variant', - }], + modelName: 'MyModel', + variantName: 'awesome-variant', + }], }); ``` @@ -1031,9 +1080,9 @@ You can call the [`CreateModel`](https://docs.aws.amazon.com/sagemaker/latest/AP new tasks.SageMakerCreateModel(this, 'Sagemaker', { modelName: 'MyModel', primaryContainer: new tasks.ContainerDefinition({ - image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), - mode: tasks.Mode.SINGLE_MODEL, - modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), + image: tasks.DockerImage.fromJsonExpression(sfn.JsonPath.stringAt('$.Model.imageName')), + mode: tasks.Mode.SINGLE_MODEL, + modelS3Location: tasks.S3Location.fromJsonExpression('$.TrainingJob.ModelArtifacts.S3ModelArtifacts'), }), }); ``` @@ -1044,9 +1093,9 @@ You can call the [`UpdateEndpoint`](https://docs.aws.amazon.com/sagemaker/latest ```ts new tasks.SageMakerUpdateEndpoint(this, 'SagemakerEndpoint', { - endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), - endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.EndpointConfig'), - }); + endpointName: sfn.JsonPath.stringAt('$.Endpoint.Name'), + endpointConfigName: sfn.JsonPath.stringAt('$.Endpoint.EndpointConfig'), +}); ``` ## SNS @@ -1069,7 +1118,7 @@ const task1 = new tasks.SnsPublish(this, 'Publish1', { }, pic: { // BINARY must be explicitly set - type: MessageAttributeDataType.BINARY, + dataType: tasks.MessageAttributeDataType.BINARY, value: sfn.JsonPath.stringAt('$.pic'), }, people: { @@ -1078,7 +1127,7 @@ const task1 = new tasks.SnsPublish(this, 'Publish1', { handles: { value: ['@kslater', '@jjf', null, '@mfanning'], }, - + }, }); // Combine a field from the execution data with @@ -1088,7 +1137,7 @@ const task2 = new tasks.SnsPublish(this, 'Publish2', { message: sfn.TaskInput.fromObject({ field1: 'somedata', field2: sfn.JsonPath.stringAt('$.field2'), - }) + }), }); ``` @@ -1103,7 +1152,7 @@ AWS Step Functions supports it's own [`StartExecution`](https://docs.aws.amazon. ```ts // Define a state machine with one Pass state const child = new sfn.StateMachine(this, 'ChildStateMachine', { - definition: sfn.Chain.start(new sfn.Pass(this, 'PassState')), + definition: sfn.Chain.start(new sfn.Pass(this, 'PassState')), }); // Include the state machine in a Task state with callback pattern @@ -1112,14 +1161,14 @@ const task = new tasks.StepFunctionsStartExecution(this, 'ChildTask', { integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, input: sfn.TaskInput.fromObject({ token: sfn.JsonPath.taskToken, - foo: 'bar' + foo: 'bar', }), - name: 'MyExecutionName' + name: 'MyExecutionName', }); // Define a second state machine with the Task state above new sfn.StateMachine(this, 'ParentStateMachine', { - definition: task + definition: task, }); ``` @@ -1128,6 +1177,7 @@ via the `associateWithParent` property. This allows the Step Functions UI to lin executions from parent executions, making it easier to trace execution flow across state machines. ```ts +declare const child: sfn.StateMachine; const task = new tasks.StepFunctionsStartExecution(this, 'ChildTask', { stateMachine: child, associateWithParent: true, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts index 876626ea05fd3..67def4d6a4699 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts @@ -60,7 +60,7 @@ export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase { return this.props.apiStack.formatArn({ service: 'execute-api', resource: apiId, - sep: '/', + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${stageName}/${method}${apiPath}`, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts index 6a9eaab73eb4b..23e4079b77921 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts @@ -10,7 +10,7 @@ export interface AthenaGetQueryExecutionProps extends sfn.TaskStateBaseProps { /** * Query that will be retrieved * - * @example 'adfsaf-23trf23-f23rt23' + * Example value: `adfsaf-23trf23-f23rt23` */ readonly queryExecutionId: string; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts index 07ec38efa97e1..700b43fc2a599 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts @@ -10,8 +10,8 @@ export interface AthenaGetQueryResultsProps extends sfn.TaskStateBaseProps { /** * Query that will be retrieved * - * @example 'adfsaf-23trf23-f23rt23' - */ + * Example value: `adfsaf-23trf23-f23rt23` + */ readonly queryExecutionId: string; /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts index f23bd66fc5591..ba3f68b95abb1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts @@ -212,8 +212,9 @@ export interface ResultConfiguration { /** * S3 path of query results * + * Example value: `s3://query-results-bucket/folder/` + * * @default - Query Result Location set in Athena settings for this workgroup - * @example s3://query-results-bucket/folder/ */ readonly outputLocation?: s3.Location; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/aws-sdk/call-aws-service.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/aws-sdk/call-aws-service.ts new file mode 100644 index 0000000000000..f3987cc0677ae --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/aws-sdk/call-aws-service.ts @@ -0,0 +1,96 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { integrationResourceArn } from '../private/task-utils'; + +/** + * Properties for calling an AWS service's API action from your + * state machine. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html + */ +export interface CallAwsServiceProps extends sfn.TaskStateBaseProps { + /** + * The AWS service to call. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html + */ + readonly service: string; + + /** + * The API action to call. + * + * Use camelCase. + */ + readonly action: string; + + /** + * Parameters for the API action call. + * + * Use PascalCase for the parameter names. + * + * @default - no parameters + */ + readonly parameters?: { [key: string]: any }; + + /** + * The resources for the IAM statement that will be added to the state + * machine role's policy to allow the state machine to make the API call. + * + * By default the action for this IAM statement will be `service:action`. + */ + readonly iamResources: string[]; + + /** + * The action for the IAM statement that will be added to the state + * machine role's policy to allow the state machine to make the API call. + * + * Use in the case where the IAM action name does not match with the + * API service/action name, e.g. `s3:ListBuckets` requires `s3:ListAllMyBuckets`. + * + * @default - service:action + */ + readonly iamAction?: string; +} + +/** + * A StepFunctions task to call an AWS service API + */ +export class CallAwsService extends sfn.TaskStateBase { + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: Construct, id: string, private readonly props: CallAwsServiceProps) { + super(scope, id, props); + + this.taskPolicies = [ + new iam.PolicyStatement({ + resources: props.iamResources, + // The prefix and the action name are case insensitive + // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html + actions: [props.iamAction ?? `${props.service}:${props.action}`], + }), + ]; + } + + /** + * @internal + */ + protected _renderTask(): any { + let service = this.props.service; + + if (!Token.isUnresolved(service)) { + service = service.toLowerCase(); + } + + return { + Resource: integrationResourceArn( + 'aws-sdk', + `${service}:${this.props.action}`, + this.props.integrationPattern, + ), + Parameters: sfn.FieldUtils.renderObject(this.props.parameters) ?? {}, // Parameters is required for aws-sdk + }; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts index c07d1e8fc5e12..bed1b16a86f70 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts @@ -119,10 +119,10 @@ export class DynamoProjectionExpression { export class DynamoAttributeValue { /** * Sets an attribute of type String. For example: "S": "Hello" - * Strings may be literal values or as JsonPath. + * Strings may be literal values or as JsonPath. Example values: * - * @example `DynamoAttributeValue.fromString('someValue') - * @example `DynamoAttributeValue.fromString(JsonPath.stringAt('$.bar')) + * - `DynamoAttributeValue.fromString('someValue')` + * - `DynamoAttributeValue.fromString(JsonPath.stringAt('$.bar'))` */ public static fromString(value: string) { return new DynamoAttributeValue({ S: value }); @@ -219,6 +219,16 @@ export class DynamoAttributeValue { return new DynamoAttributeValue({ L: value.map((val) => val.toObject()) }); } + /** + * Sets an attribute of type List. For example: "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"S", "Veggies"}] + * + * @param value Json path that specifies state input to be used + */ + public static listFromJsonPath(value: string) { + validateJsonPath(value); + return new DynamoAttributeValue({ L: value }); + } + /** * Sets an attribute of type Null. For example: "NULL": true */ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts index 496decc60c851..26dbc84a00e56 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts @@ -356,14 +356,22 @@ export class EcsRunTask extends sfn.TaskStateBase implements ec2.IConnectable { * After - arn:aws:ecs:us-west-2:123456789012:task-definition/hello_world */ private getTaskDefinitionFamilyArn(): string { - const arnComponents = cdk.Stack.of(this).parseArn(this.props.taskDefinition.taskDefinitionArn); + const arnComponents = cdk.Stack.of(this).splitArn(this.props.taskDefinition.taskDefinitionArn, cdk.ArnFormat.SLASH_RESOURCE_NAME); let { resourceName } = arnComponents; if (resourceName) { resourceName = resourceName.split(':')[0]; } - return cdk.Stack.of(this).formatArn({ ...arnComponents, resourceName }); + return cdk.Stack.of(this).formatArn({ + partition: arnComponents.partition, + service: arnComponents.service, + account: arnComponents.account, + region: arnComponents.region, + resource: arnComponents.resource, + arnFormat: arnComponents.arnFormat, + resourceName, + }); } private taskExecutionRoles(): iam.IRole[] { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts index 7fb010119373b..5b90ce066c70d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts @@ -12,7 +12,7 @@ export interface EvaluateExpressionProps extends sfn.TaskStateBaseProps { /** * The expression to evaluate. The expression may contain state paths. * - * @example '$.a + $.b' + * Example value: `'$.a + $.b'` */ readonly expression: string; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts index 457bdfa6d0011..670b7f01cb1df 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts @@ -16,9 +16,10 @@ export interface EventBridgePutEventsEntry { * * Can either be provided as an object or as a JSON-serialized string * @example - * sfn.TaskInput.fromText('{"instance-id": "i-1234567890abcdef0", "state": "terminated"}') - * sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions' }) - * sfn.TaskInput.fromJsonPathAt('$.EventDetail') + * + * sfn.TaskInput.fromText('{"instance-id": "i-1234567890abcdef0", "state": "terminated"}'); + * sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions' }); + * sfn.TaskInput.fromJsonPathAt('$.EventDetail'); */ readonly detail: sfn.TaskInput; @@ -40,7 +41,8 @@ export interface EventBridgePutEventsEntry { /** * The service or application that caused this event to be generated * - * @example 'com.example.service' + * Example value: `com.example.service` + * * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html */ readonly source: string; @@ -108,7 +110,7 @@ export class EventBridgePutEvents extends sfn.TaskStateBase { return cdk.Stack.of(this).formatArn({ resource: 'event-bus', resourceName: 'default', - sep: '/', + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, service: 'events', }); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index e7337f39b6831..0c089eee35bae 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -47,3 +47,4 @@ export * from './databrew/start-job-run'; export * from './eks/call'; export * from './apigateway'; export * from './eventbridge/put-events'; +export * from './aws-sdk/call-aws-service'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/private/task-utils.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/private/task-utils.ts index a612833075eaf..a0b28323c7e85 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/private/task-utils.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/private/task-utils.ts @@ -26,7 +26,7 @@ const resourceArnSuffix: Record = { [IntegrationPattern.WAIT_FOR_TASK_TOKEN]: '.waitForTaskToken', }; -export function integrationResourceArn(service: string, api: string, integrationPattern: IntegrationPattern): string { +export function integrationResourceArn(service: string, api: string, integrationPattern?: IntegrationPattern): string { if (!service || !api) { throw new Error("Both 'service' and 'api' must be provided to build the resource ARN."); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index 10c35d847ecd1..e11a04c111856 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -206,8 +206,12 @@ export interface ResourceConfig { /** * ML compute instance type. * - * @example To provide an instance type from the task input, write - * `new ec2.InstanceType(sfn.JsonPath.stringAt('$.path.to.instanceType'))`, where the value in the task input is an EC2 instance type prepended with "ml.". + * To provide an instance type from the task input, supply an instance type in the following way + * where the value in the task input is an EC2 instance type prepended with "ml.": + * + * ```ts + * new ec2.InstanceType(sfn.JsonPath.stringAt('$.path.to.instanceType')); + * ``` * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ResourceConfig.html#sagemaker-Type-ResourceConfig-InstanceType * * @default ec2.InstanceType(ec2.InstanceClass.M4, ec2.InstanceType.XLARGE) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts index 0aa9954e859d5..64680dc357747 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts @@ -32,6 +32,13 @@ export interface SageMakerCreateTrainingJobProps extends sfn.TaskStateBaseProps */ readonly algorithmSpecification: AlgorithmSpecification; + /** + * Isolates the training container. No inbound or outbound network calls can be made to or from the training container. + * + * @default false + */ + readonly enableNetworkIsolation?: boolean; + /** * Algorithm-specific parameters that influence the quality of the model. Set hyperparameters before you start the learning process. * For a list of hyperparameters provided by Amazon SageMaker @@ -217,6 +224,7 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam private renderParameters(): { [key: string]: any } { return { TrainingJobName: this.props.trainingJobName, + EnableNetworkIsolation: this.props.enableNetworkIsolation, RoleArn: this._role!.roleArn, ...this.renderAlgorithmSpecification(this.algorithmSpecification), ...this.renderInputDataConfig(this.inputDataConfig), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts index 0572459f1e4a3..d7d5ebdbda79a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { getResourceArn } from './resource-arn-suffix'; /** @@ -103,8 +103,8 @@ export class StartExecution implements sfn.IStepFunctionsTask { resources: [stack.formatArn({ service: 'states', resource: 'execution', - sep: ':', - resourceName: `${stack.parseArn(this.stateMachine.stateMachineArn, ':').resourceName}*`, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, + resourceName: `${stack.splitArn(this.stateMachine.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName}*`, })], })); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts index 638392e636b09..0916dbb720913 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; @@ -133,8 +133,8 @@ export class StepFunctionsStartExecution extends sfn.TaskStateBase { stack.formatArn({ service: 'states', resource: 'execution', - sep: ':', - resourceName: `${stack.parseArn(this.props.stateMachine.stateMachineArn, ':').resourceName}*`, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, + resourceName: `${stack.splitArn(this.props.stateMachine.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName}*`, }), ], }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 1853a14deb2cb..f1e8f3d215ed4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -71,9 +78,10 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", @@ -82,8 +90,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture index 11558a599e5f4..927b8e5fd6886 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture @@ -1,8 +1,9 @@ // Fixture with packages imported, but nothing else -import * as cdk from '@aws-cdk/core'; +import { Duration, RemovalPolicy, Size, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import * as ddb from '@aws-cdk/aws-dynamodb'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; @@ -11,27 +12,10 @@ import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -class Fixture extends cdk.Stack { +class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const fn = new lambda.Function(this, 'lambdaFunction', { - code: lambda.Code.fromInline(`exports.handler = async () => { - return { "hello world"}; - `), - runtime: lambda.Runtime.NODEJS_12_X, - handler: 'index.handler', - }); - - const myTable = new ddb.Table(this, 'Messages', { - tableName: 'my-table', - partitionKey: { - name: 'MessageId', - type: ddb.AttributeType.STRING, - }, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - /// here } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture deleted file mode 100644 index 47672ba140841..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture +++ /dev/null @@ -1,38 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -import * as batch from '@aws-cdk/aws-batch'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as ecs from '@aws-cdk/aws-ecs'; -import * as path from 'path'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { - isDefault: true, - }); - - const batchQueue = new batch.JobQueue(this, 'JobQueue', { - computeEnvironments: [ - { - order: 1, - computeEnvironment: new batch.ComputeEnvironment(this, 'ComputeEnv', { - computeResources: { vpc }, - }), - }, - ], - }); - - const batchJobDefinition = new batch.JobDefinition(this, 'JobDefinition', { - container: { - image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, 'batchjob-image')), - }, - }); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts new file mode 100644 index 0000000000000..d3b9e3e0d42ba --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts @@ -0,0 +1,148 @@ +import { Template } from '@aws-cdk/assertions'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +let stack: cdk.Stack; + +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); +}); + +test('CallAwsService task', () => { + // WHEN + const task = new tasks.CallAwsService(stack, 'GetObject', { + service: 's3', + action: 'getObject', + parameters: { + Bucket: 'my-bucket', + Key: sfn.JsonPath.stringAt('$.key'), + }, + iamResources: ['*'], + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definition: task, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::aws-sdk:s3:getObject', + ], + ], + }, + End: true, + Parameters: { + 'Bucket': 'my-bucket', + 'Key.$': '$.key', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:getObject', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('with custom IAM action', () => { + // WHEN + const task = new tasks.CallAwsService(stack, 'ListBuckets', { + service: 's3', + action: 'listBuckets', + iamResources: ['*'], + iamAction: 's3:ListAllMyBuckets', + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definition: task, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::aws-sdk:s3:listBuckets', + ], + ], + }, + End: true, + Parameters: {}, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:ListAllMyBuckets', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); +}); + +test('with unresolved tokens', () => { + // WHEN + const task = new tasks.CallAwsService(stack, 'ListBuckets', { + service: new cdk.CfnParameter(stack, 'Service').valueAsString, + action: new cdk.CfnParameter(stack, 'Action').valueAsString, + iamResources: ['*'], + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definition: task, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::aws-sdk:', + { + Ref: 'Service', + }, + ':', + { + Ref: 'Action', + }, + ], + ], + }, + End: true, + Parameters: {}, + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.expected.json new file mode 100644 index 0000000000000..38f975ca5ec03 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.expected.json @@ -0,0 +1,161 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:putObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:getObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:deleteObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"PutObject\",\"States\":{\"PutObject\":{\"Next\":\"GetObject\",\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::aws-sdk:s3:putObject\",\"Parameters\":{\"Body.$\":\"$.body\",\"Bucket\":\"", + { + "Ref": "Bucket83908E77" + }, + "\",\"Key\":\"test.txt\"}},\"GetObject\":{\"Next\":\"DeleteObject\",\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::aws-sdk:s3:getObject\",\"Parameters\":{\"Bucket\":\"", + { + "Ref": "Bucket83908E77" + }, + "\",\"Key\":\"test.txt\"}},\"DeleteObject\":{\"End\":true,\"Type\":\"Task\",\"ResultPath\":null,\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::aws-sdk:s3:deleteObject\",\"Parameters\":{\"Bucket\":\"", + { + "Ref": "Bucket83908E77" + }, + "\",\"Key\":\"test.txt\"}}}}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "StateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.ts new file mode 100644 index 0000000000000..3c9302e389763 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/integ.call-aws-service.ts @@ -0,0 +1,65 @@ +import * as s3 from '@aws-cdk/aws-s3'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { JsonPath } from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +/** + * + * Stack verification steps: + * * aws stepfunctions start-execution --state-machine-arn --input {"body": "hello world!"} : should return execution arn + * * + * * aws stepfunctions describe-execution --execution-arn --query 'status': should return status as SUCCEEDED + * * aws stepfunctions describe-execution --execution-arn --query 'output': should return "hello world!" + */ +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) { + super(scope, id, props); + + const bucket = new s3.Bucket(this, 'Bucket'); + + const commonParameters = { + Bucket: bucket.bucketName, + Key: 'test.txt', + }; + + const iamResources = [bucket.arnForObjects('*')]; + + const putObject = new tasks.CallAwsService(this, 'PutObject', { + service: 's3', + action: 'putObject', + parameters: { + Body: sfn.JsonPath.stringAt('$.body'), + ...commonParameters, + }, + iamResources, + }); + + const getObject = new tasks.CallAwsService(this, 'GetObject', { + service: 's3', + action: 'getObject', + parameters: commonParameters, + iamResources, + }); + + const deleteObject = new tasks.CallAwsService(this, 'DeleteObject', { + service: 's3', + action: 'deleteObject', + parameters: commonParameters, + iamResources, + resultPath: JsonPath.DISCARD, + }); + + const stateMachine = new sfn.StateMachine(this, 'StateMachine', { + definition: putObject.next(getObject).next(deleteObject), + }); + + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + }); + } +} + +const app = new cdk.App(); +new TestStack(app, 'aws-stepfunctions-aws-sdk-integ'); +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts index 1d290e8bba8eb..9b3e4ab8667af 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts @@ -3,6 +3,7 @@ import * as batch from '@aws-cdk/aws-batch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -34,266 +35,268 @@ beforeEach(() => { }); }); -test('Task with only the required parameters', () => { +describeDeprecated('RunBatchJob', () => { + test('Task with only the required parameters', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob.sync', - ], - ], - }, - End: true, - Parameters: { - JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, - JobName: 'JobName', - JobQueue: { Ref: 'JobQueueEE3AD499' }, - }, - }); -}); - -test('Task with all the parameters', () => { - // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 15, - containerOverrides: { - command: ['sudo', 'rm'], - environment: { key: 'value' }, - instanceType: new ec2.InstanceType('MULTI'), - memory: 1024, - gpuCount: 1, - vcpus: 10, - }, - dependsOn: [{ jobId: '1234', type: 'some_type' }], - payload: { - foo: sfn.JsonPath.stringAt('$.bar'), - }, - attempts: 3, - timeout: cdk.Duration.seconds(60), - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob', - ], - ], - }, - End: true, - Parameters: { - JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, - JobName: 'JobName', - JobQueue: { Ref: 'JobQueueEE3AD499' }, - ArrayProperties: { Size: 15 }, - ContainerOverrides: { - Command: ['sudo', 'rm'], - Environment: [{ Name: 'key', Value: 'value' }], - InstanceType: 'MULTI', - Memory: 1024, - ResourceRequirements: [{ Type: 'GPU', Value: '1' }], - Vcpus: 10, - }, - DependsOn: [{ JobId: '1234', Type: 'some_type' }], - Parameters: { 'foo.$': '$.bar' }, - RetryStrategy: { Attempts: 3 }, - Timeout: { AttemptDurationSeconds: 60 }, - }, - }); -}); - -test('supports tokens', () => { - // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobQueueArn: batchJobQueue.jobQueueArn, - jobName: sfn.JsonPath.stringAt('$.jobName'), - arraySize: sfn.JsonPath.numberAt('$.arraySize'), - timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), - attempts: sfn.JsonPath.numberAt('$.attempts'), - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob.sync', - ], - ], - }, - End: true, - Parameters: { - 'JobDefinition': { Ref: 'JobDefinition24FFE3ED' }, - 'JobName.$': '$.jobName', - 'JobQueue': { Ref: 'JobQueueEE3AD499' }, - 'ArrayProperties': { - 'Size.$': '$.arraySize', - }, - 'RetryStrategy': { - 'Attempts.$': '$.attempts', - }, - 'Timeout': { - 'AttemptDurationSeconds.$': '$.timeout', - }, - }, - }); -}); - -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, }), }); - }).toThrow( - /Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call RunBatchJob./i, - ); -}); -test('Task throws if environment in containerOverrides contain env with name starting with AWS_BATCH', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob.sync', + ], + ], + }, + End: true, + Parameters: { + JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, + JobName: 'JobName', + JobQueue: { Ref: 'JobQueueEE3AD499' }, + }, + }); + }); + + test('Task with all the parameters', () => { + // WHEN + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 15, containerOverrides: { - environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, + command: ['sudo', 'rm'], + environment: { key: 'value' }, + instanceType: new ec2.InstanceType('MULTI'), + memory: 1024, + gpuCount: 1, + vcpus: 10, + }, + dependsOn: [{ jobId: '1234', type: 'some_type' }], + payload: { + foo: sfn.JsonPath.stringAt('$.bar'), }, + attempts: 3, + timeout: cdk.Duration.seconds(60), + integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, }), }); - }).toThrow( - /Invalid environment variable name: AWS_BATCH_MY_NAME. Environment variable names starting with 'AWS_BATCH' are reserved./i, - ); -}); -test('Task throws if arraySize is out of limits 2-10000', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 1, - }), + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob', + ], + ], + }, + End: true, + Parameters: { + JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, + JobName: 'JobName', + JobQueue: { Ref: 'JobQueueEE3AD499' }, + ArrayProperties: { Size: 15 }, + ContainerOverrides: { + Command: ['sudo', 'rm'], + Environment: [{ Name: 'key', Value: 'value' }], + InstanceType: 'MULTI', + Memory: 1024, + ResourceRequirements: [{ Type: 'GPU', Value: '1' }], + Vcpus: 10, + }, + DependsOn: [{ JobId: '1234', Type: 'some_type' }], + Parameters: { 'foo.$': '$.bar' }, + RetryStrategy: { Attempts: 3 }, + Timeout: { AttemptDurationSeconds: 60 }, + }, }); - }).toThrow( - /arraySize must be between 2 and 10,000/, - ); + }); - expect(() => { - new sfn.Task(stack, 'Task', { + test('supports tokens', () => { + // WHEN + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 10001, + jobName: sfn.JsonPath.stringAt('$.jobName'), + arraySize: sfn.JsonPath.numberAt('$.arraySize'), + timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), + attempts: sfn.JsonPath.numberAt('$.attempts'), }), }); - }).toThrow( - /arraySize must be between 2 and 10,000/, - ); -}); -test('Task throws if dependencies exceeds 20', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - dependsOn: [...Array(21).keys()].map(i => ({ - jobId: `${i}`, - type: `some_type-${i}`, - })), - }), + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob.sync', + ], + ], + }, + End: true, + Parameters: { + 'JobDefinition': { Ref: 'JobDefinition24FFE3ED' }, + 'JobName.$': '$.jobName', + 'JobQueue': { Ref: 'JobQueueEE3AD499' }, + 'ArrayProperties': { + 'Size.$': '$.arraySize', + }, + 'RetryStrategy': { + 'Attempts.$': '$.attempts', + }, + 'Timeout': { + 'AttemptDurationSeconds.$': '$.timeout', + }, + }, }); - }).toThrow( - /dependencies must be 20 or less/, - ); -}); + }); -test('Task throws if attempts is out of limits 1-10', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - attempts: 0, - }), - }); - }).toThrow( - /attempts must be between 1 and 10/, - ); + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow( + /Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call RunBatchJob./i, + ); + }); - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - attempts: 11, - }), - }); - }).toThrow( - /attempts must be between 1 and 10/, - ); -}); + test('Task throws if environment in containerOverrides contain env with name starting with AWS_BATCH', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + containerOverrides: { + environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, + }, + }), + }); + }).toThrow( + /Invalid environment variable name: AWS_BATCH_MY_NAME. Environment variable names starting with 'AWS_BATCH' are reserved./i, + ); + }); -test('Task throws if timeout is less than 60 sec', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - timeout: cdk.Duration.seconds(59), - }), - }); - }).toThrow( - /timeout must be greater than 60 seconds/, - ); -}); + test('Task throws if arraySize is out of limits 2-10000', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 1, + }), + }); + }).toThrow( + /arraySize must be between 2 and 10,000/, + ); + + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 10001, + }), + }); + }).toThrow( + /arraySize must be between 2 and 10,000/, + ); + }); + + test('Task throws if dependencies exceeds 20', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + dependsOn: [...Array(21).keys()].map(i => ({ + jobId: `${i}`, + type: `some_type-${i}`, + })), + }), + }); + }).toThrow( + /dependencies must be 20 or less/, + ); + }); + + test('Task throws if attempts is out of limits 1-10', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + attempts: 0, + }), + }); + }).toThrow( + /attempts must be between 1 and 10/, + ); + + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + attempts: 11, + }), + }); + }).toThrow( + /attempts must be between 1 and 10/, + ); + }); + + test('Task throws if timeout is less than 60 sec', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + timeout: cdk.Duration.seconds(59), + }), + }); + }).toThrow( + /timeout must be greater than 60 seconds/, + ); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json index 094221d1ba6bb..a8b7510287767 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json @@ -193,7 +193,7 @@ { "Ref": "AWS::Partition" }, - ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"},\"Activated\":{\"BOOL.$\":\"$.foo\"}},\"TableName\":\"", + ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"},\"Activated\":{\"BOOL.$\":\"$.foo\"},\"List\":{\"L.$\":\"$.list\"}},\"TableName\":\"", { "Ref": "Messages804FA4EB" }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts index e78bba4f6721e..18be323c22db3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts @@ -37,6 +37,7 @@ class CallDynamoDBStack extends cdk.Stack { Text: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.bar')), TotalCount: tasks.DynamoAttributeValue.fromNumber(firstNumber), Activated: tasks.DynamoAttributeValue.booleanFromJsonPath(sfn.JsonPath.stringAt('$.foo')), + List: tasks.DynamoAttributeValue.listFromJsonPath(sfn.JsonPath.stringAt('$.list')), }, table, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts index f0810de3e3949..a882fb9f99b2b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts @@ -222,6 +222,22 @@ describe('DynamoAttributeValue', () => { }); }); + test('from list with json path', () => { + // GIVEN + const m = '$.path'; + // WHEN + const attribute = tasks.DynamoAttributeValue.listFromJsonPath( + sfn.JsonPath.stringAt(m), + ); + + // THEN + expect(sfn.FieldUtils.renderObject(attribute)).toEqual({ + attributeValue: { + 'L.$': m, + }, + }); + }); + test('from null', () => { // WHEN const attribute = tasks.DynamoAttributeValue.fromNull(true); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts index 74344f8139758..adc18b68f9e13 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts @@ -1,7 +1,9 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -14,381 +16,420 @@ beforeEach(() => { stack = new Stack(); vpc = new ec2.Vpc(stack, 'Vpc'); cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium'), - }); -}); - -test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.EC2, - }); - taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); - - expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/not configured for compatibility with Fargate/); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Capacity', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: new ec2.InstanceType('t3.medium'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }), + })); }); -test('Cannot create a Fargate task without a default container', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.FARGATE, - }); - expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/must have at least one essential container/); -}); +describeDeprecated('ecs-tasks', () => { + test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.EC2, + }); + taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); -test('Running a Fargate Task', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.FARGATE, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, + expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/not configured for compatibility with Fargate/); }); - // WHEN - const runTask = new sfn.Task(stack, 'RunFargate', { - task: new tasks.RunEcsFargateTask({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - environment: [ - { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, - ], - }, - ], - }), + test('Cannot create a Fargate task without a default container', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.FARGATE, + }); + expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/must have at least one essential container/); }); - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + test('Running a Fargate Task', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.FARGATE, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'FARGATE', - NetworkConfiguration: { - AwsvpcConfiguration: { - SecurityGroups: [{ 'Fn::GetAtt': ['RunFargateSecurityGroup709740F2', 'GroupId'] }], - Subnets: [{ Ref: 'VpcPrivateSubnet1Subnet536B997A' }, { Ref: 'VpcPrivateSubnet2Subnet3788AAA1' }], - }, - }, - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ + // WHEN + const runTask = new sfn.Task(stack, 'RunFargate', { + task: new tasks.RunEcsFargateTask({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ { - Environment: [ - { - 'Name': 'SOME_KEY', - 'Value.$': '$.SomeKey', - }, + containerDefinition, + environment: [ + { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, ], - Name: 'TheContainer', }, ], + }), + }); + + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); + + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'FARGATE', + NetworkConfiguration: { + AwsvpcConfiguration: { + SecurityGroups: [{ 'Fn::GetAtt': ['RunFargateSecurityGroup709740F2', 'GroupId'] }], + Subnets: [{ Ref: 'VpcPrivateSubnet1Subnet536B997A' }, { Ref: 'VpcPrivateSubnet2Subnet3788AAA1' }], + }, + }, + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + 'Name': 'SOME_KEY', + 'Value.$': '$.SomeKey', + }, + ], + Name: 'TheContainer', + }, + ], + }, }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], + ], + }, + Type: 'Task', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'ecs:RunTask', + Effect: 'Allow', + Resource: { Ref: 'TD49C78F36' }, + }, + { + Action: ['ecs:StopTask', 'ecs:DescribeTasks'], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], + }, { - Ref: 'AWS::Partition', + Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':events:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':rule/StepFunctionsGetEventsForECSTaskRule', + ], + ], + }, }, - ':states:::ecs:runTask.sync', ], - ], - }, - Type: 'Task', + }, + }); }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'ecs:RunTask', - Effect: 'Allow', - Resource: { Ref: 'TD49C78F36' }, - }, - { - Action: ['ecs:StopTask', 'ecs:DescribeTasks'], - Effect: 'Allow', - Resource: '*', - }, - { - Action: 'iam:PassRole', - Effect: 'Allow', - Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], - }, - { - Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':events:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':rule/StepFunctionsGetEventsForECSTaskRule', - ], + test('Running an EC2 Task with bridge network', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + // WHEN + const runTask = new sfn.Task(stack, 'Run', { + task: new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ + { + containerDefinition, + environment: [ + { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, ], }, - }, - ], - }, - }); -}); + ], + }), + }); -test('Running an EC2 Task with bridge network', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { - task: new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - environment: [ - { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + 'Name': 'SOME_KEY', + 'Value.$': '$.SomeKey', + }, + ], + Name: 'TheContainer', + }, ], }, - ], - }), - }); - - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], + ], + }, + Type: 'Task', + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ { - Environment: [ - { - 'Name': 'SOME_KEY', - 'Value.$': '$.SomeKey', - }, - ], - Name: 'TheContainer', + Action: 'ecs:RunTask', + Effect: 'Allow', + Resource: { Ref: 'TD49C78F36' }, }, - ], - }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', { - Ref: 'AWS::Partition', + Action: ['ecs:StopTask', 'ecs:DescribeTasks'], + Effect: 'Allow', + Resource: '*', }, - ':states:::ecs:runTask.sync', - ], - ], - }, - Type: 'Task', - }); - - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'ecs:RunTask', - Effect: 'Allow', - Resource: { Ref: 'TD49C78F36' }, - }, - { - Action: ['ecs:StopTask', 'ecs:DescribeTasks'], - Effect: 'Allow', - Resource: '*', - }, - { - Action: 'iam:PassRole', - Effect: 'Allow', - Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], - }, - { - Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':events:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':rule/StepFunctionsGetEventsForECSTaskRule', + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], + }, + { + Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':events:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':rule/StepFunctionsGetEventsForECSTaskRule', + ], ], - ], + }, }, - }, - ], - }, + ], + }, + }); }); -}); -test('Running an EC2 Task with placement strategies', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + test('Running an EC2 Task with placement strategies', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - const ec2Task = new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - placementStrategies: [ecs.PlacementStrategy.spreadAcrossInstances(), ecs.PlacementStrategy.packedByCpu(), ecs.PlacementStrategy.randomly()], - placementConstraints: [ecs.PlacementConstraint.memberOf('blieptuut')], - }); + const ec2Task = new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + placementStrategies: [ecs.PlacementStrategy.spreadAcrossInstances(), ecs.PlacementStrategy.packedByCpu(), ecs.PlacementStrategy.randomly()], + placementConstraints: [ecs.PlacementConstraint.memberOf('blieptuut')], + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - PlacementConstraints: [{ Type: 'memberOf', Expression: 'blieptuut' }], - PlacementStrategy: [{ Field: 'instanceId', Type: 'spread' }, { Field: 'cpu', Type: 'binpack' }, { Type: 'random' }], - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::ecs:runTask.sync', + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + PlacementConstraints: [{ Type: 'memberOf', Expression: 'blieptuut' }], + PlacementStrategy: [{ Field: 'instanceId', Type: 'spread' }, { Field: 'cpu', Type: 'binpack' }, { Type: 'random' }], + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], ], - ], - }, - Type: 'Task', + }, + Type: 'Task', + }); }); -}); -test('Running an EC2 Task with overridden number values', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + test('Running an EC2 Task with overridden number values', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - const ec2Task = new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - command: sfn.JsonPath.listAt('$.TheCommand'), - cpu: 5, - memoryLimit: sfn.JsonPath.numberAt('$.MemoryLimit'), - }, - ], - }); + const ec2Task = new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ + { + containerDefinition, + command: sfn.JsonPath.listAt('$.TheCommand'), + cpu: 5, + memoryLimit: sfn.JsonPath.numberAt('$.MemoryLimit'), + }, + ], + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ - { - 'Command.$': '$.TheCommand', - 'Cpu': 5, - 'Memory.$': '$.MemoryLimit', - 'Name': 'TheContainer', - }, + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + 'Command.$': '$.TheCommand', + 'Cpu': 5, + 'Memory.$': '$.MemoryLimit', + 'Name': 'TheContainer', + }, + ], + }, + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], ], }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', + Type: 'Task', + }); + }); + + test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { + compatibility: ecs.Compatibility.EC2, + }); + + const containerDefinition = taskDefinition.addContainer('ContainerDefinition', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + }); + + expect(() => + new tasks.RunEcsEc2Task({ + cluster, + containerOverrides: [ { - Ref: 'AWS::Partition', + containerDefinition, + environment: [ + { + name: 'Foo', + value: 'Bar', + }, + ], }, - ':states:::ecs:runTask.sync', ], - ], - }, - Type: 'Task', + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + taskDefinition, + }), + ).toThrowError(/Task Token is required in at least one `containerOverrides.environment`/); }); -}); -test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { - compatibility: ecs.Compatibility.EC2, - }); + test('Running a task with WAIT_FOR_TASK_TOKEN and task token in environment', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { + compatibility: ecs.Compatibility.EC2, + }); - const containerDefinition = taskDefinition.addContainer('ContainerDefinition', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - }); + const primaryContainerDef = taskDefinition.addContainer('PrimaryContainerDef', { + image: ecs.ContainerImage.fromRegistry('foo/primary'), + essential: true, + }); + + const sidecarContainerDef = taskDefinition.addContainer('SideCarContainerDef', { + image: ecs.ContainerImage.fromRegistry('foo/sidecar'), + essential: false, + }); - expect(() => - new tasks.RunEcsEc2Task({ + expect(() => new tasks.RunEcsEc2Task({ cluster, containerOverrides: [ { - containerDefinition, + containerDefinition: primaryContainerDef, environment: [ { name: 'Foo', @@ -396,51 +437,18 @@ test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', ( }, ], }, + { + containerDefinition: sidecarContainerDef, + environment: [ + { + name: 'TaskToken.$', + value: sfn.JsonPath.taskToken, + }, + ], + }, ], integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, taskDefinition, - }), - ).toThrowError(/Task Token is required in at least one `containerOverrides.environment`/); -}); - -test('Running a task with WAIT_FOR_TASK_TOKEN and task token in environment', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { - compatibility: ecs.Compatibility.EC2, - }); - - const primaryContainerDef = taskDefinition.addContainer('PrimaryContainerDef', { - image: ecs.ContainerImage.fromRegistry('foo/primary'), - essential: true, + })).not.toThrow(); }); - - const sidecarContainerDef = taskDefinition.addContainer('SideCarContainerDef', { - image: ecs.ContainerImage.fromRegistry('foo/sidecar'), - essential: false, - }); - - expect(() => new tasks.RunEcsEc2Task({ - cluster, - containerOverrides: [ - { - containerDefinition: primaryContainerDef, - environment: [ - { - name: 'Foo', - value: 'Bar', - }, - ], - }, - { - containerDefinition: sidecarContainerDef, - environment: [ - { - name: 'TaskToken.$', - value: sfn.JsonPath.taskToken, - }, - ], - }, - ], - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - taskDefinition, - })).not.toThrow(); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts index 8e93cdd6d0aab..a1ff441ec9654 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts @@ -1,4 +1,5 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; @@ -16,9 +17,13 @@ beforeEach(() => { stack = new Stack(); vpc = new ec2.Vpc(stack, 'Vpc'); cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Capacity', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: new ec2.InstanceType('t3.medium'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }), + })); }); test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { @@ -196,7 +201,7 @@ test('Running a Fargate Task', () => { Type: 'Task', }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -318,7 +323,7 @@ test('Running an EC2 Task with bridge network', () => { Type: 'Task', }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts index 56e970d049b51..4f2c2f9c2b2d6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -305,7 +305,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts index bd9bcce3bef10..16cfac3d07327 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -51,7 +51,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts index 3effd99e5eed0..3d846ec1d06d2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; @@ -605,7 +605,7 @@ test('Create Cluster without Roles', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -620,7 +620,7 @@ test('Create Cluster without Roles', () => { // The stack renders the ec2.amazonaws.com Service principal id with a // Join to the URLSuffix - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -646,7 +646,7 @@ test('Create Cluster without Roles', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts index 85bb737641440..33fc2a2f14f81 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -59,7 +59,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts index f2188455cd24b..4a44d0d64cd29 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -95,7 +95,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts index 9d4f1523378d3..8eb439d65e6f7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -51,7 +51,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts index a7c2e795f7386..0b3391f24835b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -50,7 +50,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts index f56ce0d3bce70..95e27f0e71812 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; @@ -18,7 +18,7 @@ test('Eval with Node.js', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', @@ -33,7 +33,7 @@ test('Eval with Node.js', () => { }, }); - expect(stack).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { Runtime: 'nodejs14.x', }); }); @@ -47,7 +47,7 @@ test('expression does not contain paths', () => { definition: task, }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', @@ -72,7 +72,7 @@ test('with dash and underscore in path', () => { definition: task, }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts index be513fee1b0da..1b1c3f6ebe669 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as events from '@aws-cdk/aws-events'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; @@ -238,7 +238,7 @@ describe('Put Events', () => { new sfn.StateMachine(stack, 'State Machine', { definition: task }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts index 47dd07fc83d9e..68baa9ef20804 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts @@ -1,5 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -9,133 +10,135 @@ beforeEach(() => { stack = new Stack(); }); -test('Invoke glue job with just job ARN', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); +describeDeprecated('RunGlueJobTask', () => { + test('Invoke glue job with just job ARN', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::glue:startJobRun', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun', + ], ], - ], - }, - End: true, - Parameters: { - JobName: jobName, - }, + }, + End: true, + Parameters: { + JobName: jobName, + }, + }); }); -}); -test('Invoke glue job with full properties', () => { - const jobArguments = { - key: 'value', - }; - const timeoutMinutes = 1440; - const timeout = Duration.minutes(timeoutMinutes); - const securityConfiguration = 'securityConfiguration'; - const notifyDelayAfterMinutes = 10; - const notifyDelayAfter = Duration.minutes(notifyDelayAfterMinutes); - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - arguments: jobArguments, - timeout, - securityConfiguration, - notifyDelayAfter, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('Invoke glue job with full properties', () => { + const jobArguments = { + key: 'value', + }; + const timeoutMinutes = 1440; + const timeout = Duration.minutes(timeoutMinutes); + const securityConfiguration = 'securityConfiguration'; + const notifyDelayAfterMinutes = 10; + const notifyDelayAfter = Duration.minutes(notifyDelayAfterMinutes); + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + arguments: jobArguments, + timeout, + securityConfiguration, + notifyDelayAfter, + }), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::glue:startJobRun.sync', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun.sync', + ], ], - ], - }, - End: true, - Parameters: { - JobName: jobName, - Arguments: jobArguments, - Timeout: timeoutMinutes, - SecurityConfiguration: securityConfiguration, - NotificationProperty: { - NotifyDelayAfter: notifyDelayAfterMinutes, }, - }, - }); -}); - -test('permitted role actions limited to start job run if service integration pattern is FIRE_AND_FORGET', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); - - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [{ - Action: 'glue:StartJobRun', - }], - }, + End: true, + Parameters: { + JobName: jobName, + Arguments: jobArguments, + Timeout: timeoutMinutes, + SecurityConfiguration: securityConfiguration, + NotificationProperty: { + NotifyDelayAfter: notifyDelayAfterMinutes, + }, + }, + }); }); -}); -test('permitted role actions include start, get, and stop job run if service integration pattern is SYNC', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('permitted role actions limited to start job run if service integration pattern is FIRE_AND_FORGET', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, + }), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [{ - Action: [ - 'glue:StartJobRun', - 'glue:GetJobRun', - 'glue:GetJobRuns', - 'glue:BatchStopJobRun', - ], - }], - }, + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [Match.objectLike({ + Action: 'glue:StartJobRun', + })], + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + test('permitted role actions include start, get, and stop job run if service integration pattern is SYNC', () => { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, }), }); - }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i); -}); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [Match.objectLike({ + Action: [ + 'glue:StartJobRun', + 'glue:GetJobRun', + 'glue:GetJobRuns', + 'glue:BatchStopJobRun', + ], + })], + }, + }); + }); + + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts index b7a920a1abe9e..269a92774a0a1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Duration, Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -129,11 +129,11 @@ test('permitted role actions limited to start job run if service integration pat definition: task, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: [{ + Statement: [Match.objectLike({ Action: 'glue:StartJobRun', - }], + })], }, }); }); @@ -148,16 +148,16 @@ test('permitted role actions include start, get, and stop job run if service int definition: task, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: [{ + Statement: [Match.objectLike({ Action: [ 'glue:StartJobRun', 'glue:GetJobRun', 'glue:GetJobRuns', 'glue:BatchStopJobRun', ], - }], + })], }, }); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts index 926593e2e9d9c..6a39717ae11d7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts @@ -1,64 +1,67 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; -test('Activity can be used in a Task', () => { +describeDeprecated('InvokeActivity', () => { + test('Activity can be used in a Task', () => { // GIVEN - const stack = new Stack(); + const stack = new Stack(); - // WHEN - const activity = new sfn.Activity(stack, 'Activity'); - const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeActivity(activity) }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + // WHEN + const activity = new sfn.Activity(stack, 'Activity'); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeActivity(activity) }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', - { Ref: 'Activity04690B0A' }, - '"}}}', - ]], - }, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', + { Ref: 'Activity04690B0A' }, + '"}}}', + ]], + }, + }); }); -}); -test('Activity Task metrics and Activity metrics are the same', () => { + test('Activity Task metrics and Activity metrics are the same', () => { // GIVEN - const stack = new Stack(); - const activity = new sfn.Activity(stack, 'Activity'); - const task = new sfn.Task(stack, 'Invoke', { task: new tasks.InvokeActivity(activity) }); + const stack = new Stack(); + const activity = new sfn.Activity(stack, 'Activity'); + const task = new sfn.Task(stack, 'Invoke', { task: new tasks.InvokeActivity(activity) }); - // WHEN - const activityMetrics = [ - activity.metricFailed(), - activity.metricHeartbeatTimedOut(), - activity.metricRunTime(), - activity.metricScheduled(), - activity.metricScheduleTime(), - activity.metricStarted(), - activity.metricSucceeded(), - activity.metricTime(), - activity.metricTimedOut(), - ]; + // WHEN + const activityMetrics = [ + activity.metricFailed(), + activity.metricHeartbeatTimedOut(), + activity.metricRunTime(), + activity.metricScheduled(), + activity.metricScheduleTime(), + activity.metricStarted(), + activity.metricSucceeded(), + activity.metricTime(), + activity.metricTimedOut(), + ]; - const taskMetrics = [ - task.metricFailed(), - task.metricHeartbeatTimedOut(), - task.metricRunTime(), - task.metricScheduled(), - task.metricScheduleTime(), - task.metricStarted(), - task.metricSucceeded(), - task.metricTime(), - task.metricTimedOut(), - ]; + const taskMetrics = [ + task.metricFailed(), + task.metricHeartbeatTimedOut(), + task.metricRunTime(), + task.metricScheduled(), + task.metricScheduleTime(), + task.metricStarted(), + task.metricSucceeded(), + task.metricTime(), + task.metricTimedOut(), + ]; - // THEN - for (let i = 0; i < activityMetrics.length; i++) { - expect(activityMetrics[i]).toEqual(taskMetrics[i]); - } -}); + // THEN + for (let i = 0; i < activityMetrics.length; i++) { + expect(activityMetrics[i]).toEqual(taskMetrics[i]); + } + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts index 04f86319088ce..940b45bd7486b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts @@ -1,6 +1,7 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -15,43 +16,45 @@ beforeEach(() => { }); }); -test('Invoke lambda with function ARN', () => { +describeDeprecated('InvokeFunction', () => { + test('Invoke lambda with function ARN', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', - { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, - '"}}}', - ]], - }, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', + { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, + '"}}}', + ]], + }, + }); }); -}); -test('Lambda function payload ends up in Parameters', () => { - new sfn.StateMachine(stack, 'SM', { - definition: new sfn.Task(stack, 'Task', { - task: new tasks.InvokeFunction(fn, { - payload: { - foo: sfn.JsonPath.stringAt('$.bar'), - }, + test('Lambda function payload ends up in Parameters', () => { + new sfn.StateMachine(stack, 'SM', { + definition: new sfn.Task(stack, 'Task', { + task: new tasks.InvokeFunction(fn, { + payload: { + foo: sfn.JsonPath.stringAt('$.bar'), + }, + }), }), - }), - }); + }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"foo.$":"$.bar"},"Type":"Task","Resource":"', - { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, - '"}}}', - ]], - }, + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"foo.$":"$.bar"},"Type":"Task","Resource":"', + { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, + '"}}}', + ]], + }, + }); }); -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts index 4accba8d46e22..cbbd0092706ab 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts index d52099e90906e..7aa2ec16dccac 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts @@ -1,6 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -15,171 +15,173 @@ beforeEach(() => { }); }); -test('Invoke lambda with default magic ARN', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - payload: sfn.TaskInput.fromObject({ - foo: 'bar', +describeDeprecated('run lambda task', () => { + test('Invoke lambda with default magic ARN', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + invocationType: tasks.InvocationType.REQUEST_RESPONSE, + clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', + qualifier: '1', }), - invocationType: tasks.InvocationType.REQUEST_RESPONSE, - clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', - qualifier: '1', - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], ], - ], - }, - End: true, - Parameters: { - FunctionName: { - Ref: 'Fn9270CBC0', }, - Payload: { - foo: 'bar', + End: true, + Parameters: { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + Payload: { + foo: 'bar', + }, + InvocationType: 'RequestResponse', + ClientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', + Qualifier: '1', }, - InvocationType: 'RequestResponse', - ClientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', - Qualifier: '1', - }, + }); }); -}); -test('Lambda function can be used in a Task with Task Token', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - payload: sfn.TaskInput.fromObject({ - token: sfn.JsonPath.taskToken, + test('Lambda function can be used in a Task with Task Token', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + payload: sfn.TaskInput.fromObject({ + token: sfn.JsonPath.taskToken, + }), }), - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke.waitForTaskToken', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - FunctionName: { - Ref: 'Fn9270CBC0', }, - Payload: { - 'token.$': '$$.Task.Token', + End: true, + Parameters: { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + Payload: { + 'token.$': '$$.Task.Token', + }, }, - }, + }); }); -}); -test('Lambda function is invoked with the state input as payload by default', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('Lambda function is invoked with the state input as payload by default', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], ], - ], - }, - End: true, - Parameters: { - 'FunctionName': { - Ref: 'Fn9270CBC0', }, - 'Payload.$': '$', - }, - }); -}); - -test('Lambda function can be provided with the state input as the payload', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - payload: sfn.TaskInput.fromJsonPathAt('$'), - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); - - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', - ], - ], - }, - End: true, - Parameters: { - 'FunctionName': { - Ref: 'Fn9270CBC0', + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', }, - 'Payload.$': '$', - }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in payLoad', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + test('Lambda function can be provided with the state input as the payload', () => { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + payload: sfn.TaskInput.fromJsonPathAt('$'), }), }); - }).toThrow(/Task Token is missing in payload/i); -}); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], + ], + }, + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', + }, }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call Lambda./i); -}); + }); + + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in payLoad', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow(/Task Token is missing in payload/i); + }); + + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call Lambda./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts index 1c61906207399..39c9f9a02278b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as sfn from '@aws-cdk/aws-stepfunctions'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts index ee543a7fb251f..a48703d51df61 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts index cea5b585f83f7..69c941b385579 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts index 59f5a1bb36229..7e7a5eff26348 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -142,6 +141,7 @@ test('create complex training job', () => { }, ], }, + enableNetworkIsolation: true, hyperparameters: { lr: '0.1', }, @@ -221,6 +221,7 @@ test('create complex training job', () => { { Name: 'mymetric', Regex: 'regex_pattern' }, ], }, + EnableNetworkIsolation: true, HyperParameters: { lr: '0.1', }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts index 16f499e53708c..a3fdf27ba7dbb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts index ee89086763aa6..90a58850e64a5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts index 47bd200c97daa..59638c06d52d1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts @@ -1,146 +1,149 @@ import * as sns from '@aws-cdk/aws-sns'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; -test('Publish literal message to SNS topic', () => { +describeDeprecated('PublishToTopic', () => { + test('Publish literal message to SNS topic', () => { // GIVEN - const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); - - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromText('Publish this message'), - }), - }); - - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish', - ], - ], - }, - End: true, - Parameters: { - TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: 'Publish this message', - }, - }); -}); - -test('Publish JSON to SNS topic with task token', () => { - // GIVEN - const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - message: sfn.TaskInput.fromObject({ - Input: 'Publish this message', - Token: sfn.JsonPath.taskToken, + // WHEN + const pub = new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + message: sfn.TaskInput.fromText('Publish this message'), }), - }), - }); + }); - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish.waitForTaskToken', + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish', + ], ], - ], - }, - End: true, - Parameters: { - TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: { - 'Input': 'Publish this message', - 'Token.$': '$$.Task.Token', }, - }, + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: 'Publish this message', + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in message', () => { - expect(() => { - // GIVEN + test('Publish JSON to SNS topic with task token', () => { + // GIVEN const stack = new cdk.Stack(); const topic = new sns.Topic(stack, 'Topic'); + // WHEN - new sfn.Task(stack, 'Publish', { + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - message: sfn.TaskInput.fromText('Publish this message'), + message: sfn.TaskInput.fromObject({ + Input: 'Publish this message', + Token: sfn.JsonPath.taskToken, + }), }), }); - // THEN - }).toThrow(/Task Token is missing in message/i); -}); -test('Publish to topic with ARN from payload', () => { - // GIVEN - const stack = new cdk.Stack(); - const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); - - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromText('Publish this message'), - }), + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish.waitForTaskToken', + ], + ], + }, + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: { + 'Input': 'Publish this message', + 'Token.$': '$$.Task.Token', + }, + }, + }); }); - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish', - ], - ], - }, - End: true, - Parameters: { - 'TopicArn.$': '$.topicArn', - 'Message': 'Publish this message', - }, + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in message', () => { + expect(() => { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + // WHEN + new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + message: sfn.TaskInput.fromText('Publish this message'), + }), + }); + // THEN + }).toThrow(/Task Token is missing in message/i); }); -}); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { + test('Publish to topic with ARN from payload', () => { + // GIVEN const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); + const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); - new sfn.Task(stack, 'Publish', { + // WHEN + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, message: sfn.TaskInput.fromText('Publish this message'), }), }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SNS./i); -}); + + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish', + ], + ], + }, + End: true, + Parameters: { + 'TopicArn.$': '$.topicArn', + 'Message': 'Publish this message', + }, + }); + }); + + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + message: sfn.TaskInput.fromText('Publish this message'), + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SNS./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts index 9e168d68705a6..abf2fdd365268 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts @@ -1,5 +1,6 @@ import * as sqs from '@aws-cdk/aws-sqs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -12,200 +13,202 @@ beforeEach(() => { queue = new sqs.Queue(stack, 'Queue'); }); -test('Send message to queue', () => { +describeDeprecated('SendToQueue', () => { + test('Send message to queue', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromText('Send this message'), - messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), - }), - }); + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromText('Send this message'), + messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), + }), + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - 'QueueUrl': { Ref: 'Queue4A7E3555' }, - 'MessageBody': 'Send this message', - 'MessageDeduplicationId.$': '$.deduping', - }, + }, + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody': 'Send this message', + 'MessageDeduplicationId.$': '$.deduping', + }, + }); }); -}); -test('Send message to SQS queue with task token', () => { + test('Send message to SQS queue with task token', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - messageBody: sfn.TaskInput.fromObject({ - Input: 'Send this message', - Token: sfn.JsonPath.taskToken, + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + messageBody: sfn.TaskInput.fromObject({ + Input: 'Send this message', + Token: sfn.JsonPath.taskToken, + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage.waitForTaskToken', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - 'Input': 'Send this message', - 'Token.$': '$$.Task.Token', }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + 'Input': 'Send this message', + 'Token.$': '$$.Task.Token', + }, + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in messageBody', () => { - expect(() => { + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in messageBody', () => { + expect(() => { // WHEN - new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - messageBody: sfn.TaskInput.fromText('Send this message'), - }), - }); + new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + messageBody: sfn.TaskInput.fromText('Send this message'), + }), + }); // THEN - }).toThrow(/Task Token is missing in messageBody/i); -}); + }).toThrow(/Task Token is missing in messageBody/i); + }); -test('Message body can come from state', () => { + test('Message body can come from state', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromJsonPathAt('$.theMessage'), - }), - }); + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromJsonPathAt('$.theMessage'), + }), + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - 'QueueUrl': { Ref: 'Queue4A7E3555' }, - 'MessageBody.$': '$.theMessage', - }, + }, + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody.$': '$.theMessage', + }, + }); }); -}); -test('Message body can be an object', () => { + test('Message body can be an object', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromObject({ - literal: 'literal', - SomeInput: sfn.JsonPath.stringAt('$.theMessage'), + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromObject({ + literal: 'literal', + SomeInput: sfn.JsonPath.stringAt('$.theMessage'), + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - 'literal': 'literal', - 'SomeInput.$': '$.theMessage', }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + 'literal': 'literal', + 'SomeInput.$': '$.theMessage', + }, + }, + }); }); -}); -test('Message body object can contain references', () => { + test('Message body object can contain references', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromObject({ - queueArn: queue.queueArn, + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromObject({ + queueArn: queue.queueArn, + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - queueArn: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] }, }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + queueArn: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] }, + }, + }, + }); }); -}); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - messageBody: sfn.TaskInput.fromText('Send this message'), - }), - }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SQS./i); -}); + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + messageBody: sfn.TaskInput.fromText('Send this message'), + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SQS./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts index a1d5b582a62ba..f921a57e5b740 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts @@ -1,5 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; @@ -12,216 +13,218 @@ beforeEach(() => { }); }); -test('Execute State Machine - Default - Fire and Forget', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - input: { - foo: 'bar', - }, - name: 'myExecutionName', - }), - }); +describeDeprecated('StartExecution', () => { + test('Execute State Machine - Default - Fire and Forget', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + input: { + foo: 'bar', + }, + name: 'myExecutionName', + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution', + ], ], - ], - }, - End: true, - Parameters: { - Input: { - foo: 'bar', }, - Name: 'myExecutionName', - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', + End: true, + Parameters: { + Input: { + foo: 'bar', + }, + Name: 'myExecutionName', + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, + }); }); -}); -test('Execute State Machine - Sync', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), - }); + test('Execute State Machine - Sync', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution.sync', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution.sync', + ], ], - ], - }, - End: true, - Parameters: { - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', }, - }, - }); + End: true, + Parameters: { + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, + }, + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'states:StartExecution', - Effect: 'Allow', - Resource: { - Ref: 'ChildStateMachine9133117F', + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'states:StartExecution', + Effect: 'Allow', + Resource: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, - { - Action: [ - 'states:DescribeExecution', - 'states:StopExecution', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':execution:', - { - 'Fn::Select': [ - 6, - { - 'Fn::Split': [ - ':', - { - Ref: 'ChildStateMachine9133117F', - }, - ], - }, - ], - }, - '*', - ], + { + Action: [ + 'states:DescribeExecution', + 'states:StopExecution', ], - }, - }, - { - Action: [ - 'events:PutTargets', - 'events:PutRule', - 'events:DescribeRule', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':events:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':execution:', + { + 'Fn::Select': [ + 6, + { + 'Fn::Split': [ + ':', + { + Ref: 'ChildStateMachine9133117F', + }, + ], + }, + ], + }, + '*', + ], ], + }, + }, + { + Action: [ + 'events:PutTargets', + 'events:PutRule', + 'events:DescribeRule', ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':events:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + ], + ], + }, }, + ], + Version: '2012-10-17', + }, + Roles: [ + { + Ref: 'ParentStateMachineRoleE902D002', }, ], - Version: '2012-10-17', - }, - Roles: [ - { - Ref: 'ParentStateMachineRoleE902D002', - }, - ], + }); }); -}); -test('Execute State Machine - Wait For Task Token', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - input: { - token: sfn.JsonPath.taskToken, - }, - }), - }); + test('Execute State Machine - Wait For Task Token', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + input: { + token: sfn.JsonPath.taskToken, + }, + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution.waitForTaskToken', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - Input: { - 'token.$': '$$.Task.Token', }, - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', + End: true, + Parameters: { + Input: { + 'token.$': '$$.Task.Token', + }, + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, + }); }); -}); -test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { - expect(() => { - new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - }), - }); - }).toThrow('Task Token is missing in input (pass JsonPath.taskToken somewhere in input'); -}); + test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { + expect(() => { + new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow('Task Token is missing in input (pass JsonPath.taskToken somewhere in input'); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts similarity index 92% rename from packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts index fb7cc2302130b..dfcf84183155a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { StepFunctionsInvokeActivity } from '../../lib/stepfunctions/invoke-activity'; @@ -15,7 +15,7 @@ test('Activity can be used in a Task', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': ['', [ '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts index 33a70c17265b6..48e03fca934b9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { StepFunctionsStartExecution } from '../../lib/stepfunctions/start-execution'; @@ -85,7 +85,7 @@ test('Execute State Machine - Run Job', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 908d40d376dfd..e74367df84676 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -22,13 +22,10 @@ example](https://docs.aws.amazon.com/step-functions/latest/dg/job-status-poller- ## Example ```ts -import * as cdk from '@aws-cdk/core'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; import * as lambda from '@aws-cdk/aws-lambda'; -const submitLambda = new lambda.Function(this, 'SubmitLambda', { ... }); -const getStatusLambda = new lambda.Function(this, 'CheckLambda', { ... }); +declare const submitLambda: lambda.Function; +declare const getStatusLambda: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', { lambdaFunction: submitLambda, @@ -37,7 +34,7 @@ const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', { }); const waitX = new sfn.Wait(this, 'Wait X Seconds', { - time: sfn.WaitTime.secondsPath('$.waitSeconds'), + time: sfn.WaitTime.secondsPath('$.waitSeconds'), }); const getStatus = new tasks.LambdaInvoke(this, 'Get Job Status', { @@ -49,8 +46,8 @@ const getStatus = new tasks.LambdaInvoke(this, 'Get Job Status', { }); const jobFailed = new sfn.Fail(this, 'Job Failed', { - cause: 'AWS Batch Job Failed', - error: 'DescribeJob returned FAILED', + cause: 'AWS Batch Job Failed', + error: 'DescribeJob returned FAILED', }); const finalStatus = new tasks.LambdaInvoke(this, 'Get Final Job Status', { @@ -61,17 +58,17 @@ const finalStatus = new tasks.LambdaInvoke(this, 'Get Final Job Status', { }); const definition = submitJob - .next(waitX) - .next(getStatus) - .next(new sfn.Choice(this, 'Job Complete?') - // Look at the "status" field - .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) - .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) - .otherwise(waitX)); + .next(waitX) + .next(getStatus) + .next(new sfn.Choice(this, 'Job Complete?') + // Look at the "status" field + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) + .otherwise(waitX)); new sfn.StateMachine(this, 'StateMachine', { - definition, - timeout: cdk.Duration.minutes(5) + definition, + timeout: Duration.minutes(5), }); ``` @@ -88,7 +85,7 @@ all states reachable from the start state: const startState = new sfn.Pass(this, 'StartState'); new sfn.StateMachine(this, 'StateMachine', { - definition: startState + definition: startState, }); ``` @@ -145,6 +142,7 @@ const pass = new sfn.Pass(this, 'Add Hello World', { }); // Set the next state +const nextState = new sfn.Pass(this, 'NextState'); pass.next(nextState); ``` @@ -158,7 +156,7 @@ and also injects a field called `otherData`. const pass = new sfn.Pass(this, 'Filter input and inject data', { parameters: { // input to the pass state input: sfn.JsonPath.stringAt('$.input.greeting'), - otherData: 'some-extra-stuff' + otherData: 'some-extra-stuff', }, }); ``` @@ -179,10 +177,11 @@ state. // Wait until it's the time mentioned in the the state object's "triggerTime" // field. const wait = new sfn.Wait(this, 'Wait For Trigger Time', { - time: sfn.WaitTime.timestampPath('$.triggerTime'), + time: sfn.WaitTime.timestampPath('$.triggerTime'), }); // Set the next state +const startTheWork = new sfn.Pass(this, 'StartTheWork'); wait.next(startTheWork); ``` @@ -195,10 +194,13 @@ values in the execution's JSON state: const choice = new sfn.Choice(this, 'Did it work?'); // Add conditions with .when() +const successState = new sfn.Pass(this, 'SuccessState'); +const failureState = new sfn.Pass(this, 'FailureState'); choice.when(sfn.Condition.stringEquals('$.status', 'SUCCESS'), successState); choice.when(sfn.Condition.numberGreaterThan('$.attempts', 5), failureState); // Use .otherwise() to indicate what should be done if none of the conditions match +const tryAgainState = new sfn.Pass(this, 'TryAgainState'); choice.otherwise(tryAgainState); ``` @@ -208,11 +210,15 @@ then ... else` works in a programming language), use the `.afterwards()` method: ```ts const choice = new sfn.Choice(this, 'What color is it?'); +const handleBlueItem = new sfn.Pass(this, 'HandleBlueItem'); +const handleRedItem = new sfn.Pass(this, 'HandleRedItem'); +const handleOtherItemColor = new sfn.Pass(this, 'HanldeOtherItemColor'); choice.when(sfn.Condition.stringEquals('$.color', 'BLUE'), handleBlueItem); choice.when(sfn.Condition.stringEquals('$.color', 'RED'), handleRedItem); choice.otherwise(handleOtherItemColor); // Use .afterwards() to join all possible paths back together and continue +const shipTheItem = new sfn.Pass(this, 'ShipTheItem'); choice.afterwards().next(shipTheItem); ``` @@ -279,6 +285,9 @@ be used to catch and recover from errors in subworkflows. const parallel = new sfn.Parallel(this, 'Do the work in parallel'); // Add branches to be executed in parallel +const shipItem = new sfn.Pass(this, 'ShipItem'); +const sendInvoice = new sfn.Pass(this, 'SendInvoice'); +const restock = new sfn.Pass(this, 'Restock'); parallel.branch(shipItem); parallel.branch(sendInvoice); parallel.branch(restock); @@ -287,9 +296,11 @@ parallel.branch(restock); parallel.addRetry({ maxAttempts: 1 }); // How to recover from errors +const sendFailureNotification = new sfn.Pass(this, 'SendFailureNotification'); parallel.addCatch(sendFailureNotification); // What to do in case everything succeeded +const closeOrder = new sfn.Pass(this, 'CloseOrder'); parallel.next(closeOrder); ``` @@ -310,8 +321,8 @@ Failures can be caught by encompassing `Parallel` states. ```ts const success = new sfn.Fail(this, 'Fail', { - error: 'WorkflowFailure', - cause: "Something went wrong" + error: 'WorkflowFailure', + cause: "Something went wrong", }); ``` @@ -325,8 +336,8 @@ execute the same steps for multiple entries of an array in the state input. ```ts const map = new sfn.Map(this, 'Map State', { - maxConcurrency: 1, - itemsPath: sfn.JsonPath.stringAt('$.inputForMap') + maxConcurrency: 1, + itemsPath: sfn.JsonPath.stringAt('$.inputForMap'), }); map.iterator(new sfn.Pass(this, 'Pass State')); ``` @@ -354,19 +365,17 @@ the State Machine uses. The following example uses the `DynamoDB` service integration to insert data into a DynamoDB table. ```ts -import * as cdk from '@aws-cdk/core'; -import * as ddb from '@aws-cdk/aws-dynamodb'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; // create a table -const table = new ddb.Table(this, 'montable', { +const table = new dynamodb.Table(this, 'montable', { partitionKey: { name: 'id', - type: ddb.AttributeType.STRING, + type: dynamodb.AttributeType.STRING, }, }); -const finalStatus = new sfn.Pass(stack, 'final step'); +const finalStatus = new sfn.Pass(this, 'final step'); // States language JSON to put an item into DynamoDB // snippet generated from https://docs.aws.amazon.com/step-functions/latest/dg/tutorial-code-snippet.html#tutorial-code-snippet-1 @@ -390,11 +399,11 @@ const custom = new sfn.CustomState(this, 'my custom task', { }); const chain = sfn.Chain.start(custom) - .next(finalStatus); + .next(finalStatus); const sm = new sfn.StateMachine(this, 'StateMachine', { definition: chain, - timeout: cdk.Duration.seconds(30), + timeout: Duration.seconds(30), }); // don't forget permissions. You need to assign them @@ -410,19 +419,34 @@ In particular, the `.next()` method can be repeated. The result of a series of targets of `Choice.on` or `Parallel.branch`: ```ts +const step1 = new sfn.Pass(this, 'Step1'); +const step2 = new sfn.Pass(this, 'Step2'); +const step3 = new sfn.Pass(this, 'Step3'); +const step4 = new sfn.Pass(this, 'Step4'); +const step5 = new sfn.Pass(this, 'Step5'); +const step6 = new sfn.Pass(this, 'Step6'); +const step7 = new sfn.Pass(this, 'Step7'); +const step8 = new sfn.Pass(this, 'Step8'); +const step9 = new sfn.Pass(this, 'Step9'); +const step10 = new sfn.Pass(this, 'Step10'); +const choice = new sfn.Choice(this, 'Choice'); +const condition1 = sfn.Condition.stringEquals('$.status', 'SUCCESS'); +const parallel = new sfn.Parallel(this, 'Parallel'); +const finish = new sfn.Pass(this, 'Finish'); + const definition = step1 - .next(step2) - .next(choice - .when(condition1, step3.next(step4).next(step5)) - .otherwise(step6) - .afterwards()) - .next(parallel - .branch(step7.next(step8)) - .branch(step9.next(step10))) - .next(finish); + .next(step2) + .next(choice + .when(condition1, step3.next(step4).next(step5)) + .otherwise(step6) + .afterwards()) + .next(parallel + .branch(step7.next(step8)) + .branch(step9.next(step10))) + .next(finish); new sfn.StateMachine(this, 'StateMachine', { - definition, + definition, }); ``` @@ -430,11 +454,15 @@ If you don't like the visual look of starting a chain directly off the first step, you can use `Chain.start`: ```ts +const step1 = new sfn.Pass(this, 'Step1'); +const step2 = new sfn.Pass(this, 'Step2'); +const step3 = new sfn.Pass(this, 'Step3'); + const definition = sfn.Chain - .start(step1) - .next(step2) - .next(step3) - // ... + .start(step1) + .next(step2) + .next(step3) + // ... ``` ## State Machine Fragments @@ -456,32 +484,43 @@ The class `StateMachineFragment` contains some helper functions (like `prefixStates()`) to make it easier for you to do this. If you define your state machine as a subclass of this, it will be convenient to use: -```ts +```ts nofixture +import { Construct, Stack } from '@aws-cdk/core'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; + interface MyJobProps { - jobFlavor: string; + jobFlavor: string; } class MyJob extends sfn.StateMachineFragment { - public readonly startState: sfn.State; - public readonly endStates: sfn.INextable[]; + public readonly startState: sfn.State; + public readonly endStates: sfn.INextable[]; - constructor(parent: cdk.Construct, id: string, props: MyJobProps) { - super(parent, id); + constructor(parent: Construct, id: string, props: MyJobProps) { + super(parent, id); - const first = new sfn.Task(this, 'First', { ... }); - // ... - const last = new sfn.Task(this, 'Last', { ... }); + const choice = new sfn.Choice(this, 'Choice') + .when(sfn.Condition.stringEquals('$.branch', 'left'), new sfn.Pass(this, 'Left Branch')) + .when(sfn.Condition.stringEquals('$.branch', 'right'), new sfn.Pass(this, 'Right Branch')); - this.startState = first; - this.endStates = [last]; - } + // ... + + this.startState = choice; + this.endStates = choice.afterwards().endStates; + } } -// Do 3 different variants of MyJob in parallel -new sfn.Parallel(this, 'All jobs') - .branch(new MyJob(this, 'Quick', { jobFlavor: 'quick' }).prefixStates()) - .branch(new MyJob(this, 'Medium', { jobFlavor: 'medium' }).prefixStates()) - .branch(new MyJob(this, 'Slow', { jobFlavor: 'slow' }).prefixStates()); +class MyStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + // Do 3 different variants of MyJob in parallel + new sfn.Parallel(this, 'All jobs') + .branch(new MyJob(this, 'Quick', { jobFlavor: 'quick' }).prefixStates()) + .branch(new MyJob(this, 'Medium', { jobFlavor: 'medium' }).prefixStates()) + .branch(new MyJob(this, 'Slow', { jobFlavor: 'slow' }).prefixStates()); + } +} ``` A few utility functions are available to parse state machine fragments. @@ -504,7 +543,7 @@ const activity = new sfn.Activity(this, 'Activity'); // Read this CloudFormation Output from your application and use it to poll for work on // the activity. -new cdk.CfnOutput(this, 'ActivityArn', { value: activity.activityArn }); +new CfnOutput(this, 'ActivityArn', { value: activity.activityArn }); ``` ### Activity-Level Permissions @@ -514,7 +553,7 @@ Granting IAM permissions to an activity can be achieved by calling the `grant(pr ```ts const activity = new sfn.Activity(this, 'Activity'); -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); @@ -529,20 +568,22 @@ This will grant the IAM principal the specified actions onto the activity. to create an alarm on a particular task failing: ```ts +declare const task: sfn.Task; new cloudwatch.Alarm(this, 'TaskAlarm', { - metric: task.metricFailed(), - threshold: 1, - evaluationPeriods: 1, + metric: task.metricFailed(), + threshold: 1, + evaluationPeriods: 1, }); ``` There are also metrics on the complete state machine: ```ts +declare const stateMachine: sfn.StateMachine; new cloudwatch.Alarm(this, 'StateMachineAlarm', { - metric: stateMachine.metricFailed(), - threshold: 1, - evaluationPeriods: 1, + metric: stateMachine.metricFailed(), + threshold: 1, + evaluationPeriods: 1, }); ``` @@ -550,9 +591,9 @@ And there are metrics on the capacity of all state machines in your account: ```ts new cloudwatch.Alarm(this, 'ThrottledAlarm', { - metric: StateTransitionMetrics.metricThrottledEvents(), - threshold: 10, - evaluationPeriods: 2, + metric: sfn.StateTransitionMetric.metricThrottledEvents(), + threshold: 10, + evaluationPeriods: 2, }); ``` @@ -578,14 +619,16 @@ Enable logging to CloudWatch by passing a logging configuration with a destination LogGroup: ```ts -const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); - -new sfn.StateMachine(stack, 'MyStateMachine', { - definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')), - logs: { - destination: logGroup, - level: sfn.LogLevel.ALL, - } +import * as logs from '@aws-cdk/aws-logs'; + +const logGroup = new logs.LogGroup(this, 'MyLogGroup'); + +new sfn.StateMachine(this, 'MyStateMachine', { + definition: sfn.Chain.start(new sfn.Pass(this, 'Pass')), + logs: { + destination: logGroup, + level: sfn.LogLevel.ALL, + }, }); ``` @@ -594,9 +637,9 @@ new sfn.StateMachine(stack, 'MyStateMachine', { Enable X-Ray tracing for StateMachine: ```ts -new sfn.StateMachine(stack, 'MyStateMachine', { - definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')), - tracingEnabled: true +new sfn.StateMachine(this, 'MyStateMachine', { + definition: sfn.Chain.start(new sfn.Pass(this, 'Pass')), + tracingEnabled: true, }); ``` @@ -620,11 +663,12 @@ Any object that implements the `IGrantable` interface (has an associated princip Grant permission to start an execution of a state machine by calling the `grantStartExecution()` API. ```ts -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); -const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', { +declare const definition: sfn.IChainable; +const stateMachine = new sfn.StateMachine(this, 'StateMachine', { definition, }); @@ -641,11 +685,12 @@ The following permission is provided to a service principal by the `grantStartEx Grant `read` access to a state machine by calling the `grantRead()` API. ```ts -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); -const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', { +declare const definition: sfn.IChainable; +const stateMachine = new sfn.StateMachine(this, 'StateMachine', { definition, }); @@ -669,11 +714,12 @@ The following read permissions are provided to a service principal by the `grant Grant permission to allow task responses to a state machine by calling the `grantTaskResponse()` API: ```ts -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); -const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', { +declare const definition: sfn.IChainable; +const stateMachine = new sfn.StateMachine(this, 'StateMachine', { definition, }); @@ -692,11 +738,12 @@ The following read permissions are provided to a service principal by the `grant Grant execution-level permissions to a state machine by calling the `grantExecution()` API: ```ts -const role = new iam.Role(stack, 'Role', { +const role = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); -const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', { +declare const definition: sfn.IChainable; +const stateMachine = new sfn.StateMachine(this, 'StateMachine', { definition, }); @@ -709,9 +756,10 @@ stateMachine.grantExecution(role, 'states:GetExecutionHistory'); You can add any set of permissions to a state machine by calling the `grant()` API. ```ts -const user = new iam.User(stack, 'MyUser'); +const user = new iam.User(this, 'MyUser'); -const stateMachine = new stepfunction.StateMachine(stack, 'StateMachine', { +declare const definition: sfn.IChainable; +const stateMachine = new sfn.StateMachine(this, 'StateMachine', { definition, }); @@ -727,11 +775,11 @@ into your CDK stack. State machines can be imported by their ARN via the `StateMachine.fromStateMachineArn()` API ```ts -import * as sfn from 'aws-stepfunctions'; - +const app = new App(); const stack = new Stack(app, 'MyStack'); sfn.StateMachine.fromStateMachineArn( stack, 'ImportedStateMachine', - 'arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ'); + 'arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ', +); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 3f474c93d7a1d..a226142e11959 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,6 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { StatesMetrics } from './stepfunctions-canned-metrics.generated'; import { CfnActivity } from './stepfunctions.generated'; @@ -28,7 +28,7 @@ export class Activity extends Resource implements IActivity { class Imported extends Resource implements IActivity { public get activityArn() { return activityArn; } public get activityName() { - return Stack.of(this).parseArn(activityArn, ':').resourceName || ''; + return Stack.of(this).splitArn(activityArn, ArnFormat.COLON_RESOURCE_NAME).resourceName || ''; } } @@ -43,7 +43,7 @@ export class Activity extends Resource implements IActivity { service: 'states', resource: 'activity', resourceName: activityName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); } @@ -71,7 +71,7 @@ export class Activity extends Resource implements IActivity { service: 'states', resource: 'activity', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.activityName = this.getResourceNameAttribute(resource.attrName); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index d7e7a73efa3f0..a75a5f1843992 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; -import { Arn, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Arn, ArnFormat, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { StateGraph } from './state-graph'; import { StatesMetrics } from './stepfunctions-canned-metrics.generated'; @@ -244,7 +244,7 @@ abstract class StateMachineBase extends Resource implements IStateMachine { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: { StateMachineArn: this.stateMachineArn }, + dimensionsMap: { StateMachineArn: this.stateMachineArn }, statistic: 'sum', ...props, }).attachTo(this); @@ -333,8 +333,8 @@ abstract class StateMachineBase extends Resource implements IStateMachine { return Stack.of(this).formatArn({ resource: 'execution', service: 'states', - resourceName: Arn.parse(this.stateMachineArn, ':').resourceName, - sep: ':', + resourceName: Arn.split(this.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } @@ -412,7 +412,7 @@ export class StateMachine extends StateMachineBase { service: 'states', resource: 'stateMachine', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts index 953a73bb33192..c3a9a12e2c111 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts @@ -15,7 +15,7 @@ export class StateTransitionMetric { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: { ServiceMetric: 'StateTransition' }, + dimensionsMap: { ServiceMetric: 'StateTransition' }, ...props, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts index 5743c0171d188..ae0859a35c69b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts @@ -168,7 +168,7 @@ export abstract class TaskStateBase extends State implements INextable { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: this.taskMetrics?.metricDimensions, + dimensionsMap: this.taskMetrics?.metricDimensions, statistic: 'sum', ...props, }).attachTo(this); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts index 8386b3ce334b1..f0fd18d22c090 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts @@ -18,21 +18,21 @@ export class WaitTime { /** * Wait until the given ISO8601 timestamp * - * @example 2016-03-14T01:59:00Z + * Example value: `2016-03-14T01:59:00Z` */ public static timestamp(timestamp: string) { return new WaitTime({ Timestamp: timestamp }); } /** * Wait for a number of seconds stored in the state object. * - * @example $.waitSeconds + * Example value: `$.waitSeconds` */ public static secondsPath(path: string) { return new WaitTime({ SecondsPath: path }); } /** * Wait until a timestamp found in the state object. * - * @example $.waitTimestamp + * Example value: `$.waitTimestamp` */ public static timestampPath(path: string) { return new WaitTime({ TimestampPath: path }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index dbbb99ff04844..40cea293bc09f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -72,12 +79,12 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-stepfunctions/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..91085b6c8933c --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { App, CfnOutput, Duration, Stack } from '@aws-cdk/core'; +import sfn = require('@aws-cdk/aws-stepfunctions'); +import tasks = require('@aws-cdk/aws-stepfunctions-tasks'); +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import iam = require('@aws-cdk/aws-iam'); + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts index f2ca02a571ffc..ec89fbfe3a964 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts @@ -1,5 +1,4 @@ -import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; @@ -13,7 +12,7 @@ describe('Activity', () => { new stepfunctions.Activity(stack, 'Activity'); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::Activity', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::Activity', { Name: 'Activity', }); }); @@ -58,15 +57,15 @@ describe('Activity', () => { activity.grant(role, 'states:SendTaskSuccess'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: 'states:SendTaskSuccess', Effect: 'Allow', Resource: { Ref: 'Activity04690B0A', }, - })), + })]), }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts index 6689cfcb64530..2af4fa09e66ab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as stepfunctions from '../lib'; describe('Condition Variables', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts index c03b716eb6248..9dc1e278097a8 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as sfn from '../lib'; import { render } from './private/render-util'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts index a8e94b156466c..98c3295d0a68a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 85549614a0538..0e6e3e9c25175 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { FieldUtils, JsonPath, TaskInput } from '../lib'; describe('Fields', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts index e5e379f578800..7840d2207f9a3 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts index b89a567b22826..9ca3bd8876781 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts index f3cc78bb68455..75779bd62f381 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { Result } from '../lib'; describe('Pass State', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts index 528b79b59dd58..78448372842b9 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts @@ -1,12 +1,13 @@ -import { arrayWith, objectLike, ResourcePart } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as stepfunctions from '../lib'; describe('State Machine Resources', () => { - test('Tasks can add permissions to the execution role', () => { + testDeprecated('Tasks can add permissions to the execution role', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -27,7 +28,7 @@ describe('State Machine Resources', () => { }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -44,18 +45,13 @@ describe('State Machine Resources', () => { test('Tasks hidden inside a Parallel state are also included', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ - resourceArn: 'resource', - policyStatements: [ - new iam.PolicyStatement({ - actions: ['resource:Everything'], - resources: ['resource'], - }), - ], + const task = new FakeTask(stack, 'Task', { + policies: [ + new iam.PolicyStatement({ + actions: ['resource:Everything'], + resources: ['resource'], }), - }, + ], }); const para = new stepfunctions.Parallel(stack, 'Para'); @@ -67,7 +63,7 @@ describe('State Machine Resources', () => { }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -81,7 +77,7 @@ describe('State Machine Resources', () => { }); }), - test('Task should render InputPath / Parameters / OutputPath correctly', () => { + testDeprecated('Task should render InputPath / Parameters / OutputPath correctly', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -128,7 +124,7 @@ describe('State Machine Resources', () => { }); }), - test('Task combines taskobject parameters with direct parameters', () => { + testDeprecated('Task combines taskobject parameters with direct parameters', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -174,11 +170,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant start execution to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -190,15 +182,15 @@ describe('State Machine Resources', () => { stateMachine.grantStartExecution(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: 'states:StartExecution', Effect: 'Allow', Resource: { Ref: 'StateMachine2E01A3A5', }, - })), + })]), }, }); @@ -207,11 +199,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant read access to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -223,7 +211,7 @@ describe('State Machine Resources', () => { stateMachine.grantRead(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -297,11 +285,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant task response actions to the state machine', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -313,7 +297,7 @@ describe('State Machine Resources', () => { stateMachine.grantTaskResponse(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -335,11 +319,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant actions to the executions', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -351,7 +331,7 @@ describe('State Machine Resources', () => { stateMachine.grantExecution(role, 'states:GetExecutionHistory'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -400,11 +380,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant actions to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -416,7 +392,7 @@ describe('State Machine Resources', () => { stateMachine.grant(role, 'states:ListExecution'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -445,7 +421,7 @@ describe('State Machine Resources', () => { stateMachine.grantStartExecution(role); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -478,7 +454,7 @@ describe('State Machine Resources', () => { stateMachine.grantRead(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -545,7 +521,7 @@ describe('State Machine Resources', () => { stateMachine.grantTaskResponse(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -575,7 +551,7 @@ describe('State Machine Resources', () => { stateMachine.grant(role, 'states:ListExecution'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -675,30 +651,48 @@ describe('State Machine Resources', () => { test('State machines must depend on their roles', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ - resourceArn: 'resource', - policyStatements: [ - new iam.PolicyStatement({ - resources: ['resource'], - actions: ['lambda:InvokeFunction'], - }), - ], + const task = new FakeTask(stack, 'Task', { + policies: [ + new iam.PolicyStatement({ + resources: ['resource'], + actions: ['lambda:InvokeFunction'], }), - }, + ], }); new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResource('AWS::StepFunctions::StateMachine', { DependsOn: [ 'StateMachineRoleDefaultPolicyDF1E6607', 'StateMachineRoleB840431D', ], - }, ResourcePart.CompleteDefinition); + }); }); }); + +interface FakeTaskProps extends stepfunctions.TaskStateBaseProps { + readonly policies?: iam.PolicyStatement[]; +} + +class FakeTask extends stepfunctions.TaskStateBase { + protected readonly taskMetrics?: stepfunctions.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: Construct, id: string, props: FakeTaskProps = {}) { + super(scope, id, props); + this.taskPolicies = props.policies; + } + + protected _renderTask(): any { + return { + Resource: 'my-resource', + Parameters: stepfunctions.FieldUtils.renderObject({ + MyParameter: 'myParameter', + }), + }; + } +} 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 6db3dd6b476bd..f60c0b756e66a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; @@ -16,7 +16,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', }); @@ -34,7 +34,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', StateMachineType: 'STANDARD', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', @@ -54,7 +54,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', StateMachineType: 'EXPRESS', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', @@ -138,7 +138,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', LoggingConfiguration: { Destinations: [{ @@ -153,7 +153,7 @@ describe('State Machine', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ @@ -191,14 +191,14 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', TracingConfiguration: { Enabled: true, }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ @@ -233,7 +233,7 @@ describe('State Machine', () => { bucket.grantRead(sm); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts index 993b6a6d27351..1ddddaae31cab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts @@ -1,6 +1,7 @@ -import '@aws-cdk/assert-internal/jest'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; +import { Construct } from 'constructs'; import * as stepfunctions from '../lib'; describe('States Language', () => { @@ -363,7 +364,7 @@ describe('States Language', () => { test('States can have error branches', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -393,7 +394,7 @@ describe('States Language', () => { test('Retries and errors with a result path', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -423,8 +424,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -467,8 +468,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -496,9 +497,9 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); - const task3 = new stepfunctions.Task(stack, 'Task3', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); + const task3 = new FakeTask(stack, 'Task3'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -523,8 +524,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -537,8 +538,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -709,12 +710,12 @@ class SimpleChain extends stepfunctions.StateMachineFragment { public readonly startState: stepfunctions.State; public readonly endStates: stepfunctions.INextable[]; - private readonly task2: stepfunctions.Task; + private readonly task2: stepfunctions.TaskStateBase; constructor(scope: constructs.Construct, id: string) { super(scope, id); - const task1 = new stepfunctions.Task(this, 'Task1', { task: new FakeTask() }); - this.task2 = new stepfunctions.Task(this, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(this, 'Task1'); + this.task2 = new FakeTask(this, 'Task2'); task1.next(this.task2); @@ -732,10 +733,22 @@ function render(sm: stepfunctions.IChainable) { return new cdk.Stack().resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } -class FakeTask implements stepfunctions.IStepFunctionsTask { - public bind(_task: stepfunctions.Task): stepfunctions.StepFunctionsTaskConfig { +interface FakeTaskProps extends stepfunctions.TaskStateBaseProps { + readonly policies?: iam.PolicyStatement[]; +} + +class FakeTask extends stepfunctions.TaskStateBase { + protected readonly taskMetrics?: stepfunctions.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: Construct, id: string, props: FakeTaskProps = {}) { + super(scope, id, props); + this.taskPolicies = props.policies; + } + + protected _renderTask(): any { return { - resourceArn: 'resource', + Resource: 'resource', }; } } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts index a57c489134efd..a616ca0061e11 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts index 06dc0282c9c05..ba4c2a79e660e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts @@ -1,8 +1,9 @@ import { Metric } from '@aws-cdk/aws-cloudwatch'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as sfn from '../lib'; -describe('Task state', () => { +describeDeprecated('Task state', () => { let stack: cdk.Stack; let task: sfn.Task; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts index 350ff400a4d86..42671449be619 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import { Pass, Wait, WaitTime } from '../lib'; import { render } from './private/render-util'; diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index ccda6db1260a9..cdb5aaa6dff47 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -44,7 +44,7 @@ const canary = new synthetics.Canary(this, 'MyCanary', { }), runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, environmentVariables: { - stage: 'prod', + stage: 'prod', }, }); ``` @@ -56,24 +56,24 @@ const synthetics = require('Synthetics'); const log = require('SyntheticsLogger'); const pageLoadBlueprint = async function () { - // Configure the stage of the API using environment variables - const url = `https://api.example.com/${process.env.stage}/user/books/topbook/`; - - const page = await synthetics.getPage(); - const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); - // Wait for page to render. Increase or decrease wait time based on endpoint being monitored. - await page.waitFor(15000); - // This will take a screenshot that will be included in test output artifacts. - await synthetics.takeScreenshot('loaded', 'loaded'); - const pageTitle = await page.title(); - log.info('Page title: ' + pageTitle); - if (response.status() !== 200) { - throw 'Failed to load page!'; - } + // Configure the stage of the API using environment variables + const url = `https://api.example.com/${process.env.stage}/user/books/topbook/`; + + const page = await synthetics.getPage(); + const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); + // Wait for page to render. Increase or decrease wait time based on endpoint being monitored. + await page.waitFor(15000); + // This will take a screenshot that will be included in test output artifacts. + await synthetics.takeScreenshot('loaded', 'loaded'); + const pageTitle = await page.title(); + log.info('Page title: ' + pageTitle); + if (response.status() !== 200) { + throw 'Failed to load page!'; + } }; exports.handler = async () => { - return await pageLoadBlueprint(); + return await pageLoadBlueprint(); }; ``` @@ -97,13 +97,15 @@ object to the `schedule` property. Configure a run rate of up to 60 minutes with `Schedule.rate`: ```ts -Schedule.rate(Duration.minutes(5)), // Runs every 5 minutes. +const schedule = synthetics.Schedule.rate(Duration.minutes(5)); // Runs every 5 minutes. ``` -You can also specify a [cron expression](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_cron.html) via `Schedule.expression`: +You can also specify a [cron expression](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_cron.html) with `Schedule.cron`: ```ts -Schedule.expression('cron(0 0,8,16 * * ? *)'), // Run at 12am, 8am, 4pm UTC every day +const schedule = synthetics.Schedule.cron({ + hour: '0,8,16', // Run at 12am, 8am, 4pm UTC every day +}); ``` If you want the canary to run just once upon deployment, you can use `Schedule.once()`. @@ -127,7 +129,7 @@ new synthetics.Canary(this, 'Inline Canary', { code: synthetics.Code.fromInline('/* Synthetics handler code */'), handler: 'index.handler', // must be 'index.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, }); // To supply the code from your local filesystem: @@ -136,7 +138,7 @@ new synthetics.Canary(this, 'Asset Canary', { code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), handler: 'index.handler', // must end with '.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, }); // To supply the code from a S3 bucket: @@ -147,7 +149,7 @@ new synthetics.Canary(this, 'Bucket Canary', { code: synthetics.Code.fromBucket(bucket, 'canary.zip'), handler: 'index.handler', // must end with '.handler' }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, }); ``` @@ -181,8 +183,10 @@ You can configure a CloudWatch Alarm on a canary metric. Metrics are emitted by Create an alarm that tracks the canary metric: -```ts fixture=canary +```ts import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +declare const canary: synthetics.Canary; new cloudwatch.Alarm(this, 'CanaryAlarm', { metric: canary.metricSuccessPercent(), evaluationPeriods: 2, @@ -190,7 +194,3 @@ new cloudwatch.Alarm(this, 'CanaryAlarm', { comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, }); ``` - -### Future Work - -- Add blueprints to the Test class [#9613](https://github.com/aws/aws-cdk/issues/9613#issue-677134857). diff --git a/packages/@aws-cdk/aws-synthetics/lib/runtime.ts b/packages/@aws-cdk/aws-synthetics/lib/runtime.ts index c710d68a34e35..81ac1a857e6db 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/runtime.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/runtime.ts @@ -104,6 +104,16 @@ export class Runtime { */ public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_2 = new Runtime('syn-nodejs-puppeteer-3.2', RuntimeFamily.NODEJS); + /** + * `syn-nodejs-puppeteer-3.3` includes the following: + * - Lambda runtime Node.js 12.x + * - Puppeteer-core version 5.5.0 + * - Chromium version 88.0.4298.0 + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Library_nodejs_puppeteer.html#CloudWatch_Synthetics_runtimeversion-nodejs-puppeteer-3.3 + */ + public static readonly SYNTHETICS_NODEJS_PUPPETEER_3_3 = new Runtime('syn-nodejs-puppeteer-3.3', RuntimeFamily.NODEJS); + /** * `syn-python-selenium-1.0` includes the following: * - Lambda runtime Python 3.8 diff --git a/packages/@aws-cdk/aws-synthetics/lib/schedule.ts b/packages/@aws-cdk/aws-synthetics/lib/schedule.ts index 3bd92c81b4d0b..248b44e4c59cb 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/schedule.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/schedule.ts @@ -42,9 +42,81 @@ export class Schedule { return new Schedule(`rate(${minutes} minutes)`); } + /** + * Create a schedule from a set of cron fields + */ + public static cron(options: CronOptions): Schedule { + if (options.weekDay !== undefined && options.day !== undefined) { + throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + } + + const minute = fallback(options.minute, '*'); + const hour = fallback(options.hour, '*'); + const month = fallback(options.month, '*'); + + // Weekday defaults to '?' if not supplied. If it is supplied, day must become '?' + const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*'); + const weekDay = fallback(options.weekDay, '?'); + + // '*' is only allowed in the year field + const year = '*'; + + return new Schedule(`cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`); + } + private constructor( /** * The Schedule expression */ public readonly expressionString: string) {} } + + +/** + * Options to configure a cron expression + * + * All fields are strings so you can use complex expressions. Absence of + * a field implies '*' or '?', whichever one is appropriate. + * + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_cron.html + */ +export interface CronOptions { + /** + * The minute to run this rule at + * + * @default - Every minute + */ + readonly minute?: string; + + /** + * The hour to run this rule at + * + * @default - Every hour + */ + readonly hour?: string; + + /** + * The day of the month to run this rule at + * + * @default - Every day of the month + */ + readonly day?: string; + + /** + * The month to run this rule at + * + * @default - Every month + */ + readonly month?: string; + + /** + * The day of the week to run this rule at + * + * @default - Any day of the week + */ + readonly weekDay?: string; +} + +function fallback(x: string | undefined, def: string): string { + return x ?? def; +} diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index 6504764f4f218..745f97fc21261 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -79,7 +86,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-synthetics/rosetta/canary.ts-fixture b/packages/@aws-cdk/aws-synthetics/rosetta/canary.ts-fixture deleted file mode 100644 index d1950f9d9bcbe..0000000000000 --- a/packages/@aws-cdk/aws-synthetics/rosetta/canary.ts-fixture +++ /dev/null @@ -1,21 +0,0 @@ -// Fixture with a canary already created, named `canary` -import { Construct, Duration, Stack } from '@aws-cdk/core'; -import * as synthetics from '@aws-cdk/aws-synthetics'; -import * as path from 'path'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const canary = new synthetics.Canary(this, 'MyCanary', { - schedule: synthetics.Schedule.rate(Duration.minutes(5)), - test: synthetics.Test.custom({ - code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), - handler: 'index.handler', - }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, - }); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-synthetics/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-synthetics/rosetta/default.ts-fixture index 0af6c54c30689..a0a9d5c2a596e 100644 --- a/packages/@aws-cdk/aws-synthetics/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-synthetics/rosetta/default.ts-fixture @@ -1,5 +1,6 @@ // Fixture with packages imported, but nothing else -import { Construct, Duration, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; import * as synthetics from '@aws-cdk/aws-synthetics'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.test.ts b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts index 27491d01b2850..83ba173562834 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.test.ts +++ b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts @@ -292,6 +292,27 @@ test('Schedule can be set to 1 minute', () => { }); }); +test('Schedule can be set with Cron', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.cron({ minute: '30' }), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', { + Schedule: Match.objectLike({ Expression: 'cron(30 * * * ? *)' }), + }); +}); + + test('Schedule can be set with Expression', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json index 70d3908aa07f6..988973d8bfe78 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json @@ -456,6 +456,173 @@ "StartCanaryAfterCreation": true } }, + "MyCanaryThreeArtifactsBucket894E857E": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyCanaryThreeServiceRole68117E65": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:PutObject", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryThreeArtifactsBucket894E857E", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:::*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyCanaryThree968B1271": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyCanaryThreeArtifactsBucket894E857E" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + } + ] + ] + } + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryThreeServiceRole68117E65", + "Arn" + ] + }, + "Name": "assetcanary-three", + "RuntimeVersion": "syn-nodejs-puppeteer-3.3", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + }, "MyPythonCanaryArtifactsBucket7AE88133": { "Type": "AWS::S3::Bucket", "Properties": { diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts index 54822badf1c99..268667b8439f3 100644 --- a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts @@ -11,6 +11,7 @@ import * as synthetics from '../lib'; * -- aws synthetics get-canary --name canary-integ has a state of 'RUNNING' * -- aws synthetics get-canary --name assetcanary-one has a state of 'RUNNING' * -- aws synthetics get-canary --name assetcanary-two has a state of 'RUNNING' + * -- aws synthetics get-canary --name assetcanary-three has a state of 'RUNNING' */ const app = new cdk.App(); const stack = new cdk.Stack(app, 'canary-one'); @@ -50,6 +51,15 @@ new synthetics.Canary(stack, 'MyCanaryTwo', { runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_2, }); +new synthetics.Canary(stack, 'MyCanaryThree', { + canaryName: 'assetcanary-three', + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary.zip')), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, +}); + new synthetics.Canary(stack, 'MyPythonCanary', { canaryName: 'py-canary-integ', test: synthetics.Test.custom({ diff --git a/packages/@aws-cdk/aws-synthetics/test/schedule.test.ts b/packages/@aws-cdk/aws-synthetics/test/schedule.test.ts new file mode 100644 index 0000000000000..664f27774b61f --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/schedule.test.ts @@ -0,0 +1,26 @@ +import * as synthetics from '../lib'; + +describe('cron', () => { + test('day and weekDay are mutex: given week day', () => { + expect(synthetics.Schedule.cron({ + weekDay: 'MON-FRI', + }).expressionString).toEqual('cron(* * ? * MON-FRI *)'); + }); + + test('day and weekDay are mutex: given month day', () => { + expect(synthetics.Schedule.cron({ + day: '1', + }).expressionString).toEqual('cron(* * 1 * ? *)'); + }); + + test('day and weekDay are mutex: given neither', () => { + expect(synthetics.Schedule.cron({}).expressionString).toEqual('cron(* * * * ? *)'); + }); + + test('day and weekDay are mutex: throw if given both', () => { + expect(() => synthetics.Schedule.cron({ + day: '1', + weekDay: 'MON', + })).toThrow('Cannot supply both \'day\' and \'weekDay\', use at most one'); + }); +}); diff --git a/packages/@aws-cdk/aws-timestream/README.md b/packages/@aws-cdk/aws-timestream/README.md index 80120fe64f2ca..c5b4cb3de1e85 100644 --- a/packages/@aws-cdk/aws-timestream/README.md +++ b/packages/@aws-cdk/aws-timestream/README.md @@ -16,5 +16,5 @@ 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'); +import * as timestream from '@aws-cdk/aws-timestream'; ``` diff --git a/packages/@aws-cdk/aws-timestream/package.json b/packages/@aws-cdk/aws-timestream/package.json index 8e812faac660d..9aa39709df395 100644 --- a/packages/@aws-cdk/aws-timestream/package.json +++ b/packages/@aws-cdk/aws-timestream/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.Timestream", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-transfer/package.json b/packages/@aws-cdk/aws-transfer/package.json index 3dcd461a79ab6..cc1d96d9fb22e 100644 --- a/packages/@aws-cdk/aws-transfer/package.json +++ b/packages/@aws-cdk/aws-transfer/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 33482a6fa8f12..401458b6f509a 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index 4710718baec70..9daf58db6d53c 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-wafv2/README.md b/packages/@aws-cdk/aws-wafv2/README.md index 0d1a5610493db..eb0003b605449 100644 --- a/packages/@aws-cdk/aws-wafv2/README.md +++ b/packages/@aws-cdk/aws-wafv2/README.md @@ -18,3 +18,23 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw ```ts import * as wafv2 from '@aws-cdk/aws-wafv2'; ``` + +## Examples + +Create a simple WebACL resource. + +```csharp +var WebACL = new CfnWebACL(this, "WebACL", new CfnWebACLProps{ + Name = "MyWebACL", + Scope = "REGIONAL", + DefaultAction = new CfnWebACL.DefaultActionProperty { + Allow = new CfnWebACL.AllowActionProperty{} + }, + VisibilityConfig = new CfnWebACL.VisibilityConfigProperty { + SampledRequestsEnabled = true, + CloudWatchMetricsEnabled = true, + MetricName = "WebACL", + }, + Rules = new CfnWebACL.RuleProperty[] {} + }); +``` diff --git a/packages/@aws-cdk/aws-wafv2/package.json b/packages/@aws-cdk/aws-wafv2/package.json index 6ac9c2eb572fa..4c689f62396f0 100644 --- a/packages/@aws-cdk/aws-wafv2/package.json +++ b/packages/@aws-cdk/aws-wafv2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -78,7 +85,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-wisdom/.eslintrc.js b/packages/@aws-cdk/aws-wisdom/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wisdom/.gitignore b/packages/@aws-cdk/aws-wisdom/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/.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-wisdom/.npmignore b/packages/@aws-cdk/aws-wisdom/.npmignore new file mode 100644 index 0000000000000..f931fede67c44 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/.npmignore @@ -0,0 +1,29 @@ +# 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/ +!*.lit.ts diff --git a/packages/@aws-cdk/aws-wisdom/LICENSE b/packages/@aws-cdk/aws-wisdom/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/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-2021 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-wisdom/NOTICE b/packages/@aws-cdk/aws-wisdom/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-wisdom/README.md b/packages/@aws-cdk/aws-wisdom/README.md new file mode 100644 index 0000000000000..218dae4f51735 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/README.md @@ -0,0 +1,20 @@ +# AWS::Wisdom 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]) are always stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib + +--- + + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import * as wisdom from '@aws-cdk/aws-wisdom'; +``` diff --git a/packages/@aws-cdk/aws-wisdom/jest.config.js b/packages/@aws-cdk/aws-wisdom/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-wisdom/lib/index.ts b/packages/@aws-cdk/aws-wisdom/lib/index.ts new file mode 100644 index 0000000000000..368aee100e245 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Wisdom CloudFormation Resources: +export * from './wisdom.generated'; diff --git a/packages/@aws-cdk/aws-wisdom/package.json b/packages/@aws-cdk/aws-wisdom/package.json new file mode 100644 index 0000000000000..aa12d6e4aa992 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/package.json @@ -0,0 +1,112 @@ +{ + "name": "@aws-cdk/aws-wisdom", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Wisdom", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Wisdom", + "packageId": "Amazon.CDK.AWS.Wisdom", + "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.wisdom", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "wisdom" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-wisdom", + "module": "aws_cdk.aws_wisdom" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-wisdom" + }, + "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": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "cdk-build": { + "cloudformation": "AWS::Wisdom", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Wisdom", + "aws-wisdom" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "@types/jest": "^27.0.2" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-wisdom/test/aws-wisdom.test.ts b/packages/@aws-cdk/aws-wisdom/test/aws-wisdom.test.ts new file mode 100644 index 0000000000000..465c7bdea0693 --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/test/aws-wisdom.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assertions'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index 8a7f2477621a5..151f94ddb9dad 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -76,7 +76,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/aws-xray/README.md b/packages/@aws-cdk/aws-xray/README.md index 77a0090e7f659..c543893c88228 100644 --- a/packages/@aws-cdk/aws-xray/README.md +++ b/packages/@aws-cdk/aws-xray/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import xray = require('@aws-cdk/aws-xray'); +import * as xray from '@aws-cdk/aws-xray'; ``` diff --git a/packages/@aws-cdk/aws-xray/package.json b/packages/@aws-cdk/aws-xray/package.json index f1219a428f2fb..7a5d9bfb5c5cd 100644 --- a/packages/@aws-cdk/aws-xray/package.json +++ b/packages/@aws-cdk/aws-xray/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.XRay", @@ -80,7 +87,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "@aws-cdk/core": "0.0.0" diff --git a/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index a2a06573cbefc..1d0ab53e1cd4d 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -54,8 +54,8 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 19c814c818182..46c876faf346e 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,863 @@ +# CloudFormation Resource Specification v49.0.0 + +## New Resource Types + +* AWS::AppStream::AppBlock +* AWS::AppStream::Application +* AWS::AppStream::ApplicationFleetAssociation +* AWS::DataBrew::Ruleset + +## Attribute Changes + +* AWS::EC2::VPCDHCPOptionsAssociation Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html +* AWS::EC2::VPCDHCPOptionsAssociation Id (__added__) + +## Property Changes + +* AWS::APS::RuleGroupsNamespace Name.Required (__changed__) + * Old: false + * New: true +* AWS::AppStream::Fleet MaxConcurrentSessions (__added__) +* AWS::AppStream::Fleet Platform (__added__) +* AWS::AppStream::Fleet UsbDeviceFilterStrings (__added__) +* AWS::AppStream::Fleet ComputeCapacity.Required (__changed__) + * Old: true + * New: false +* AWS::Chatbot::SlackChannelConfiguration GuardrailPolicies (__added__) +* AWS::Chatbot::SlackChannelConfiguration UserRoleRequired (__added__) +* AWS::CloudFormation::StackSet ManagedExecution (__added__) +* AWS::CloudWatch::AnomalyDetector MetricMathAnomalyDetector (__added__) +* AWS::CloudWatch::AnomalyDetector SingleMetricAnomalyDetector (__added__) +* AWS::DataBrew::Job ValidationConfigurations (__added__) +* AWS::DataBrew::Job Recipe.PrimitiveType (__deleted__) +* AWS::EC2::VPCDHCPOptionsAssociation DhcpOptionsId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid +* AWS::EC2::VPCDHCPOptionsAssociation DhcpOptionsId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::VPCDHCPOptionsAssociation VpcId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid +* AWS::EC2::VPCEndpointService PayerResponsibility (__deleted__) +* AWS::Lambda::EventSourceMapping FilterCriteria (__added__) +* AWS::Logs::LogGroup Tags (__added__) +* AWS::Route53::HostedZone Name.Required (__changed__) + * Old: true + * New: false + +## Property Type Changes + +* AWS::CloudWatch::AnomalyDetector.MetricMathAnomalyDetector (__added__) +* AWS::CloudWatch::AnomalyDetector.SingleMetricAnomalyDetector (__added__) +* AWS::DataBrew::Dataset.Metadata (__added__) +* AWS::DataBrew::Job.AllowedStatistics (__added__) +* AWS::DataBrew::Job.EntityDetectorConfiguration (__added__) +* AWS::DataBrew::Job.ValidationConfiguration (__added__) +* AWS::MSK::Cluster.ConnectivityInfo (__added__) +* AWS::MSK::Cluster.PublicAccess (__added__) +* AWS::DataBrew::Dataset.DatabaseInputDefinition QueryString (__added__) +* AWS::DataBrew::Dataset.DatabaseInputDefinition GlueConnectionName.Required (__changed__) + * Old: false + * New: true +* AWS::DataBrew::Dataset.Input Metadata (__added__) +* AWS::DataBrew::Job.ProfileConfiguration EntityDetectorConfiguration (__added__) +* AWS::MSK::Cluster.BrokerNodeGroupInfo ConnectivityInfo (__added__) +* AWS::Transfer::Server.IdentityProviderDetails Function (__added__) + + +# CloudFormation Resource Specification v48.0.0 + +## New Resource Types + +* AWS::Batch::SchedulingPolicy +* AWS::IoTWireless::FuotaTask +* AWS::IoTWireless::MulticastGroup + +## Attribute Changes + +* AWS::EC2::NetworkInterface Id (__deleted__) +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddresses.DuplicatesAllowed (__deleted__) +* AWS::EC2::NetworkInterface Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html +* AWS::IoTAnalytics::Channel Id (__deleted__) +* AWS::IoTAnalytics::Dataset Id (__deleted__) +* AWS::IoTAnalytics::Datastore Id (__deleted__) +* AWS::IoTAnalytics::Pipeline Id (__deleted__) + +## Property Changes + +* AWS::ApiGateway::Stage Variables.DuplicatesAllowed (__deleted__) +* AWS::AppConfig::ConfigurationProfile Type (__added__) +* AWS::CloudFront::Function FunctionMetadata (__deleted__) +* AWS::CloudWatch::AnomalyDetector MetricName.Required (__changed__) + * Old: true + * New: false +* AWS::CloudWatch::AnomalyDetector Namespace.Required (__changed__) + * Old: true + * New: false +* AWS::CloudWatch::AnomalyDetector Stat.Required (__changed__) + * Old: true + * New: false +* AWS::EC2::CapacityReservation OutPostArn (__added__) +* AWS::EC2::CapacityReservation PlacementGroupArn (__added__) +* AWS::EC2::NetworkInterface Description.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-description + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-description +* AWS::EC2::NetworkInterface GroupSet.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-groupset + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-groupset +* AWS::EC2::NetworkInterface GroupSet.DuplicatesAllowed (__changed__) + * Old: true + * New: false +* AWS::EC2::NetworkInterface InterfaceType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-interfacetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-interfacetype +* AWS::EC2::NetworkInterface Ipv6AddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresscount +* AWS::EC2::NetworkInterface Ipv6Addresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresses +* AWS::EC2::NetworkInterface PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddress +* AWS::EC2::NetworkInterface PrivateIpAddresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddresses +* AWS::EC2::NetworkInterface PrivateIpAddresses.DuplicatesAllowed (__changed__) + * Old: true + * New: false +* AWS::EC2::NetworkInterface PrivateIpAddresses.UpdateType (__changed__) + * Old: Mutable + * New: Conditional +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-secondaryprivateipaddresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-secondaryprivateipcount +* AWS::EC2::NetworkInterface SourceDestCheck.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-sourcedestcheck + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-sourcedestcheck +* AWS::EC2::NetworkInterface SubnetId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-subnetid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-subnetid +* AWS::EC2::NetworkInterface Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-tags +* AWS::FSx::FileSystem FileSystemTypeVersion (__added__) +* AWS::FSx::FileSystem OntapConfiguration (__added__) +* AWS::FinSpace::Environment DataBundles (__added__) +* AWS::FinSpace::Environment SuperuserParameters (__added__) +* AWS::IoTAnalytics::Channel ChannelName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Channel Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Actions.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset ContentDeliveryRules.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset DatasetName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset LateDataRules.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Triggers.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Datastore Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline PipelineActivities.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline PipelineName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline Tags.DuplicatesAllowed (__deleted__) +* AWS::Lightsail::Instance Location (__deleted__) +* AWS::Lightsail::Instance State (__deleted__) +* AWS::MemoryDB::Cluster ClusterEndpoint (__deleted__) +* AWS::Redshift::Cluster Endpoint (__deleted__) +* AWS::S3ObjectLambda::AccessPoint ObjectLambdaConfiguration.Required (__changed__) + * Old: false + * New: true + +## Property Type Changes + +* AWS::CloudWatch::AnomalyDetector.Metric (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricDataQueries (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricDataQuery (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricStat (__added__) +* AWS::FSx::FileSystem.DiskIopsConfiguration (__added__) +* AWS::FSx::FileSystem.OntapConfiguration (__added__) +* AWS::FinSpace::Environment.SuperuserParameters (__added__) +* AWS::ApiGateway::Stage.CanarySetting StageVariableOverrides.DuplicatesAllowed (__deleted__) +* AWS::ApiGateway::Stage.MethodSetting CacheDataEncrypted.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted +* AWS::ApiGateway::Stage.MethodSetting CacheTtlInSeconds.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds +* AWS::ApiGateway::Stage.MethodSetting CachingEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled +* AWS::ApiGateway::Stage.MethodSetting DataTraceEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled +* AWS::ApiGateway::Stage.MethodSetting HttpMethod.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod +* AWS::ApiGateway::Stage.MethodSetting LoggingLevel.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel +* AWS::ApiGateway::Stage.MethodSetting MetricsEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled +* AWS::ApiGateway::Stage.MethodSetting ResourcePath.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath +* AWS::ApiGateway::Stage.MethodSetting ThrottlingBurstLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit +* AWS::ApiGateway::Stage.MethodSetting ThrottlingRateLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit +* AWS::AppMesh::GatewayRoute.GatewayRouteSpec Priority (__added__) +* AWS::CloudFront::Distribution.CacheBehavior ResponseHeadersPolicyId (__added__) +* AWS::CloudFront::Distribution.DefaultCacheBehavior ResponseHeadersPolicyId (__added__) +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification Primary.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-primary + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-primary +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-privateipaddress +* AWS::IoTAnalytics::Dataset.ContainerAction Variables.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-datasetcontentversionvalue-datasetname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-variable-datasetcontentversionvalue-datasetname +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-outputfileurivalue.html#cfn-iotanalytics-dataset-outputfileurivalue-filename + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-outputfileurivalue.html#cfn-iotanalytics-dataset-variable-outputfileurivalue-filename +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset.QueryAction Filters.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset.Schedule ScheduleExpression.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-schedule.html#cfn-iotanalytics-dataset-schedule-scheduleexpression + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-trigger-schedule.html#cfn-iotanalytics-dataset-trigger-schedule-scheduleexpression +* AWS::IoTAnalytics::Datastore.DatastorePartitions Partitions.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Datastore.IotSiteWiseMultiLayerStorage CustomerManagedS3Storage.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Datastore.SchemaDefinition Columns.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveItemType (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Type (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveType (__added__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.AddAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Channel ChannelName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Channel Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Datastore DatastoreName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Datastore Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich RoleArn.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich ThingName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich RoleArn.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich ThingName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Filter Filter.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Filter Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda BatchSize.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda LambdaName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Math.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.SelectAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::S3ObjectLambda::AccessPoint.TransformationConfiguration Actions.Required (__changed__) + * Old: false + * New: true +* AWS::S3ObjectLambda::AccessPoint.TransformationConfiguration ContentTransformation.Required (__changed__) + * Old: false + * New: true +* AWS::SecretsManager::RotationSchedule.HostedRotationLambda SuperuserSecretArn (__added__) +* AWS::SecretsManager::RotationSchedule.HostedRotationLambda SuperuserSecretKmsKeyArn (__added__) + + +# CloudFormation Resource Specification v47.0.0 + +## New Resource Types + +* AWS::CloudFront::ResponseHeadersPolicy +* AWS::DataSync::LocationHDFS +* AWS::IoT::Logging +* AWS::IoT::ResourceSpecificLogging +* AWS::Pinpoint::InAppTemplate +* AWS::Redshift::EndpointAccess +* AWS::Redshift::EndpointAuthorization +* AWS::Redshift::EventSubscription +* AWS::Redshift::ScheduledAction + +## Attribute Changes + +* AWS::EC2::InternetGateway InternetGatewayId (__added__) +* AWS::EC2::NetworkInterface Id (__added__) +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddresses.DuplicatesAllowed (__added__) +* AWS::EC2::NetworkInterface Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html +* AWS::IoTAnalytics::Channel Id (__added__) +* AWS::IoTAnalytics::Dataset Id (__added__) +* AWS::IoTAnalytics::Datastore Id (__added__) +* AWS::IoTAnalytics::Pipeline Id (__added__) + +## Property Changes + +* AWS::DMS::Endpoint RedisSettings (__added__) +* AWS::EC2::NetworkInterface Description.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-description + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-description +* AWS::EC2::NetworkInterface GroupSet.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-groupset + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-groupset +* AWS::EC2::NetworkInterface GroupSet.DuplicatesAllowed (__changed__) + * Old: false + * New: true +* AWS::EC2::NetworkInterface InterfaceType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-interfacetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-interfacetype +* AWS::EC2::NetworkInterface Ipv6AddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresscount +* AWS::EC2::NetworkInterface Ipv6Addresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresses +* AWS::EC2::NetworkInterface PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddress +* AWS::EC2::NetworkInterface PrivateIpAddresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddresses +* AWS::EC2::NetworkInterface PrivateIpAddresses.DuplicatesAllowed (__changed__) + * Old: false + * New: true +* AWS::EC2::NetworkInterface PrivateIpAddresses.UpdateType (__changed__) + * Old: Conditional + * New: Mutable +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-secondaryprivateipcount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-secondaryprivateipaddresscount +* AWS::EC2::NetworkInterface SourceDestCheck.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-sourcedestcheck + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-sourcedestcheck +* AWS::EC2::NetworkInterface SubnetId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-subnetid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-subnetid +* AWS::EC2::NetworkInterface Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-tags +* AWS::EC2::TransitGatewayPeeringAttachment Options (__added__) +* AWS::EC2::VPCEndpointService PayerResponsibility (__added__) +* AWS::EKS::Cluster Logging (__added__) +* AWS::EKS::Cluster Tags (__added__) +* AWS::EKS::Cluster ResourcesVpcConfig.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::IoTAnalytics::Channel ChannelName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Channel Tags.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset Actions.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset ContentDeliveryRules.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset DatasetName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Dataset LateDataRules.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset Tags.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset Triggers.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Datastore Tags.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline PipelineActivities.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline PipelineName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline Tags.DuplicatesAllowed (__added__) +* AWS::S3ObjectLambda::AccessPoint Name.Required (__changed__) + * Old: true + * New: false +* AWS::SageMaker::Endpoint RetainDeploymentConfig (__added__) + +## Property Type Changes + +* AWS::EKS::Cluster.Provider (__removed__) +* AWS::DMS::Endpoint.RedisSettings (__added__) +* AWS::EC2::EC2Fleet.CapacityRebalance (__added__) +* AWS::EC2::EC2Fleet.MaintenanceStrategies (__added__) +* AWS::EC2::TransitGatewayPeeringAttachment.TransitGatewayPeeringAttachmentOptions (__added__) +* AWS::EKS::Cluster.ClusterLogging (__added__) +* AWS::EKS::Cluster.Logging (__added__) +* AWS::EKS::Cluster.LoggingTypeConfig (__added__) +* AWS::NetworkFirewall::FirewallPolicy.StatefulEngineOptions (__added__) +* AWS::NetworkFirewall::RuleGroup.StatefulRuleOptions (__added__) +* AWS::DMS::Endpoint.KafkaSettings IncludeControlDetails (__added__) +* AWS::DMS::Endpoint.KafkaSettings IncludeNullAndEmpty (__added__) +* AWS::DMS::Endpoint.KafkaSettings IncludeTableAlterOperations (__added__) +* AWS::DMS::Endpoint.KafkaSettings IncludeTransactionDetails (__added__) +* AWS::DMS::Endpoint.KafkaSettings NoHexPrefix (__added__) +* AWS::DMS::Endpoint.KafkaSettings PartitionIncludeSchemaTable (__added__) +* AWS::DMS::Endpoint.KafkaSettings SaslPassword (__added__) +* AWS::DMS::Endpoint.KafkaSettings SaslUserName (__added__) +* AWS::DMS::Endpoint.KafkaSettings SecurityProtocol (__added__) +* AWS::DMS::Endpoint.KafkaSettings SslCaCertificateArn (__added__) +* AWS::DMS::Endpoint.KafkaSettings SslClientCertificateArn (__added__) +* AWS::DMS::Endpoint.KafkaSettings SslClientKeyArn (__added__) +* AWS::DMS::Endpoint.KafkaSettings SslClientKeyPassword (__added__) +* AWS::DMS::Endpoint.KinesisSettings IncludeControlDetails (__added__) +* AWS::DMS::Endpoint.KinesisSettings IncludeNullAndEmpty (__added__) +* AWS::DMS::Endpoint.KinesisSettings IncludeTableAlterOperations (__added__) +* AWS::DMS::Endpoint.KinesisSettings IncludeTransactionDetails (__added__) +* AWS::DMS::Endpoint.KinesisSettings NoHexPrefix (__added__) +* AWS::DMS::Endpoint.KinesisSettings PartitionIncludeSchemaTable (__added__) +* AWS::EC2::EC2Fleet.SpotOptionsRequest MaintenanceStrategies.PrimitiveType (__deleted__) +* AWS::EC2::EC2Fleet.SpotOptionsRequest MaintenanceStrategies.Type (__added__) +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification Primary.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-primary + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-primary +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-privateipaddress +* AWS::EKS::Cluster.EncryptionConfig Provider.Type (__deleted__) +* AWS::EKS::Cluster.EncryptionConfig Provider.PrimitiveType (__added__) +* AWS::EKS::Cluster.EncryptionConfig Provider.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EKS::Cluster.EncryptionConfig Resources.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EKS::Cluster.KubernetesNetworkConfig ServiceIpv4Cidr.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EKS::Cluster.ResourcesVpcConfig EndpointPrivateAccess (__added__) +* AWS::EKS::Cluster.ResourcesVpcConfig EndpointPublicAccess (__added__) +* AWS::EKS::Cluster.ResourcesVpcConfig PublicAccessCidrs (__added__) +* AWS::EKS::Cluster.ResourcesVpcConfig SecurityGroupIds.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EKS::Cluster.ResourcesVpcConfig SubnetIds.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::IoTAnalytics::Dataset.ContainerAction Variables.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-variable-datasetcontentversionvalue-datasetname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-datasetcontentversionvalue-datasetname +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-outputfileurivalue.html#cfn-iotanalytics-dataset-variable-outputfileurivalue-filename + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-outputfileurivalue.html#cfn-iotanalytics-dataset-outputfileurivalue-filename +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Dataset.QueryAction Filters.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Dataset.Schedule ScheduleExpression.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-trigger-schedule.html#cfn-iotanalytics-dataset-trigger-schedule-scheduleexpression + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-schedule.html#cfn-iotanalytics-dataset-schedule-scheduleexpression +* AWS::IoTAnalytics::Datastore.DatastorePartitions Partitions.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Datastore.IotSiteWiseMultiLayerStorage CustomerManagedS3Storage.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Datastore.SchemaDefinition Columns.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveType (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveItemType (__added__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Type (__added__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.AddAttributes Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Channel ChannelName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Channel Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Datastore DatastoreName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Datastore Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Attribute.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich RoleArn.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich ThingName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Attribute.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich RoleArn.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich ThingName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Filter Filter.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Filter Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Lambda BatchSize.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Lambda LambdaName.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Lambda Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Math Attribute.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Math Math.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.Math Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Name.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.DuplicatesAllowed (__added__) +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Pipeline.SelectAttributes Name.Required (__changed__) + * Old: false + * New: true +* AWS::NetworkFirewall::FirewallPolicy.FirewallPolicy StatefulDefaultActions (__added__) +* AWS::NetworkFirewall::FirewallPolicy.FirewallPolicy StatefulEngineOptions (__added__) +* AWS::NetworkFirewall::FirewallPolicy.StatefulRuleGroupReference Priority (__added__) +* AWS::NetworkFirewall::RuleGroup.RuleGroup StatefulRuleOptions (__added__) +* AWS::SageMaker::Endpoint.TrafficRoutingConfig LinearStepSize (__added__) + + +# CloudFormation Resource Specification v46.0.0 + +## New Resource Types + +* AWS::Connect::HoursOfOperation +* AWS::Connect::User +* AWS::Connect::UserHierarchyGroup +* AWS::EC2::CapacityReservationFleet +* AWS::IoT::JobTemplate +* AWS::Lightsail::Database +* AWS::Lightsail::StaticIp +* AWS::Panorama::ApplicationInstance +* AWS::Panorama::Package +* AWS::Panorama::PackageVersion +* AWS::Rekognition::Project +* AWS::Route53Resolver::ResolverConfig +* AWS::Wisdom::Assistant +* AWS::Wisdom::AssistantAssociation +* AWS::Wisdom::KnowledgeBase + +## Attribute Changes + +* AWS::ApiGateway::Authorizer AuthorizerId (__added__) +* AWS::AutoScaling::LifecycleHook Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html +* AWS::EC2::NetworkAcl Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html +* AWS::EC2::NetworkAcl Id (__added__) +* AWS::EC2::NetworkAclEntry Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html +* AWS::EC2::NetworkAclEntry Id (__added__) +* AWS::EC2::RouteTable Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html +* AWS::EC2::RouteTable RouteTableId (__added__) +* AWS::Lightsail::Instance KeyPairName (__deleted__) +* AWS::Lightsail::Instance UserData (__deleted__) +* AWS::Route53Resolver::ResolverRule TargetIps.PrimitiveType (__deleted__) +* AWS::Route53Resolver::ResolverRule TargetIps.DuplicatesAllowed (__added__) +* AWS::Route53Resolver::ResolverRule TargetIps.ItemType (__added__) +* AWS::Route53Resolver::ResolverRule TargetIps.Type (__added__) +* AWS::StepFunctions::Activity Arn (__added__) + +## Property Changes + +* AWS::ApiGateway::Authorizer Name.Required (__changed__) + * Old: false + * New: true +* AWS::ApiGateway::VpcLink Tags (__added__) +* AWS::AutoScaling::AutoScalingGroup DesiredCapacityType (__added__) +* AWS::AutoScaling::LifecycleHook AutoScalingGroupName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-autoscalinggroupname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-autoscalinggroupname +* AWS::AutoScaling::LifecycleHook DefaultResult.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-defaultresult + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-defaultresult +* AWS::AutoScaling::LifecycleHook HeartbeatTimeout.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-heartbeattimeout + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-heartbeattimeout +* AWS::AutoScaling::LifecycleHook LifecycleHookName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecyclehookname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecyclehookname +* AWS::AutoScaling::LifecycleHook LifecycleTransition.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-lifecycletransition + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecycletransition +* AWS::AutoScaling::LifecycleHook NotificationMetadata.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-notificationmetadata + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-notificationmetadata +* AWS::AutoScaling::LifecycleHook NotificationTargetARN.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-notificationtargetarn + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-notificationtargetarn +* AWS::AutoScaling::LifecycleHook RoleARN.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-rolearn + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-rolearn +* AWS::Batch::ComputeEnvironment UnmanagedvCpus (__added__) +* AWS::Batch::JobDefinition SchedulingPriority (__added__) +* AWS::Batch::JobQueue SchedulingPolicyArn (__added__) +* AWS::Budgets::BudgetsAction Subscribers.Required (__changed__) + * Old: false + * New: true +* AWS::Cassandra::Table DefaultTimeToLive (__added__) +* AWS::CodeStarNotifications::NotificationRule CreatedBy (__added__) +* AWS::CodeStarNotifications::NotificationRule EventTypeId (__added__) +* AWS::CodeStarNotifications::NotificationRule TargetAddress (__added__) +* AWS::EC2::NetworkAcl Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html#cfn-ec2-networkacl-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html#cfn-ec2-networkacl-tags +* AWS::EC2::NetworkAcl VpcId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html#cfn-ec2-networkacl-vpcid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html#cfn-ec2-networkacl-vpcid +* AWS::EC2::NetworkAclEntry CidrBlock.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-cidrblock + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-cidrblock +* AWS::EC2::NetworkAclEntry Egress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-egress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-egress +* AWS::EC2::NetworkAclEntry Icmp.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-icmp + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-icmp +* AWS::EC2::NetworkAclEntry Ipv6CidrBlock.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-ipv6cidrblock + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-ipv6cidrblock +* AWS::EC2::NetworkAclEntry NetworkAclId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-networkaclid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-networkaclid +* AWS::EC2::NetworkAclEntry PortRange.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-portrange + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-portrange +* AWS::EC2::NetworkAclEntry Protocol.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-protocol + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-protocol +* AWS::EC2::NetworkAclEntry RuleAction.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-ruleaction + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-ruleaction +* AWS::EC2::NetworkAclEntry RuleNumber.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-rulenumber + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-rulenumber +* AWS::EC2::RouteTable Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html#cfn-ec2-routetable-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html#cfn-ec2-routetable-tags +* AWS::EC2::RouteTable VpcId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html#cfn-ec2-routetable-vpcid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html#cfn-ec2-routetable-vpcid +* AWS::ECS::TaskDefinition RuntimePlatform (__added__) +* AWS::FMS::Policy ResourcesCleanUp (__added__) +* AWS::Glue::Registry Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::Glue::Schema Tags.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::ImageBuilder::InfrastructureConfiguration InstanceMetadataOptions (__added__) +* AWS::Lightsail::Instance KeyPairName (__added__) +* AWS::Lightsail::Instance UserData (__added__) +* AWS::Location::Tracker PositionFiltering (__added__) +* AWS::MWAA::Environment Tags.Type (__deleted__) +* AWS::MWAA::Environment Tags.PrimitiveType (__added__) +* AWS::MemoryDB::Cluster ACLName.Required (__changed__) + * Old: false + * New: true +* AWS::MemoryDB::Cluster NodeType.Required (__changed__) + * Old: false + * New: true +* AWS::MemoryDB::ParameterGroup Family.Required (__changed__) + * Old: false + * New: true +* AWS::MemoryDB::SubnetGroup SubnetIds.Required (__changed__) + * Old: false + * New: true +* AWS::Pinpoint::Campaign Priority (__added__) +* AWS::QuickSight::Analysis SourceEntity.Required (__changed__) + * Old: false + * New: true +* AWS::QuickSight::Dashboard SourceEntity.Required (__changed__) + * Old: false + * New: true +* AWS::QuickSight::Template SourceEntity.Required (__changed__) + * Old: false + * New: true +* AWS::Route53RecoveryControl::Cluster Tags (__added__) +* AWS::Route53RecoveryControl::ControlPanel Tags (__added__) +* AWS::Route53RecoveryControl::SafetyRule Tags (__added__) +* AWS::Route53Resolver::ResolverRule Tags.DuplicatesAllowed (__added__) +* AWS::Route53Resolver::ResolverRule TargetIps.DuplicatesAllowed (__added__) +* AWS::SageMaker::NotebookInstance PlatformIdentifier (__added__) +* AWS::StepFunctions::Activity Tags.DuplicatesAllowed (__added__) +* AWS::Synthetics::Canary ArtifactConfig (__added__) + +## Property Type Changes + +* AWS::MWAA::Environment.TagMap (__removed__) +* AWS::AppFlow::ConnectorProfile.OAuthProperties (__added__) +* AWS::AppFlow::ConnectorProfile.SAPODataConnectorProfileCredentials (__added__) +* AWS::AppFlow::ConnectorProfile.SAPODataConnectorProfileProperties (__added__) +* AWS::AppFlow::Flow.S3InputFormatConfig (__added__) +* AWS::AppFlow::Flow.SAPODataSourceProperties (__added__) +* AWS::AutoScaling::AutoScalingGroup.AcceleratorCountRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.AcceleratorTotalMemoryMiBRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.BaselineEbsBandwidthMbpsRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.InstanceRequirements (__added__) +* AWS::AutoScaling::AutoScalingGroup.MemoryGiBPerVCpuRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.MemoryMiBRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.NetworkInterfaceCountRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.TotalLocalStorageGBRequest (__added__) +* AWS::AutoScaling::AutoScalingGroup.VCpuCountRequest (__added__) +* AWS::EC2::EC2Fleet.AcceleratorCountRequest (__added__) +* AWS::EC2::EC2Fleet.AcceleratorTotalMemoryMiBRequest (__added__) +* AWS::EC2::EC2Fleet.BaselineEbsBandwidthMbpsRequest (__added__) +* AWS::EC2::EC2Fleet.InstanceRequirementsRequest (__added__) +* AWS::EC2::EC2Fleet.MemoryGiBPerVCpuRequest (__added__) +* AWS::EC2::EC2Fleet.MemoryMiBRequest (__added__) +* AWS::EC2::EC2Fleet.NetworkInterfaceCountRequest (__added__) +* AWS::EC2::EC2Fleet.TotalLocalStorageGBRequest (__added__) +* AWS::EC2::EC2Fleet.VCpuCountRangeRequest (__added__) +* AWS::EC2::SpotFleet.AcceleratorCountRequest (__added__) +* AWS::EC2::SpotFleet.AcceleratorTotalMemoryMiBRequest (__added__) +* AWS::EC2::SpotFleet.BaselineEbsBandwidthMbpsRequest (__added__) +* AWS::EC2::SpotFleet.InstanceRequirementsRequest (__added__) +* AWS::EC2::SpotFleet.MemoryGiBPerVCpuRequest (__added__) +* AWS::EC2::SpotFleet.MemoryMiBRequest (__added__) +* AWS::EC2::SpotFleet.NetworkInterfaceCountRequest (__added__) +* AWS::EC2::SpotFleet.TotalLocalStorageGBRequest (__added__) +* AWS::EC2::SpotFleet.VCpuCountRangeRequest (__added__) +* AWS::ECS::TaskDefinition.RuntimePlatform (__added__) +* AWS::ImageBuilder::InfrastructureConfiguration.InstanceMetadataOptions (__added__) +* AWS::Pinpoint::Campaign.CampaignInAppMessage (__added__) +* AWS::Pinpoint::Campaign.DefaultButtonConfiguration (__added__) +* AWS::Pinpoint::Campaign.InAppMessageBodyConfig (__added__) +* AWS::Pinpoint::Campaign.InAppMessageButton (__added__) +* AWS::Pinpoint::Campaign.InAppMessageContent (__added__) +* AWS::Pinpoint::Campaign.InAppMessageHeaderConfig (__added__) +* AWS::Pinpoint::Campaign.OverrideButtonConfiguration (__added__) +* AWS::QuickSight::DataSource.AmazonOpenSearchParameters (__added__) +* AWS::Synthetics::Canary.ArtifactConfig (__added__) +* AWS::Synthetics::Canary.S3Encryption (__added__) +* AWS::AppFlow::ConnectorProfile.ConnectorProfileCredentials SAPOData (__added__) +* AWS::AppFlow::ConnectorProfile.ConnectorProfileProperties SAPOData (__added__) +* AWS::AppFlow::Flow.ConnectorOperator SAPOData (__added__) +* AWS::AppFlow::Flow.S3SourceProperties S3InputFormatConfig (__added__) +* AWS::AppFlow::Flow.SourceConnectorProperties SAPOData (__added__) +* AWS::AutoScaling::AutoScalingGroup.LaunchTemplateOverrides InstanceRequirements (__added__) +* AWS::Backup::BackupSelection.BackupSelectionResourceType Conditions (__added__) +* AWS::Backup::BackupSelection.BackupSelectionResourceType NotResources (__added__) +* AWS::CodeBuild::Project.ProjectBuildBatchConfig BatchReportMode (__added__) +* AWS::EC2::EC2Fleet.FleetLaunchTemplateOverridesRequest InstanceRequirements (__added__) +* AWS::EC2::EC2Fleet.SpotOptionsRequest MaintenanceStrategies (__added__) +* AWS::EC2::EC2Fleet.TargetCapacitySpecificationRequest TargetCapacityUnitType (__added__) +* AWS::EC2::SpotFleet.LaunchTemplateOverrides InstanceRequirements (__added__) +* AWS::EC2::SpotFleet.SpotCapacityRebalance TerminationDelay (__added__) +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification InstanceRequirements (__added__) +* AWS::EC2::SpotFleet.SpotFleetLaunchSpecification InstanceType.Required (__changed__) + * Old: true + * New: false +* AWS::EC2::SpotFleet.SpotFleetRequestConfigData TargetCapacityUnitType (__added__) +* AWS::ImageBuilder::ContainerRecipe.EbsInstanceBlockDeviceSpecification Throughput (__added__) +* AWS::ImageBuilder::ImageRecipe.EbsInstanceBlockDeviceSpecification Throughput (__added__) +* AWS::Lightsail::Instance.Networking MonthlyTransfer.Type (__added__) +* AWS::Pinpoint::Campaign.Limits Session (__added__) +* AWS::Pinpoint::Campaign.MessageConfiguration InAppMessage (__added__) +* AWS::QuickSight::DataSource.DataSourceParameters AmazonOpenSearchParameters (__added__) + + # CloudFormation Resource Specification v43.0.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 a8900329a3917..0f28a38a86f87 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -24,13 +24,9 @@ async function main() { // iterate over all cloudformation namespaces for (const namespace of cfnspec.namespaces()) { - const [moduleFamily, moduleBaseName] = (namespace === 'AWS::Serverless' ? 'AWS::SAM' : namespace).split('::'); - - const moduleName = `${moduleFamily}-${moduleBaseName.replace(/V\d+$/, '')}`.toLocaleLowerCase(); - const packagePath = path.join(root, moduleName); - - const lowcaseModuleName = moduleBaseName.toLocaleLowerCase(); - const packageName = `@aws-cdk/${moduleName}`; + const module = cfnspec.createModuleDefinitionFromCfnNamespace(namespace); + const lowcaseModuleName = module.moduleName.toLocaleLowerCase(); + const packagePath = path.join(root, module.moduleName); // we already have a module for this namesapce, move on. if (await fs.pathExists(packagePath)) { @@ -42,12 +38,12 @@ async function main() { if (scopes.indexOf(namespace) !== -1) { // V2-style module is already modeled in the root package, nothing to be done! continue; - } else if (await fs.pathExists(path.join(root, `${moduleFamily}-${moduleBaseName}`.toLocaleLowerCase()))) { + } else if (await fs.pathExists(path.join(root, `${module.moduleFamily}-${module.moduleBaseName}`.toLocaleLowerCase()))) { // V2-style package already has it's own package (legacy behavior), nothing to be done! continue; } else { // V2-style package needs to be added to it's "V1" package... Get down to business! - console.error(`Adding ${namespace} to ${packageName}`); + console.error(`Adding ${namespace} to ${module.packageName}`); scopes.push(namespace); packageJson['cdk-build'].cloudformation = scopes; await fs.writeJson(packageJsonPath, packageJson, { encoding: 'utf-8', spaces: 2 }); @@ -62,22 +58,6 @@ async function main() { } } - // dotnet names - const dotnetPackage = `Amazon.CDK.${moduleFamily}.${moduleBaseName}`; - - // java names - const javaGroupId = 'software.amazon.awscdk'; - const javaPackage = moduleFamily === 'AWS' - ? `services.${lowcaseModuleName}` - : `${moduleFamily.toLocaleLowerCase()}.${lowcaseModuleName}`; - const javaArtifactId = moduleFamily === 'AWS' - ? lowcaseModuleName - : `${moduleFamily.toLocaleLowerCase()}-${lowcaseModuleName}`; - - // python names - const pythonDistName = `aws-cdk.${moduleName}`; - const pythonModuleName = pythonDistName.replace(/-/g, '_'); - async function write(relativePath: string, contents: string[] | string | object) { const fullPath = path.join(packagePath, relativePath); const dir = path.dirname(fullPath); @@ -97,10 +77,10 @@ async function main() { await fs.writeFile(fullPath, data + '\n'); } - console.log(`generating module for ${packageName}...`); + console.log(`generating module for ${module.packageName}...`); await write('package.json', { - name: packageName, + name: module.packageName, version, description: `The CDK Construct Library for ${namespace}`, main: 'lib/index.js', @@ -110,17 +90,17 @@ async function main() { projectReferences: true, targets: { dotnet: { - namespace: dotnetPackage, - packageId: dotnetPackage, + namespace: module.dotnetPackage, + packageId: module.dotnetPackage, signAssembly: true, assemblyOriginatorKeyFile: '../../key.snk', iconUrl: 'https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png', }, java: { - package: `${javaGroupId}.${javaPackage}`, + package: `${module.javaGroupId}.${module.javaPackage}`, maven: { - groupId: javaGroupId, - artifactId: javaArtifactId, + groupId: module.javaGroupId, + artifactId: module.javaArtifactId, }, }, python: { @@ -128,15 +108,15 @@ async function main() { 'Framework :: AWS CDK', 'Framework :: AWS CDK :: 1', ], - distName: pythonDistName, - module: pythonModuleName, + distName: module.pythonDistName, + module: module.pythonModuleName, }, }, }, repository: { type: 'git', url: 'https://github.com/aws/aws-cdk.git', - directory: `packages/${packageName}`, + directory: `packages/${module.packageName}`, }, homepage: 'https://github.com/aws/aws-cdk', scripts: { @@ -169,7 +149,7 @@ async function main() { 'cdk', 'constructs', namespace, - moduleName, + module.moduleName, ], author: { name: 'Amazon Web Services', @@ -271,28 +251,7 @@ async function main() { '});', ]); - await write('README.md', [ - `# ${namespace} 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]) are always stable and safe to use.', - '>', - '> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib', - '', - '---', - '', - '', - '', - 'This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.', - '', - '```ts', - `import ${lowcaseModuleName} = require('${packageName}');`, - '```', - ]); + await cfnspec.createLibraryReadme(namespace, path.join(packagePath, 'README.md')); await write('.eslintrc.js', [ "const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc');", @@ -310,10 +269,10 @@ async function main() { await fs.copy(path.join(templateDir, file), path.join(packagePath, file)); } - await addDependencyToMegaPackage(path.join('@aws-cdk', 'cloudformation-include'), packageName, version, ['dependencies', 'peerDependencies']); - await addDependencyToMegaPackage('aws-cdk-lib', packageName, version, ['devDependencies']); - await addDependencyToMegaPackage('monocdk', packageName, version, ['devDependencies']); - await addDependencyToMegaPackage('decdk', packageName, version, ['dependencies']); + await addDependencyToMegaPackage(path.join('@aws-cdk', 'cloudformation-include'), module.packageName, version, ['dependencies', 'peerDependencies']); + await addDependencyToMegaPackage('aws-cdk-lib', module.packageName, version, ['devDependencies']); + await addDependencyToMegaPackage('monocdk', module.packageName, version, ['devDependencies']); + await addDependencyToMegaPackage('decdk', module.packageName, version, ['dependencies']); } } diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index f3986a67888db..cfb3b31ea7b28 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -43.0.0 +49.0.0 diff --git a/packages/@aws-cdk/cfnspec/lib/index.ts b/packages/@aws-cdk/cfnspec/lib/index.ts index 6ab020d9580cc..17ef1bde49c36 100644 --- a/packages/@aws-cdk/cfnspec/lib/index.ts +++ b/packages/@aws-cdk/cfnspec/lib/index.ts @@ -3,6 +3,7 @@ import { CfnLintFileSchema } from './_private_schema/cfn-lint'; import * as schema from './schema'; export { schema }; export * from './canned-metrics'; +export * from './library-creation'; /** * The complete AWS CloudFormation Resource specification, having any CDK patches and enhancements included in it. diff --git a/packages/@aws-cdk/cfnspec/lib/library-creation.ts b/packages/@aws-cdk/cfnspec/lib/library-creation.ts new file mode 100644 index 0000000000000..85292a89d2749 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/library-creation.ts @@ -0,0 +1,83 @@ +import * as fs from 'fs-extra'; + +export interface ModuleDefinition { + readonly namespace: string; + readonly moduleName: string; + readonly moduleFamily: string; + readonly moduleBaseName: string; + readonly packageName: string; + + readonly dotnetPackage: string; + readonly javaGroupId: string; + readonly javaPackage: string; + readonly javaArtifactId: string; + + readonly pythonDistName: string; + readonly pythonModuleName: string; +} + +export function createModuleDefinitionFromCfnNamespace(namespace: string): ModuleDefinition { + const [moduleFamily, moduleBaseName] = (namespace === 'AWS::Serverless' ? 'AWS::SAM' : namespace).split('::'); + const moduleName = `${moduleFamily}-${moduleBaseName.replace(/V\d+$/, '')}`.toLocaleLowerCase(); + + const lowcaseModuleName = moduleBaseName.toLocaleLowerCase(); + const packageName = `@aws-cdk/${moduleName}`; + + // dotnet names + const dotnetPackage = `Amazon.CDK.${moduleFamily}.${moduleBaseName}`; + + // java names + const javaGroupId = 'software.amazon.awscdk'; + const javaPackage = moduleFamily === 'AWS' + ? `services.${lowcaseModuleName}` + : `${moduleFamily.toLocaleLowerCase()}.${lowcaseModuleName}`; + const javaArtifactId = moduleFamily === 'AWS' + ? lowcaseModuleName + : `${moduleFamily.toLocaleLowerCase()}-${lowcaseModuleName}`; + + // python names + const pythonDistName = `aws-cdk.${moduleName}`; + const pythonModuleName = pythonDistName.replace(/-/g, '_'); + + return { + namespace, + moduleName, + moduleFamily, + moduleBaseName, + packageName, + dotnetPackage, + javaGroupId, + javaPackage, + javaArtifactId, + pythonDistName, + pythonModuleName, + }; +} + +export async function createLibraryReadme(namespace: string, readmePath: string) { + const module = createModuleDefinitionFromCfnNamespace(namespace); + + await fs.writeFile(readmePath, [ + `# ${namespace} 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]) are always stable and safe to use.', + '>', + '> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib', + '', + '---', + '', + '', + '', + 'This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.', + '', + '```ts', + `import ${module.moduleName.toLocaleLowerCase()} = require('${module.packageName}');`, + '```', + '', + ].join('\n'), 'utf8'); +} diff --git a/packages/@aws-cdk/cfnspec/package.json b/packages/@aws-cdk/cfnspec/package.json index 1109498bfe092..c871edefcaa16 100644 --- a/packages/@aws-cdk/cfnspec/package.json +++ b/packages/@aws-cdk/cfnspec/package.json @@ -32,15 +32,15 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/md5": "^2.3.1", "fast-json-patch": "^2.2.1", - "fs-extra": "^9.1.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "json-diff": "^0.5.4", "sort-json": "^2.0.0" }, "dependencies": { + "fs-extra": "^9.1.0", "md5": "^2.3.0" }, "repository": { diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index b5b29349fe4e4..5dd730e2bce6d 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -1783,7 +1783,6 @@ }, "StageVariableOverrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-canarysetting.html#cfn-apigateway-stage-canarysetting-stagevariableoverrides", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -1798,64 +1797,64 @@ } }, "AWS::ApiGateway::Stage.MethodSetting": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html", "Properties": { "CacheDataEncrypted": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "CacheTtlInSeconds": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "CachingEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "DataTraceEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "HttpMethod": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "LoggingLevel": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "MetricsEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "ResourcePath": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "ThrottlingBurstLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "ThrottlingRateLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit", "PrimitiveType": "Double", "Required": false, "UpdateType": "Mutable" @@ -2557,6 +2556,12 @@ "Type": "RedshiftConnectorProfileCredentials", "UpdateType": "Mutable" }, + "SAPOData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-connectorprofilecredentials.html#cfn-appflow-connectorprofile-connectorprofilecredentials-sapodata", + "Required": false, + "Type": "SAPODataConnectorProfileCredentials", + "UpdateType": "Mutable" + }, "Salesforce": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-connectorprofilecredentials.html#cfn-appflow-connectorprofile-connectorprofilecredentials-salesforce", "Required": false, @@ -2640,6 +2645,12 @@ "Type": "RedshiftConnectorProfileProperties", "UpdateType": "Mutable" }, + "SAPOData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-connectorprofileproperties.html#cfn-appflow-connectorprofile-connectorprofileproperties-sapodata", + "Required": false, + "Type": "SAPODataConnectorProfileProperties", + "UpdateType": "Mutable" + }, "Salesforce": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-connectorprofileproperties.html#cfn-appflow-connectorprofile-connectorprofileproperties-salesforce", "Required": false, @@ -2843,6 +2854,31 @@ } } }, + "AWS::AppFlow::ConnectorProfile.OAuthProperties": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-oauthproperties.html", + "Properties": { + "AuthCodeUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-oauthproperties.html#cfn-appflow-connectorprofile-oauthproperties-authcodeurl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "OAuthScopes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-oauthproperties.html#cfn-appflow-connectorprofile-oauthproperties-oauthscopes", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TokenUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-oauthproperties.html#cfn-appflow-connectorprofile-oauthproperties-tokenurl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AppFlow::ConnectorProfile.RedshiftConnectorProfileCredentials": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-redshiftconnectorprofilecredentials.html", "Properties": { @@ -2889,6 +2925,70 @@ } } }, + "AWS::AppFlow::ConnectorProfile.SAPODataConnectorProfileCredentials": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofilecredentials.html", + "Properties": { + "BasicAuthCredentials": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofilecredentials.html#cfn-appflow-connectorprofile-sapodataconnectorprofilecredentials-basicauthcredentials", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "OAuthCredentials": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofilecredentials.html#cfn-appflow-connectorprofile-sapodataconnectorprofilecredentials-oauthcredentials", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppFlow::ConnectorProfile.SAPODataConnectorProfileProperties": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html", + "Properties": { + "ApplicationHostUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-applicationhosturl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ApplicationServicePath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-applicationservicepath", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ClientNumber": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-clientnumber", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LogonLanguage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-logonlanguage", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "OAuthProperties": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-oauthproperties", + "Required": false, + "Type": "OAuthProperties", + "UpdateType": "Mutable" + }, + "PortNumber": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-portnumber", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "PrivateLinkServiceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-sapodataconnectorprofileproperties.html#cfn-appflow-connectorprofile-sapodataconnectorprofileproperties-privatelinkservicename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AppFlow::ConnectorProfile.SalesforceConnectorProfileCredentials": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-connectorprofile-salesforceconnectorprofilecredentials.html", "Properties": { @@ -3224,6 +3324,12 @@ "Required": false, "UpdateType": "Mutable" }, + "SAPOData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-connectoroperator.html#cfn-appflow-flow-connectoroperator-sapodata", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Salesforce": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-connectoroperator.html#cfn-appflow-flow-connectoroperator-salesforce", "PrimitiveType": "String", @@ -3530,6 +3636,17 @@ } } }, + "AWS::AppFlow::Flow.S3InputFormatConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-s3inputformatconfig.html", + "Properties": { + "S3InputFileType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-s3inputformatconfig.html#cfn-appflow-flow-s3inputformatconfig-s3inputfiletype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AppFlow::Flow.S3OutputFormatConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-s3outputformatconfig.html", "Properties": { @@ -3567,6 +3684,23 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" + }, + "S3InputFormatConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-s3sourceproperties.html#cfn-appflow-flow-s3sourceproperties-s3inputformatconfig", + "Required": false, + "Type": "S3InputFormatConfig", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppFlow::Flow.SAPODataSourceProperties": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-sapodatasourceproperties.html", + "Properties": { + "ObjectPath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-sapodatasourceproperties.html#cfn-appflow-flow-sapodatasourceproperties-objectpath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" } } }, @@ -3771,6 +3905,12 @@ "Type": "S3SourceProperties", "UpdateType": "Mutable" }, + "SAPOData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-sourceconnectorproperties.html#cfn-appflow-flow-sourceconnectorproperties-sapodata", + "Required": false, + "Type": "SAPODataSourceProperties", + "UpdateType": "Mutable" + }, "Salesforce": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-sourceconnectorproperties.html#cfn-appflow-flow-sourceconnectorproperties-salesforce", "Required": false, @@ -4212,6 +4352,12 @@ "Required": false, "Type": "HttpGatewayRoute", "UpdateType": "Mutable" + }, + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html#cfn-appmesh-gatewayroute-gatewayroutespec-priority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -6748,6 +6894,69 @@ } } }, + "AWS::AppStream::AppBlock.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html#cfn-appstream-appblock-s3location-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "S3Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html#cfn-appstream-appblock-s3location-s3key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppStream::AppBlock.ScriptDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html", + "Properties": { + "ExecutableParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-executableparameters", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "ExecutablePath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-executablepath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ScriptS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-scripts3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Immutable" + }, + "TimeoutInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-timeoutinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppStream::Application.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html#cfn-appstream-application-s3location-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html#cfn-appstream-application-s3location-s3key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::AppStream::DirectoryConfig.ServiceAccountCredentials": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-directoryconfig-serviceaccountcredentials.html", "Properties": { @@ -8309,6 +8518,202 @@ } } }, + "AWS::AutoScaling::AutoScalingGroup.AcceleratorCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratorcountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratorcountrequest.html#cfn-autoscaling-autoscalinggroup-acceleratorcountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratorcountrequest.html#cfn-autoscaling-autoscalinggroup-acceleratorcountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AutoScaling::AutoScalingGroup.AcceleratorTotalMemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratortotalmemorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratortotalmemorymibrequest.html#cfn-autoscaling-autoscalinggroup-acceleratortotalmemorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-acceleratortotalmemorymibrequest.html#cfn-autoscaling-autoscalinggroup-acceleratortotalmemorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AutoScaling::AutoScalingGroup.BaselineEbsBandwidthMbpsRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-baselineebsbandwidthmbpsrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-baselineebsbandwidthmbpsrequest.html#cfn-autoscaling-autoscalinggroup-baselineebsbandwidthmbpsrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-baselineebsbandwidthmbpsrequest.html#cfn-autoscaling-autoscalinggroup-baselineebsbandwidthmbpsrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AutoScaling::AutoScalingGroup.InstanceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html", + "Properties": { + "AcceleratorCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-acceleratorcount", + "Required": false, + "Type": "AcceleratorCountRequest", + "UpdateType": "Mutable" + }, + "AcceleratorManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-acceleratormanufacturers", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AcceleratorNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-acceleratornames", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AcceleratorTotalMemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-acceleratortotalmemorymib", + "Required": false, + "Type": "AcceleratorTotalMemoryMiBRequest", + "UpdateType": "Mutable" + }, + "AcceleratorTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-acceleratortypes", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "BareMetal": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-baremetal", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "BaselineEbsBandwidthMbps": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-baselineebsbandwidthmbps", + "Required": false, + "Type": "BaselineEbsBandwidthMbpsRequest", + "UpdateType": "Mutable" + }, + "BurstablePerformance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-burstableperformance", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "CpuManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-cpumanufacturers", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ExcludedInstanceTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-excludedinstancetypes", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "InstanceGenerations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-instancegenerations", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "LocalStorage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-localstorage", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LocalStorageTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-localstoragetypes", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "MemoryGiBPerVCpu": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-memorygibpervcpu", + "Required": false, + "Type": "MemoryGiBPerVCpuRequest", + "UpdateType": "Mutable" + }, + "MemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-memorymib", + "Required": false, + "Type": "MemoryMiBRequest", + "UpdateType": "Mutable" + }, + "NetworkInterfaceCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-networkinterfacecount", + "Required": false, + "Type": "NetworkInterfaceCountRequest", + "UpdateType": "Mutable" + }, + "OnDemandMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-ondemandmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "RequireHibernateSupport": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-requirehibernatesupport", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "SpotMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-spotmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "TotalLocalStorageGB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-totallocalstoragegb", + "Required": false, + "Type": "TotalLocalStorageGBRequest", + "UpdateType": "Mutable" + }, + "VCpuCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancerequirements.html#cfn-autoscaling-autoscalinggroup-instancerequirements-vcpucount", + "Required": false, + "Type": "VCpuCountRequest", + "UpdateType": "Mutable" + } + } + }, "AWS::AutoScaling::AutoScalingGroup.InstancesDistribution": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-instancesdistribution.html", "Properties": { @@ -8372,6 +8777,12 @@ "AWS::AutoScaling::AutoScalingGroup.LaunchTemplateOverrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-launchtemplateoverrides.html", "Properties": { + "InstanceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-launchtemplateoverrides.html#cfn-as-mixedinstancespolicy-instancerequirements", + "Required": false, + "Type": "InstanceRequirements", + "UpdateType": "Mutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-as-mixedinstancespolicy-launchtemplateoverrides.html#cfn-autoscaling-autoscalinggroup-launchtemplateoverrides-instancetype", "PrimitiveType": "String", @@ -8462,6 +8873,40 @@ } } }, + "AWS::AutoScaling::AutoScalingGroup.MemoryGiBPerVCpuRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorygibpervcpurequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorygibpervcpurequest.html#cfn-autoscaling-autoscalinggroup-memorygibpervcpurequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorygibpervcpurequest.html#cfn-autoscaling-autoscalinggroup-memorygibpervcpurequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AutoScaling::AutoScalingGroup.MemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorymibrequest.html#cfn-autoscaling-autoscalinggroup-memorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-memorymibrequest.html#cfn-autoscaling-autoscalinggroup-memorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AutoScaling::AutoScalingGroup.MetricsCollection": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-metricscollection.html", "Properties": { @@ -8498,6 +8943,23 @@ } } }, + "AWS::AutoScaling::AutoScalingGroup.NetworkInterfaceCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-networkinterfacecountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-networkinterfacecountrequest.html#cfn-autoscaling-autoscalinggroup-networkinterfacecountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-networkinterfacecountrequest.html#cfn-autoscaling-autoscalinggroup-networkinterfacecountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AutoScaling::AutoScalingGroup.NotificationConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-notificationconfigurations.html", "Properties": { @@ -8540,6 +9002,40 @@ } } }, + "AWS::AutoScaling::AutoScalingGroup.TotalLocalStorageGBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-totallocalstoragegbrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-totallocalstoragegbrequest.html#cfn-autoscaling-autoscalinggroup-totallocalstoragegbrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-totallocalstoragegbrequest.html#cfn-autoscaling-autoscalinggroup-totallocalstoragegbrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AutoScaling::AutoScalingGroup.VCpuCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-vcpucountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-vcpucountrequest.html#cfn-autoscaling-autoscalinggroup-vcpucountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-vcpucountrequest.html#cfn-autoscaling-autoscalinggroup-vcpucountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AutoScaling::LaunchConfiguration.BlockDevice": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-launchconfig-blockdev-template.html", "Properties": { @@ -9318,6 +9814,12 @@ "AWS::Backup::BackupSelection.BackupSelectionResourceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupselection-backupselectionresourcetype.html", "Properties": { + "Conditions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupselection-backupselectionresourcetype.html#cfn-backup-backupselection-backupselectionresourcetype-conditions", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, "IamRoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupselection-backupselectionresourcetype.html#cfn-backup-backupselection-backupselectionresourcetype-iamrolearn", "PrimitiveType": "String", @@ -9332,6 +9834,14 @@ "Type": "List", "UpdateType": "Immutable" }, + "NotResources": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupselection-backupselectionresourcetype.html#cfn-backup-backupselection-backupselectionresourcetype-notresources", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, "Resources": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-backup-backupselection-backupselectionresourcetype.html#cfn-backup-backupselection-backupselectionresourcetype-resources", "DuplicatesAllowed": true, @@ -10174,6 +10684,47 @@ } } }, + "AWS::Batch::SchedulingPolicy.FairsharePolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html", + "Properties": { + "ComputeReservation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-computereservation", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "ShareDecaySeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-sharedecayseconds", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "ShareDistribution": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-sharedistribution", + "ItemType": "ShareAttributes", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Batch::SchedulingPolicy.ShareAttributes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html", + "Properties": { + "ShareIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html#cfn-batch-schedulingpolicy-shareattributes-shareidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "WeightFactor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html#cfn-batch-schedulingpolicy-shareattributes-weightfactor", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Budgets::Budget.BudgetData": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-budgets-budget-budgetdata.html", "Properties": { @@ -11083,6 +11634,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ResponseHeadersPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-responseheaderspolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "SmoothStreaming": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-smoothstreaming", "PrimitiveType": "Boolean", @@ -11299,6 +11856,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ResponseHeadersPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-responseheaderspolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "SmoothStreaming": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-smoothstreaming", "PrimitiveType": "Boolean", @@ -12095,6 +12658,333 @@ } } }, + "AWS::CloudFront::ResponseHeadersPolicy.AccessControlAllowHeaders": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolallowheaders.html", + "Properties": { + "Items": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolallowheaders.html#cfn-cloudfront-responseheaderspolicy-accesscontrolallowheaders-items", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.AccessControlAllowMethods": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolallowmethods.html", + "Properties": { + "Items": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolallowmethods.html#cfn-cloudfront-responseheaderspolicy-accesscontrolallowmethods-items", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.AccessControlAllowOrigins": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolalloworigins.html", + "Properties": { + "Items": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolalloworigins.html#cfn-cloudfront-responseheaderspolicy-accesscontrolalloworigins-items", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.AccessControlExposeHeaders": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolexposeheaders.html", + "Properties": { + "Items": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-accesscontrolexposeheaders.html#cfn-cloudfront-responseheaderspolicy-accesscontrolexposeheaders-items", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.ContentSecurityPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-contentsecuritypolicy.html", + "Properties": { + "ContentSecurityPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-contentsecuritypolicy.html#cfn-cloudfront-responseheaderspolicy-contentsecuritypolicy-contentsecuritypolicy", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-contentsecuritypolicy.html#cfn-cloudfront-responseheaderspolicy-contentsecuritypolicy-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.ContentTypeOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-contenttypeoptions.html", + "Properties": { + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-contenttypeoptions.html#cfn-cloudfront-responseheaderspolicy-contenttypeoptions-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.CorsConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html", + "Properties": { + "AccessControlAllowCredentials": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolallowcredentials", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "AccessControlAllowHeaders": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolallowheaders", + "Required": true, + "Type": "AccessControlAllowHeaders", + "UpdateType": "Mutable" + }, + "AccessControlAllowMethods": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolallowmethods", + "Required": true, + "Type": "AccessControlAllowMethods", + "UpdateType": "Mutable" + }, + "AccessControlAllowOrigins": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolalloworigins", + "Required": true, + "Type": "AccessControlAllowOrigins", + "UpdateType": "Mutable" + }, + "AccessControlExposeHeaders": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolexposeheaders", + "Required": false, + "Type": "AccessControlExposeHeaders", + "UpdateType": "Mutable" + }, + "AccessControlMaxAgeSec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-accesscontrolmaxagesec", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "OriginOverride": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-corsconfig.html#cfn-cloudfront-responseheaderspolicy-corsconfig-originoverride", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.CustomHeader": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheader.html", + "Properties": { + "Header": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheader.html#cfn-cloudfront-responseheaderspolicy-customheader-header", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheader.html#cfn-cloudfront-responseheaderspolicy-customheader-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheader.html#cfn-cloudfront-responseheaderspolicy-customheader-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.CustomHeadersConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheadersconfig.html", + "Properties": { + "Items": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-customheadersconfig.html#cfn-cloudfront-responseheaderspolicy-customheadersconfig-items", + "DuplicatesAllowed": true, + "ItemType": "CustomHeader", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.FrameOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-frameoptions.html", + "Properties": { + "FrameOption": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-frameoptions.html#cfn-cloudfront-responseheaderspolicy-frameoptions-frameoption", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-frameoptions.html#cfn-cloudfront-responseheaderspolicy-frameoptions-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.ReferrerPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-referrerpolicy.html", + "Properties": { + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-referrerpolicy.html#cfn-cloudfront-responseheaderspolicy-referrerpolicy-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "ReferrerPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-referrerpolicy.html#cfn-cloudfront-responseheaderspolicy-referrerpolicy-referrerpolicy", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.ResponseHeadersPolicyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html", + "Properties": { + "Comment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig-comment", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "CorsConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig-corsconfig", + "Required": false, + "Type": "CorsConfig", + "UpdateType": "Mutable" + }, + "CustomHeadersConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig-customheadersconfig", + "Required": false, + "Type": "CustomHeadersConfig", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SecurityHeadersConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-responseheaderspolicyconfig.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig-securityheadersconfig", + "Required": false, + "Type": "SecurityHeadersConfig", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.SecurityHeadersConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html", + "Properties": { + "ContentSecurityPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-contentsecuritypolicy", + "Required": false, + "Type": "ContentSecurityPolicy", + "UpdateType": "Mutable" + }, + "ContentTypeOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-contenttypeoptions", + "Required": false, + "Type": "ContentTypeOptions", + "UpdateType": "Mutable" + }, + "FrameOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-frameoptions", + "Required": false, + "Type": "FrameOptions", + "UpdateType": "Mutable" + }, + "ReferrerPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-referrerpolicy", + "Required": false, + "Type": "ReferrerPolicy", + "UpdateType": "Mutable" + }, + "StrictTransportSecurity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-stricttransportsecurity", + "Required": false, + "Type": "StrictTransportSecurity", + "UpdateType": "Mutable" + }, + "XSSProtection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-securityheadersconfig.html#cfn-cloudfront-responseheaderspolicy-securityheadersconfig-xssprotection", + "Required": false, + "Type": "XSSProtection", + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.StrictTransportSecurity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-stricttransportsecurity.html", + "Properties": { + "AccessControlMaxAgeSec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-stricttransportsecurity.html#cfn-cloudfront-responseheaderspolicy-stricttransportsecurity-accesscontrolmaxagesec", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "IncludeSubdomains": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-stricttransportsecurity.html#cfn-cloudfront-responseheaderspolicy-stricttransportsecurity-includesubdomains", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-stricttransportsecurity.html#cfn-cloudfront-responseheaderspolicy-stricttransportsecurity-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "Preload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-stricttransportsecurity.html#cfn-cloudfront-responseheaderspolicy-stricttransportsecurity-preload", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudFront::ResponseHeadersPolicy.XSSProtection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-xssprotection.html", + "Properties": { + "ModeBlock": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-xssprotection.html#cfn-cloudfront-responseheaderspolicy-xssprotection-modeblock", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Override": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-xssprotection.html#cfn-cloudfront-responseheaderspolicy-xssprotection-override", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "Protection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-xssprotection.html#cfn-cloudfront-responseheaderspolicy-xssprotection-protection", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "ReportUri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-responseheaderspolicy-xssprotection.html#cfn-cloudfront-responseheaderspolicy-xssprotection-reporturi", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::CloudFront::StreamingDistribution.Logging": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-streamingdistribution-logging.html", "Properties": { @@ -12417,6 +13307,125 @@ } } }, + "AWS::CloudWatch::AnomalyDetector.Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html", + "Properties": { + "Dimensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-dimensions", + "ItemType": "Dimension", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MetricName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-metricname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Namespace": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-namespace", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricDataQueries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataqueries.html", + "ItemType": "MetricDataQuery", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AWS::CloudWatch::AnomalyDetector.MetricDataQuery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html", + "Properties": { + "AccountId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-accountid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Expression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-expression", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Id": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-id", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Label": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-label", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MetricStat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-metricstat", + "Required": false, + "Type": "MetricStat", + "UpdateType": "Immutable" + }, + "Period": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-period", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "ReturnData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-returndata", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricMathAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricmathanomalydetector.html", + "Properties": { + "MetricDataQueries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricmathanomalydetector.html#cfn-cloudwatch-anomalydetector-metricmathanomalydetector-metricdataqueries", + "ItemType": "MetricDataQuery", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricStat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html", + "Properties": { + "Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-metric", + "Required": true, + "Type": "Metric", + "UpdateType": "Immutable" + }, + "Period": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-period", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Immutable" + }, + "Stat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-stat", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Unit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-unit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::CloudWatch::AnomalyDetector.Range": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-range.html", "Properties": { @@ -12434,6 +13443,36 @@ } } }, + "AWS::CloudWatch::AnomalyDetector.SingleMetricAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html", + "Properties": { + "Dimensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-dimensions", + "ItemType": "Dimension", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MetricName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-metricname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Namespace": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-namespace", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Stat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-stat", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::CloudWatch::InsightRule.Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-insightrule-tags.html", "ItemType": "Tag", @@ -12684,6 +13723,12 @@ "AWS::CodeBuild::Project.ProjectBuildBatchConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html", "Properties": { + "BatchReportMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-batchreportmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "CombineArtifacts": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-combineartifacts", "PrimitiveType": "Boolean", @@ -15284,6 +16329,46 @@ } } }, + "AWS::Connect::HoursOfOperation.HoursOfOperationConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationconfig.html", + "Properties": { + "Day": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationconfig.html#cfn-connect-hoursofoperation-hoursofoperationconfig-day", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "EndTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationconfig.html#cfn-connect-hoursofoperation-hoursofoperationconfig-endtime", + "Required": true, + "Type": "HoursOfOperationTimeSlice", + "UpdateType": "Mutable" + }, + "StartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationconfig.html#cfn-connect-hoursofoperation-hoursofoperationconfig-starttime", + "Required": true, + "Type": "HoursOfOperationTimeSlice", + "UpdateType": "Mutable" + } + } + }, + "AWS::Connect::HoursOfOperation.HoursOfOperationTimeSlice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationtimeslice.html", + "Properties": { + "Hours": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationtimeslice.html#cfn-connect-hoursofoperation-hoursofoperationtimeslice-hours", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Minutes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-hoursofoperation-hoursofoperationtimeslice.html#cfn-connect-hoursofoperation-hoursofoperationtimeslice-minutes", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Connect::QuickConnect.PhoneNumberQuickConnectConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-quickconnect-phonenumberquickconnectconfig.html", "Properties": { @@ -15358,6 +16443,58 @@ } } }, + "AWS::Connect::User.UserIdentityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-useridentityinfo.html", + "Properties": { + "Email": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-useridentityinfo.html#cfn-connect-user-useridentityinfo-email", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "FirstName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-useridentityinfo.html#cfn-connect-user-useridentityinfo-firstname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LastName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-useridentityinfo.html#cfn-connect-user-useridentityinfo-lastname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Connect::User.UserPhoneConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-userphoneconfig.html", + "Properties": { + "AfterContactWorkTimeLimit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-userphoneconfig.html#cfn-connect-user-userphoneconfig-aftercontactworktimelimit", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "AutoAccept": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-userphoneconfig.html#cfn-connect-user-userphoneconfig-autoaccept", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "DeskPhoneNumber": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-userphoneconfig.html#cfn-connect-user-userphoneconfig-deskphonenumber", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PhoneType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-connect-user-userphoneconfig.html#cfn-connect-user-userphoneconfig-phonetype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::CustomerProfiles::Integration.ConnectorOperator": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-customerprofiles-integration-connectoroperator.html", "Properties": { @@ -16343,6 +17480,84 @@ "Required": false, "UpdateType": "Mutable" }, + "IncludeControlDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-includecontroldetails", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeNullAndEmpty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-includenullandempty", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeTableAlterOperations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-includetablealteroperations", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeTransactionDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-includetransactiondetails", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "NoHexPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-nohexprefix", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "PartitionIncludeSchemaTable": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-partitionincludeschematable", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "SaslPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-saslpassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SaslUserName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-saslusername", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SecurityProtocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-securityprotocol", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslCaCertificateArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-sslcacertificatearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslClientCertificateArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-sslclientcertificatearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslClientKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-sslclientkeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslClientKeyPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-sslclientkeypassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Topic": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kafkasettings.html#cfn-dms-endpoint-kafkasettings-topic", "PrimitiveType": "String", @@ -16354,12 +17569,48 @@ "AWS::DMS::Endpoint.KinesisSettings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html", "Properties": { + "IncludeControlDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-includecontroldetails", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeNullAndEmpty": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-includenullandempty", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeTableAlterOperations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-includetablealteroperations", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IncludeTransactionDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-includetransactiondetails", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "MessageFormat": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-messageformat", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, + "NoHexPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-nohexprefix", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "PartitionIncludeSchemaTable": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-partitionincludeschematable", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "ServiceAccessRoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-kinesissettings.html#cfn-dms-endpoint-kinesissettings-serviceaccessrolearn", "PrimitiveType": "String", @@ -16584,6 +17835,53 @@ } } }, + "AWS::DMS::Endpoint.RedisSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html", + "Properties": { + "AuthPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-authpassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AuthType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-authtype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AuthUserName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-authusername", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-port", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "ServerName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-servername", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslCaCertificateArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-sslcacertificatearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SslSecurityProtocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redissettings.html#cfn-dms-endpoint-redissettings-sslsecurityprotocol", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DMS::Endpoint.RedshiftSettings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-redshiftsettings.html", "Properties": { @@ -16723,6 +18021,12 @@ "GlueConnectionName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-databaseinputdefinition.html#cfn-databrew-dataset-databaseinputdefinition-glueconnectionname", "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "QueryString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-databaseinputdefinition.html#cfn-databrew-dataset-databaseinputdefinition-querystring", + "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, @@ -16913,6 +18217,12 @@ "Type": "DatabaseInputDefinition", "UpdateType": "Mutable" }, + "Metadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-input.html#cfn-databrew-dataset-input-metadata", + "Required": false, + "Type": "Metadata", + "UpdateType": "Mutable" + }, "S3InputDefinition": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-input.html#cfn-databrew-dataset-input-s3inputdefinition", "Required": false, @@ -16932,6 +18242,17 @@ } } }, + "AWS::DataBrew::Dataset.Metadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-metadata.html", + "Properties": { + "SourceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-metadata.html#cfn-databrew-dataset-metadata-sourcearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Dataset.PathOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-pathoptions.html", "Properties": { @@ -16990,6 +18311,18 @@ } } }, + "AWS::DataBrew::Job.AllowedStatistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-allowedstatistics.html", + "Properties": { + "Statistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-allowedstatistics.html#cfn-databrew-job-allowedstatistics-statistics", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Job.ColumnSelector": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-columnselector.html", "Properties": { @@ -17117,6 +18450,24 @@ } } }, + "AWS::DataBrew::Job.EntityDetectorConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html", + "Properties": { + "AllowedStatistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html#cfn-databrew-job-entitydetectorconfiguration-allowedstatistics", + "Required": false, + "Type": "AllowedStatistics", + "UpdateType": "Mutable" + }, + "EntityTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html#cfn-databrew-job-entitydetectorconfiguration-entitytypes", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Job.JobSample": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-jobsample.html", "Properties": { @@ -17224,6 +18575,12 @@ "Type": "StatisticsConfiguration", "UpdateType": "Mutable" }, + "EntityDetectorConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-profileconfiguration.html#cfn-databrew-job-profileconfiguration-entitydetectorconfiguration", + "Required": false, + "Type": "EntityDetectorConfiguration", + "UpdateType": "Mutable" + }, "ProfileColumns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-profileconfiguration.html#cfn-databrew-job-profileconfiguration-profilecolumns", "ItemType": "ColumnSelector", @@ -17314,6 +18671,23 @@ } } }, + "AWS::DataBrew::Job.ValidationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html", + "Properties": { + "RulesetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html#cfn-databrew-job-validationconfiguration-rulesetarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ValidationMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html#cfn-databrew-job-validationconfiguration-validationmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Project.Sample": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-project-sample.html", "Properties": { @@ -18070,6 +19444,106 @@ } } }, + "AWS::DataBrew::Ruleset.ColumnSelector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html#cfn-databrew-ruleset-columnselector-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Regex": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html#cfn-databrew-ruleset-columnselector-regex", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.Rule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html", + "Properties": { + "CheckExpression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-checkexpression", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ColumnSelectors": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-columnselectors", + "ItemType": "ColumnSelector", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Disabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-disabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SubstitutionMap": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-substitutionmap", + "ItemType": "SubstitutionValue", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Threshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-threshold", + "Required": false, + "Type": "Threshold", + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.SubstitutionValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html#cfn-databrew-ruleset-substitutionvalue-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ValueReference": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html#cfn-databrew-ruleset-substitutionvalue-valuereference", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.Threshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Unit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-unit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-value", + "PrimitiveType": "Double", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::DataPipeline::Pipeline.Field": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datapipeline-pipeline-pipelineobjects-fields.html", "Properties": { @@ -18206,6 +19680,40 @@ } } }, + "AWS::DataSync::LocationHDFS.NameNode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-namenode.html", + "Properties": { + "Hostname": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-namenode.html#cfn-datasync-locationhdfs-namenode-hostname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-namenode.html#cfn-datasync-locationhdfs-namenode-port", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::DataSync::LocationHDFS.QopConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-qopconfiguration.html", + "Properties": { + "DataTransferProtection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-qopconfiguration.html#cfn-datasync-locationhdfs-qopconfiguration-datatransferprotection", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RpcProtection": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationhdfs-qopconfiguration.html#cfn-datasync-locationhdfs-qopconfiguration-rpcprotection", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DataSync::LocationNFS.MountOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datasync-locationnfs-mountoptions.html", "Properties": { @@ -19032,6 +20540,72 @@ } } }, + "AWS::EC2::CapacityReservationFleet.InstanceTypeSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html", + "Properties": { + "AvailabilityZone": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-availabilityzone", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "AvailabilityZoneId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-availabilityzoneid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "EbsOptimized": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-ebsoptimized", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "InstancePlatform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-instanceplatform", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "InstanceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-instancetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-priority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Weight": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-instancetypespecification.html#cfn-ec2-capacityreservationfleet-instancetypespecification-weight", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::CapacityReservationFleet.TagSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-tagspecification.html", + "Properties": { + "ResourceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-tagspecification.html#cfn-ec2-capacityreservationfleet-tagspecification-resourcetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-capacityreservationfleet-tagspecification.html#cfn-ec2-capacityreservationfleet-tagspecification-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::ClientVpnEndpoint.CertificateAuthenticationRequest": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-clientvpnendpoint-certificateauthenticationrequest.html", "Properties": { @@ -19158,6 +20732,74 @@ } } }, + "AWS::EC2::EC2Fleet.AcceleratorCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratorcountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratorcountrequest.html#cfn-ec2-ec2fleet-acceleratorcountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratorcountrequest.html#cfn-ec2-ec2fleet-acceleratorcountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.AcceleratorTotalMemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratortotalmemorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratortotalmemorymibrequest.html#cfn-ec2-ec2fleet-acceleratortotalmemorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-acceleratortotalmemorymibrequest.html#cfn-ec2-ec2fleet-acceleratortotalmemorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.BaselineEbsBandwidthMbpsRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-baselineebsbandwidthmbpsrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-baselineebsbandwidthmbpsrequest.html#cfn-ec2-ec2fleet-baselineebsbandwidthmbpsrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-baselineebsbandwidthmbpsrequest.html#cfn-ec2-ec2fleet-baselineebsbandwidthmbpsrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.CapacityRebalance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-capacityrebalance.html", + "Properties": { + "ReplacementStrategy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-capacityrebalance.html#cfn-ec2-ec2fleet-capacityrebalance-replacementstrategy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "TerminationDelay": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-capacityrebalance.html#cfn-ec2-ec2fleet-capacityrebalance-terminationdelay", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::EC2Fleet.CapacityReservationOptionsRequest": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-capacityreservationoptionsrequest.html", "Properties": { @@ -19197,6 +20839,12 @@ "Required": false, "UpdateType": "Immutable" }, + "InstanceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-fleetlaunchtemplateoverridesrequest.html#cfn-ec2-ec2fleet-fleetlaunchtemplateoverridesrequest-instancerequirements", + "Required": false, + "Type": "InstanceRequirementsRequest", + "UpdateType": "Immutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-fleetlaunchtemplateoverridesrequest.html#cfn-ec2-ec2fleet-fleetlaunchtemplateoverridesrequest-instancetype", "PrimitiveType": "String", @@ -19258,6 +20906,213 @@ } } }, + "AWS::EC2::EC2Fleet.InstanceRequirementsRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html", + "Properties": { + "AcceleratorCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-acceleratorcount", + "Required": false, + "Type": "AcceleratorCountRequest", + "UpdateType": "Immutable" + }, + "AcceleratorManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-acceleratormanufacturers", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AcceleratorNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-acceleratornames", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AcceleratorTotalMemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-acceleratortotalmemorymib", + "Required": false, + "Type": "AcceleratorTotalMemoryMiBRequest", + "UpdateType": "Immutable" + }, + "AcceleratorTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-acceleratortypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "BareMetal": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-baremetal", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "BaselineEbsBandwidthMbps": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-baselineebsbandwidthmbps", + "Required": false, + "Type": "BaselineEbsBandwidthMbpsRequest", + "UpdateType": "Immutable" + }, + "BurstablePerformance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-burstableperformance", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "CpuManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-cpumanufacturers", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "ExcludedInstanceTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-excludedinstancetypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "InstanceGenerations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-instancegenerations", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "LocalStorage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-localstorage", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "LocalStorageTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-localstoragetypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MemoryGiBPerVCpu": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-memorygibpervcpu", + "Required": false, + "Type": "MemoryGiBPerVCpuRequest", + "UpdateType": "Immutable" + }, + "MemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-memorymib", + "Required": false, + "Type": "MemoryMiBRequest", + "UpdateType": "Immutable" + }, + "NetworkInterfaceCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-networkinterfacecount", + "Required": false, + "Type": "NetworkInterfaceCountRequest", + "UpdateType": "Immutable" + }, + "OnDemandMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-ondemandmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "RequireHibernateSupport": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-requirehibernatesupport", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "SpotMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-spotmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "TotalLocalStorageGB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-totallocalstoragegb", + "Required": false, + "Type": "TotalLocalStorageGBRequest", + "UpdateType": "Immutable" + }, + "VCpuCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-instancerequirementsrequest.html#cfn-ec2-ec2fleet-instancerequirementsrequest-vcpucount", + "Required": false, + "Type": "VCpuCountRangeRequest", + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.MaintenanceStrategies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-maintenancestrategies.html", + "Properties": { + "CapacityRebalance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-maintenancestrategies.html#cfn-ec2-ec2fleet-maintenancestrategies-capacityrebalance", + "Required": false, + "Type": "CapacityRebalance", + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.MemoryGiBPerVCpuRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorygibpervcpurequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorygibpervcpurequest.html#cfn-ec2-ec2fleet-memorygibpervcpurequest-max", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorygibpervcpurequest.html#cfn-ec2-ec2fleet-memorygibpervcpurequest-min", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.MemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorymibrequest.html#cfn-ec2-ec2fleet-memorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-memorymibrequest.html#cfn-ec2-ec2fleet-memorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.NetworkInterfaceCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-networkinterfacecountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-networkinterfacecountrequest.html#cfn-ec2-ec2fleet-networkinterfacecountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-networkinterfacecountrequest.html#cfn-ec2-ec2fleet-networkinterfacecountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::EC2Fleet.OnDemandOptionsRequest": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-ondemandoptionsrequest.html", "Properties": { @@ -19373,6 +21228,12 @@ "Required": false, "UpdateType": "Immutable" }, + "MaintenanceStrategies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-spotoptionsrequest.html#cfn-ec2-ec2fleet-spotoptionsrequest-maintenancestrategies", + "Required": false, + "Type": "MaintenanceStrategies", + "UpdateType": "Immutable" + }, "MaxTotalPrice": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-spotoptionsrequest.html#cfn-ec2-ec2fleet-spotoptionsrequest-maxtotalprice", "PrimitiveType": "String", @@ -19439,6 +21300,12 @@ "Required": false, "UpdateType": "Mutable" }, + "TargetCapacityUnitType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-targetcapacityspecificationrequest.html#cfn-ec2-ec2fleet-targetcapacityspecificationrequest-targetcapacityunittype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "TotalTargetCapacity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-targetcapacityspecificationrequest.html#cfn-ec2-ec2fleet-targetcapacityspecificationrequest-totaltargetcapacity", "PrimitiveType": "Integer", @@ -19447,6 +21314,40 @@ } } }, + "AWS::EC2::EC2Fleet.TotalLocalStorageGBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-totallocalstoragegbrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-totallocalstoragegbrequest.html#cfn-ec2-ec2fleet-totallocalstoragegbrequest-max", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-totallocalstoragegbrequest.html#cfn-ec2-ec2fleet-totallocalstoragegbrequest-min", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::EC2Fleet.VCpuCountRangeRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-vcpucountrangerequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-vcpucountrangerequest.html#cfn-ec2-ec2fleet-vcpucountrangerequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ec2fleet-vcpucountrangerequest.html#cfn-ec2-ec2fleet-vcpucountrangerequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::Instance.AssociationParameter": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance-ssmassociations-associationparameters.html", "Properties": { @@ -21478,6 +23379,57 @@ } } }, + "AWS::EC2::SpotFleet.AcceleratorCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratorcountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratorcountrequest.html#cfn-ec2-spotfleet-acceleratorcountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratorcountrequest.html#cfn-ec2-spotfleet-acceleratorcountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::SpotFleet.AcceleratorTotalMemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratortotalmemorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratortotalmemorymibrequest.html#cfn-ec2-spotfleet-acceleratortotalmemorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-acceleratortotalmemorymibrequest.html#cfn-ec2-spotfleet-acceleratortotalmemorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::SpotFleet.BaselineEbsBandwidthMbpsRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-baselineebsbandwidthmbpsrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-baselineebsbandwidthmbpsrequest.html#cfn-ec2-spotfleet-baselineebsbandwidthmbpsrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-baselineebsbandwidthmbpsrequest.html#cfn-ec2-spotfleet-baselineebsbandwidthmbpsrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::SpotFleet.BlockDeviceMapping": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-blockdevicemapping.html", "Properties": { @@ -21705,6 +23657,151 @@ } } }, + "AWS::EC2::SpotFleet.InstanceRequirementsRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html", + "Properties": { + "AcceleratorCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-acceleratorcount", + "Required": false, + "Type": "AcceleratorCountRequest", + "UpdateType": "Immutable" + }, + "AcceleratorManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-acceleratormanufacturers", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AcceleratorNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-acceleratornames", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AcceleratorTotalMemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-acceleratortotalmemorymib", + "Required": false, + "Type": "AcceleratorTotalMemoryMiBRequest", + "UpdateType": "Immutable" + }, + "AcceleratorTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-acceleratortypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "BareMetal": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-baremetal", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "BaselineEbsBandwidthMbps": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-baselineebsbandwidthmbps", + "Required": false, + "Type": "BaselineEbsBandwidthMbpsRequest", + "UpdateType": "Immutable" + }, + "BurstablePerformance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-burstableperformance", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "CpuManufacturers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-cpumanufacturers", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "ExcludedInstanceTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-excludedinstancetypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "InstanceGenerations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-instancegenerations", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "LocalStorage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-localstorage", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "LocalStorageTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-localstoragetypes", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MemoryGiBPerVCpu": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-memorygibpervcpu", + "Required": false, + "Type": "MemoryGiBPerVCpuRequest", + "UpdateType": "Immutable" + }, + "MemoryMiB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-memorymib", + "Required": false, + "Type": "MemoryMiBRequest", + "UpdateType": "Immutable" + }, + "NetworkInterfaceCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-networkinterfacecount", + "Required": false, + "Type": "NetworkInterfaceCountRequest", + "UpdateType": "Immutable" + }, + "OnDemandMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-ondemandmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "RequireHibernateSupport": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-requirehibernatesupport", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "SpotMaxPricePercentageOverLowestPrice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-spotmaxpricepercentageoverlowestprice", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "TotalLocalStorageGB": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-totallocalstoragegb", + "Required": false, + "Type": "TotalLocalStorageGBRequest", + "UpdateType": "Immutable" + }, + "VCpuCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-instancerequirementsrequest.html#cfn-ec2-spotfleet-instancerequirementsrequest-vcpucount", + "Required": false, + "Type": "VCpuCountRangeRequest", + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::SpotFleet.LaunchTemplateConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateconfig.html", "Properties": { @@ -21733,6 +23830,12 @@ "Required": false, "UpdateType": "Immutable" }, + "InstanceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-instancerequirements", + "Required": false, + "Type": "InstanceRequirementsRequest", + "UpdateType": "Immutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-launchtemplateoverrides.html#cfn-ec2-spotfleet-launchtemplateoverrides-instancetype", "PrimitiveType": "String", @@ -21776,6 +23879,57 @@ } } }, + "AWS::EC2::SpotFleet.MemoryGiBPerVCpuRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorygibpervcpurequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorygibpervcpurequest.html#cfn-ec2-spotfleet-memorygibpervcpurequest-max", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorygibpervcpurequest.html#cfn-ec2-spotfleet-memorygibpervcpurequest-min", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::SpotFleet.MemoryMiBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorymibrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorymibrequest.html#cfn-ec2-spotfleet-memorymibrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-memorymibrequest.html#cfn-ec2-spotfleet-memorymibrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::SpotFleet.NetworkInterfaceCountRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-networkinterfacecountrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-networkinterfacecountrequest.html#cfn-ec2-spotfleet-networkinterfacecountrequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-networkinterfacecountrequest.html#cfn-ec2-spotfleet-networkinterfacecountrequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::SpotFleet.PrivateIpAddressSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-privateipaddressspecification.html", "Properties": { @@ -21801,6 +23955,12 @@ "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" + }, + "TerminationDelay": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotcapacityrebalance.html#cfn-ec2-spotfleet-spotcapacityrebalance-terminationdelay", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" } } }, @@ -21833,10 +23993,16 @@ "Required": true, "UpdateType": "Immutable" }, + "InstanceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancerequirements", + "Required": false, + "Type": "InstanceRequirementsRequest", + "UpdateType": "Immutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetlaunchspecification.html#cfn-ec2-spotfleet-spotfleetlaunchspecification-instancetype", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "KernelId": { @@ -22039,6 +24205,12 @@ "Required": true, "UpdateType": "Mutable" }, + "TargetCapacityUnitType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata.html#cfn-ec2-spotfleet-spotfleetrequestconfigdata-targetcapacityunittype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "TerminateInstancesWithExpiration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata.html#cfn-ec2-spotfleet-spotfleetrequestconfigdata-terminateinstanceswithexpiration", "PrimitiveType": "Boolean", @@ -22142,6 +24314,40 @@ } } }, + "AWS::EC2::SpotFleet.TotalLocalStorageGBRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-totallocalstoragegbrequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-totallocalstoragegbrequest.html#cfn-ec2-spotfleet-totallocalstoragegbrequest-max", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-totallocalstoragegbrequest.html#cfn-ec2-spotfleet-totallocalstoragegbrequest-min", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EC2::SpotFleet.VCpuCountRangeRequest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-vcpucountrangerequest.html", + "Properties": { + "Max": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-vcpucountrangerequest.html#cfn-ec2-spotfleet-vcpucountrangerequest-max", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Min": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-vcpucountrangerequest.html#cfn-ec2-spotfleet-vcpucountrangerequest-min", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::EC2::TrafficMirrorFilterRule.TrafficMirrorPortRange": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-trafficmirrorfilterrule-trafficmirrorportrange.html", "Properties": { @@ -22170,6 +24376,17 @@ } } }, + "AWS::EC2::TransitGatewayPeeringAttachment.TransitGatewayPeeringAttachmentOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-transitgatewaypeeringattachment-transitgatewaypeeringattachmentoptions.html", + "Properties": { + "DynamicRouting": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-transitgatewaypeeringattachment-transitgatewaypeeringattachmentoptions.html#cfn-ec2-transitgatewaypeeringattachment-transitgatewaypeeringattachmentoptions-dynamicrouting", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::VPNConnection.VpnTunnelOptionsSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-vpnconnection-vpntunneloptionsspecification.html", "Properties": { @@ -23419,6 +25636,23 @@ } } }, + "AWS::ECS::TaskDefinition.RuntimePlatform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-runtimeplatform.html", + "Properties": { + "CpuArchitecture": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-runtimeplatform.html#cfn-ecs-taskdefinition-runtimeplatform-cpuarchitecture", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "OperatingSystemFamily": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-runtimeplatform.html#cfn-ecs-taskdefinition-runtimeplatform-operatingsystemfamily", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::ECS::TaskDefinition.Secret": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-secret.html", "Properties": { @@ -23800,21 +26034,33 @@ } } }, + "AWS::EKS::Cluster.ClusterLogging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-clusterlogging.html", + "Properties": { + "EnabledTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-clusterlogging.html#cfn-eks-cluster-clusterlogging-enabledtypes", + "ItemType": "LoggingTypeConfig", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::EKS::Cluster.EncryptionConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-encryptionconfig.html", "Properties": { "Provider": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-encryptionconfig.html#cfn-eks-cluster-encryptionconfig-provider", + "PrimitiveType": "Json", "Required": false, - "Type": "Provider", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "Resources": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-encryptionconfig.html#cfn-eks-cluster-encryptionconfig-resources", "PrimitiveItemType": "String", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -23825,15 +26071,26 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-kubernetesnetworkconfig.html#cfn-eks-cluster-kubernetesnetworkconfig-serviceipv4cidr", "PrimitiveType": "String", "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::EKS::Cluster.Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-logging.html", + "Properties": { + "ClusterLogging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-logging.html#cfn-eks-cluster-logging-clusterlogging", + "Required": false, + "Type": "ClusterLogging", "UpdateType": "Mutable" } } }, - "AWS::EKS::Cluster.Provider": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-provider.html", + "AWS::EKS::Cluster.LoggingTypeConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-loggingtypeconfig.html", "Properties": { - "KeyArn": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-provider.html#cfn-eks-cluster-provider-keyarn", + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-loggingtypeconfig.html#cfn-eks-cluster-loggingtypeconfig-type", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" @@ -23843,19 +26100,38 @@ "AWS::EKS::Cluster.ResourcesVpcConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html", "Properties": { + "EndpointPrivateAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html#cfn-eks-cluster-resourcesvpcconfig-endpointprivateaccess", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "EndpointPublicAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html#cfn-eks-cluster-resourcesvpcconfig-endpointpublicaccess", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "PublicAccessCidrs": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html#cfn-eks-cluster-resourcesvpcconfig-publicaccesscidrs", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "SecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html#cfn-eks-cluster-resourcesvpcconfig-securitygroupids", "PrimitiveItemType": "String", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "SubnetIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-resourcesvpcconfig.html#cfn-eks-cluster-resourcesvpcconfig-subnetids", "PrimitiveItemType": "String", "Required": true, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -27992,6 +30268,23 @@ } } }, + "AWS::FSx::FileSystem.DiskIopsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html", + "Properties": { + "Iops": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration-iops", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Mode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration-mode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::FSx::FileSystem.LustreConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html", "Properties": { @@ -28069,6 +30362,72 @@ } } }, + "AWS::FSx::FileSystem.OntapConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html", + "Properties": { + "AutomaticBackupRetentionDays": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-automaticbackupretentiondays", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "DailyAutomaticBackupStartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-dailyautomaticbackupstarttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DeploymentType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-deploymenttype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DiskIopsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration", + "Required": false, + "Type": "DiskIopsConfiguration", + "UpdateType": "Mutable" + }, + "EndpointIpAddressRange": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-endpointipaddressrange", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "FsxAdminPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-fsxadminpassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PreferredSubnetId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-preferredsubnetid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "RouteTableIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-routetableids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "ThroughputCapacity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-throughputcapacity", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "WeeklyMaintenanceStartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-weeklymaintenancestarttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::FSx::FileSystem.SelfManagedActiveDirectoryConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-windowsconfiguration-selfmanagedactivedirectoryconfiguration.html", "Properties": { @@ -28224,6 +30583,29 @@ } } }, + "AWS::FinSpace::Environment.SuperuserParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html", + "Properties": { + "EmailAddress": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-emailaddress", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "FirstName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-firstname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "LastName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-lastname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::FraudDetector::Detector.EntityType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-frauddetector-detector-entitytype.html", "Properties": { @@ -32696,6 +35078,12 @@ "Required": false, "UpdateType": "Immutable" }, + "Throughput": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-containerrecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-containerrecipe-ebsinstanceblockdevicespecification-throughput", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, "VolumeSize": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-containerrecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-containerrecipe-ebsinstanceblockdevicespecification-volumesize", "PrimitiveType": "Integer", @@ -32971,6 +35359,12 @@ "Required": false, "UpdateType": "Immutable" }, + "Throughput": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-throughput", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, "VolumeSize": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-volumesize", "PrimitiveType": "Integer", @@ -33025,6 +35419,23 @@ } } }, + "AWS::ImageBuilder::InfrastructureConfiguration.InstanceMetadataOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-instancemetadataoptions.html", + "Properties": { + "HttpPutResponseHopLimit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-instancemetadataoptions.html#cfn-imagebuilder-infrastructureconfiguration-instancemetadataoptions-httpputresponsehoplimit", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "HttpTokens": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-instancemetadataoptions.html#cfn-imagebuilder-infrastructureconfiguration-instancemetadataoptions-httptokens", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ImageBuilder::InfrastructureConfiguration.Logging": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-logging.html", "Properties": { @@ -36788,6 +39199,52 @@ } } }, + "AWS::IoTWireless::FuotaTask.LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html", + "Properties": { + "RfRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html#cfn-iotwireless-fuotatask-lorawan-rfregion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "StartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html#cfn-iotwireless-fuotatask-lorawan-starttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoTWireless::MulticastGroup.LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html", + "Properties": { + "DlClass": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-dlclass", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "NumberOfDevicesInGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-numberofdevicesingroup", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "NumberOfDevicesRequested": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-numberofdevicesrequested", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "RfRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-rfregion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::IoTWireless::PartnerAccount.SidewalkAccountInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-partneraccount-sidewalkaccountinfo.html", "Properties": { @@ -41896,6 +44353,59 @@ } } }, + "AWS::Lightsail::Database.RelationalDatabaseParameter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html", + "Properties": { + "AllowedValues": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-allowedvalues", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ApplyMethod": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-applymethod", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ApplyType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-applytype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DataType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-datatype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IsModifiable": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-ismodifiable", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ParameterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-parametername", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ParameterValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-database-relationaldatabaseparameter.html#cfn-lightsail-database-relationaldatabaseparameter-parametervalue", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Lightsail::Disk.AddOn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-disk-addon.html", "Properties": { @@ -42070,6 +44580,7 @@ "MonthlyTransfer": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lightsail-instance-networking.html#cfn-lightsail-instance-networking-monthlytransfer", "Required": false, + "Type": "MonthlyTransfer", "UpdateType": "Mutable" }, "Ports": { @@ -42715,6 +45226,12 @@ "Type": "List", "UpdateType": "Immutable" }, + "ConnectivityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-brokernodegroupinfo.html#cfn-msk-cluster-brokernodegroupinfo-connectivityinfo", + "Required": false, + "Type": "ConnectivityInfo", + "UpdateType": "Mutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-brokernodegroupinfo.html#cfn-msk-cluster-brokernodegroupinfo-instancetype", "PrimitiveType": "String", @@ -42793,6 +45310,17 @@ } } }, + "AWS::MSK::Cluster.ConnectivityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-connectivityinfo.html", + "Properties": { + "PublicAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-connectivityinfo.html#cfn-msk-cluster-connectivityinfo-publicaccess", + "Required": false, + "Type": "PublicAccess", + "UpdateType": "Mutable" + } + } + }, "AWS::MSK::Cluster.EBSStorageInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-ebsstorageinfo.html", "Properties": { @@ -42938,6 +45466,17 @@ } } }, + "AWS::MSK::Cluster.PublicAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-publicaccess.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-publicaccess.html#cfn-msk-cluster-publicaccess-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::MSK::Cluster.S3": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-s3.html", "Properties": { @@ -43106,9 +45645,6 @@ } } }, - "AWS::MWAA::Environment.TagMap": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-mwaa-environment-tagmap.html" - }, "AWS::Macie::FindingsFilter.Criterion": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-macie-findingsfilter-criterion.html" }, @@ -49286,6 +51822,20 @@ "AWS::NetworkFirewall::FirewallPolicy.FirewallPolicy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-firewallpolicy.html", "Properties": { + "StatefulDefaultActions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-firewallpolicy.html#cfn-networkfirewall-firewallpolicy-firewallpolicy-statefuldefaultactions", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "StatefulEngineOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-firewallpolicy.html#cfn-networkfirewall-firewallpolicy-firewallpolicy-statefulengineoptions", + "Required": false, + "Type": "StatefulEngineOptions", + "UpdateType": "Mutable" + }, "StatefulRuleGroupReferences": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-firewallpolicy.html#cfn-networkfirewall-firewallpolicy-firewallpolicy-statefulrulegroupreferences", "DuplicatesAllowed": false, @@ -49341,9 +51891,26 @@ } } }, + "AWS::NetworkFirewall::FirewallPolicy.StatefulEngineOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulengineoptions.html", + "Properties": { + "RuleOrder": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulengineoptions.html#cfn-networkfirewall-firewallpolicy-statefulengineoptions-ruleorder", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::NetworkFirewall::FirewallPolicy.StatefulRuleGroupReference": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulrulegroupreference.html", "Properties": { + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulrulegroupreference.html#cfn-networkfirewall-firewallpolicy-statefulrulegroupreference-priority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "ResourceArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulrulegroupreference.html#cfn-networkfirewall-firewallpolicy-statefulrulegroupreference-resourcearn", "PrimitiveType": "String", @@ -49638,6 +52205,12 @@ "Required": true, "Type": "RulesSource", "UpdateType": "Mutable" + }, + "StatefulRuleOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulegroup.html#cfn-networkfirewall-rulegroup-rulegroup-statefulruleoptions", + "Required": false, + "Type": "StatefulRuleOptions", + "UpdateType": "Mutable" } } }, @@ -49762,6 +52335,17 @@ } } }, + "AWS::NetworkFirewall::RuleGroup.StatefulRuleOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statefulruleoptions.html", + "Properties": { + "RuleOrder": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statefulruleoptions.html#cfn-networkfirewall-rulegroup-statefulruleoptions-ruleorder", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::NetworkFirewall::RuleGroup.StatelessRule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statelessrule.html", "Properties": { @@ -50954,6 +53538,28 @@ } } }, + "AWS::Panorama::ApplicationInstance.ManifestOverridesPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-panorama-applicationinstance-manifestoverridespayload.html", + "Properties": { + "PayloadData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-panorama-applicationinstance-manifestoverridespayload.html#cfn-panorama-applicationinstance-manifestoverridespayload-payloaddata", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Panorama::ApplicationInstance.ManifestPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-panorama-applicationinstance-manifestpayload.html", + "Properties": { + "PayloadData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-panorama-applicationinstance-manifestpayload.html#cfn-panorama-applicationinstance-manifestpayload-payloaddata", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::Pinpoint::ApplicationSettings.CampaignHook": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-applicationsettings-campaignhook.html", "Properties": { @@ -51110,6 +53716,30 @@ } } }, + "AWS::Pinpoint::Campaign.CampaignInAppMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-campaigninappmessage.html", + "Properties": { + "Content": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-campaigninappmessage.html#cfn-pinpoint-campaign-campaigninappmessage-content", + "ItemType": "InAppMessageContent", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "CustomConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-campaigninappmessage.html#cfn-pinpoint-campaign-campaigninappmessage-customconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "Layout": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-campaigninappmessage.html#cfn-pinpoint-campaign-campaigninappmessage-layout", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::Campaign.CampaignSmsMessage": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-campaignsmsmessage.html", "Properties": { @@ -51151,6 +53781,47 @@ } } }, + "AWS::Pinpoint::Campaign.DefaultButtonConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html", + "Properties": { + "BackgroundColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-backgroundcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "BorderRadius": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-borderradius", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "ButtonAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-buttonaction", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Link": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-link", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Text": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-text", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-defaultbuttonconfiguration.html#cfn-pinpoint-campaign-defaultbuttonconfiguration-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::Campaign.EventDimensions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-eventdimensions.html", "Properties": { @@ -51174,6 +53845,122 @@ } } }, + "AWS::Pinpoint::Campaign.InAppMessageBodyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebodyconfig.html", + "Properties": { + "Alignment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebodyconfig.html#cfn-pinpoint-campaign-inappmessagebodyconfig-alignment", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Body": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebodyconfig.html#cfn-pinpoint-campaign-inappmessagebodyconfig-body", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebodyconfig.html#cfn-pinpoint-campaign-inappmessagebodyconfig-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::Campaign.InAppMessageButton": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebutton.html", + "Properties": { + "Android": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebutton.html#cfn-pinpoint-campaign-inappmessagebutton-android", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + }, + "DefaultConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebutton.html#cfn-pinpoint-campaign-inappmessagebutton-defaultconfig", + "Required": false, + "Type": "DefaultButtonConfiguration", + "UpdateType": "Mutable" + }, + "IOS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebutton.html#cfn-pinpoint-campaign-inappmessagebutton-ios", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + }, + "Web": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagebutton.html#cfn-pinpoint-campaign-inappmessagebutton-web", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::Campaign.InAppMessageContent": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html", + "Properties": { + "BackgroundColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-backgroundcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "BodyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-bodyconfig", + "Required": false, + "Type": "InAppMessageBodyConfig", + "UpdateType": "Mutable" + }, + "HeaderConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-headerconfig", + "Required": false, + "Type": "InAppMessageHeaderConfig", + "UpdateType": "Mutable" + }, + "ImageUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-imageurl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PrimaryBtn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-primarybtn", + "Required": false, + "Type": "InAppMessageButton", + "UpdateType": "Mutable" + }, + "SecondaryBtn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessagecontent.html#cfn-pinpoint-campaign-inappmessagecontent-secondarybtn", + "Required": false, + "Type": "InAppMessageButton", + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::Campaign.InAppMessageHeaderConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessageheaderconfig.html", + "Properties": { + "Alignment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessageheaderconfig.html#cfn-pinpoint-campaign-inappmessageheaderconfig-alignment", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Header": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessageheaderconfig.html#cfn-pinpoint-campaign-inappmessageheaderconfig-header", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-inappmessageheaderconfig.html#cfn-pinpoint-campaign-inappmessageheaderconfig-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::Campaign.Limits": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-limits.html", "Properties": { @@ -51195,6 +53982,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Session": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-limits.html#cfn-pinpoint-campaign-limits-session", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "Total": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-limits.html#cfn-pinpoint-campaign-limits-total", "PrimitiveType": "Integer", @@ -51319,6 +54112,12 @@ "Type": "Message", "UpdateType": "Mutable" }, + "InAppMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-messageconfiguration.html#cfn-pinpoint-campaign-messageconfiguration-inappmessage", + "Required": false, + "Type": "CampaignInAppMessage", + "UpdateType": "Mutable" + }, "SMSMessage": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-messageconfiguration.html#cfn-pinpoint-campaign-messageconfiguration-smsmessage", "Required": false, @@ -51344,6 +54143,23 @@ } } }, + "AWS::Pinpoint::Campaign.OverrideButtonConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-overridebuttonconfiguration.html", + "Properties": { + "ButtonAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-overridebuttonconfiguration.html#cfn-pinpoint-campaign-overridebuttonconfiguration-buttonaction", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Link": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-overridebuttonconfiguration.html#cfn-pinpoint-campaign-overridebuttonconfiguration-link", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::Campaign.QuietTime": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-campaign-schedule-quiettime.html", "Properties": { @@ -51461,6 +54277,180 @@ } } }, + "AWS::Pinpoint::InAppTemplate.BodyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-bodyconfig.html", + "Properties": { + "Alignment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-bodyconfig.html#cfn-pinpoint-inapptemplate-bodyconfig-alignment", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Body": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-bodyconfig.html#cfn-pinpoint-inapptemplate-bodyconfig-body", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-bodyconfig.html#cfn-pinpoint-inapptemplate-bodyconfig-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::InAppTemplate.ButtonConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-buttonconfig.html", + "Properties": { + "Android": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-buttonconfig.html#cfn-pinpoint-inapptemplate-buttonconfig-android", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + }, + "DefaultConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-buttonconfig.html#cfn-pinpoint-inapptemplate-buttonconfig-defaultconfig", + "Required": false, + "Type": "DefaultButtonConfiguration", + "UpdateType": "Mutable" + }, + "IOS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-buttonconfig.html#cfn-pinpoint-inapptemplate-buttonconfig-ios", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + }, + "Web": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-buttonconfig.html#cfn-pinpoint-inapptemplate-buttonconfig-web", + "Required": false, + "Type": "OverrideButtonConfiguration", + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::InAppTemplate.DefaultButtonConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html", + "Properties": { + "BackgroundColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-backgroundcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "BorderRadius": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-borderradius", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "ButtonAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-buttonaction", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Link": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-link", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Text": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-text", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-defaultbuttonconfiguration.html#cfn-pinpoint-inapptemplate-defaultbuttonconfiguration-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::InAppTemplate.HeaderConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-headerconfig.html", + "Properties": { + "Alignment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-headerconfig.html#cfn-pinpoint-inapptemplate-headerconfig-alignment", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Header": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-headerconfig.html#cfn-pinpoint-inapptemplate-headerconfig-header", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TextColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-headerconfig.html#cfn-pinpoint-inapptemplate-headerconfig-textcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::InAppTemplate.InAppMessageContent": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html", + "Properties": { + "BackgroundColor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-backgroundcolor", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "BodyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-bodyconfig", + "Required": false, + "Type": "BodyConfig", + "UpdateType": "Mutable" + }, + "HeaderConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-headerconfig", + "Required": false, + "Type": "HeaderConfig", + "UpdateType": "Mutable" + }, + "ImageUrl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-imageurl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PrimaryBtn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-primarybtn", + "Required": false, + "Type": "ButtonConfig", + "UpdateType": "Mutable" + }, + "SecondaryBtn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-inappmessagecontent.html#cfn-pinpoint-inapptemplate-inappmessagecontent-secondarybtn", + "Required": false, + "Type": "ButtonConfig", + "UpdateType": "Mutable" + } + } + }, + "AWS::Pinpoint::InAppTemplate.OverrideButtonConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-overridebuttonconfiguration.html", + "Properties": { + "ButtonAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-overridebuttonconfiguration.html#cfn-pinpoint-inapptemplate-overridebuttonconfiguration-buttonaction", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Link": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-inapptemplate-overridebuttonconfiguration.html#cfn-pinpoint-inapptemplate-overridebuttonconfiguration-link", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::PushTemplate.APNSPushNotificationTemplate": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pinpoint-pushtemplate-apnspushnotificationtemplate.html", "Properties": { @@ -53139,6 +56129,17 @@ } } }, + "AWS::QuickSight::DataSource.AmazonOpenSearchParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-quicksight-datasource-amazonopensearchparameters.html", + "Properties": { + "Domain": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-quicksight-datasource-amazonopensearchparameters.html#cfn-quicksight-datasource-amazonopensearchparameters-domain", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::QuickSight::DataSource.AthenaParameters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-quicksight-datasource-athenaparameters.html", "Properties": { @@ -53263,6 +56264,12 @@ "Type": "AmazonElasticsearchParameters", "UpdateType": "Mutable" }, + "AmazonOpenSearchParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-quicksight-datasource-datasourceparameters.html#cfn-quicksight-datasource-datasourceparameters-amazonopensearchparameters", + "Required": false, + "Type": "AmazonOpenSearchParameters", + "UpdateType": "Mutable" + }, "AthenaParameters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-quicksight-datasource-datasourceparameters.html#cfn-quicksight-datasource-datasourceparameters-athenaparameters", "Required": false, @@ -54354,6 +57361,103 @@ } } }, + "AWS::Redshift::EndpointAccess.VpcSecurityGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-endpointaccess-vpcsecuritygroup.html", + "Properties": { + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-endpointaccess-vpcsecuritygroup.html#cfn-redshift-endpointaccess-vpcsecuritygroup-status", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "VpcSecurityGroupId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-endpointaccess-vpcsecuritygroup.html#cfn-redshift-endpointaccess-vpcsecuritygroup-vpcsecuritygroupid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::ScheduledAction.PauseClusterMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-pauseclustermessage.html", + "Properties": { + "ClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-pauseclustermessage.html#cfn-redshift-scheduledaction-pauseclustermessage-clusteridentifier", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::ScheduledAction.ResizeClusterMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html", + "Properties": { + "Classic": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html#cfn-redshift-scheduledaction-resizeclustermessage-classic", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html#cfn-redshift-scheduledaction-resizeclustermessage-clusteridentifier", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ClusterType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html#cfn-redshift-scheduledaction-resizeclustermessage-clustertype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "NodeType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html#cfn-redshift-scheduledaction-resizeclustermessage-nodetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "NumberOfNodes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resizeclustermessage.html#cfn-redshift-scheduledaction-resizeclustermessage-numberofnodes", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::ScheduledAction.ResumeClusterMessage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resumeclustermessage.html", + "Properties": { + "ClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-resumeclustermessage.html#cfn-redshift-scheduledaction-resumeclustermessage-clusteridentifier", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::ScheduledAction.ScheduledActionType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-scheduledactiontype.html", + "Properties": { + "PauseCluster": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-scheduledactiontype.html#cfn-redshift-scheduledaction-scheduledactiontype-pausecluster", + "Required": false, + "Type": "PauseClusterMessage", + "UpdateType": "Mutable" + }, + "ResizeCluster": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-scheduledactiontype.html#cfn-redshift-scheduledaction-scheduledactiontype-resizecluster", + "Required": false, + "Type": "ResizeClusterMessage", + "UpdateType": "Mutable" + }, + "ResumeCluster": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-redshift-scheduledaction-scheduledactiontype.html#cfn-redshift-scheduledaction-scheduledactiontype-resumecluster", + "Required": false, + "Type": "ResumeClusterMessage", + "UpdateType": "Mutable" + } + } + }, "AWS::ResourceGroups::Group.ConfigurationItem": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resourcegroups-group-configurationitem.html", "Properties": { @@ -56636,14 +59740,14 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3objectlambda-accesspoint-transformationconfiguration.html#cfn-s3objectlambda-accesspoint-transformationconfiguration-actions", "DuplicatesAllowed": false, "PrimitiveItemType": "String", - "Required": false, + "Required": true, "Type": "List", "UpdateType": "Mutable" }, "ContentTransformation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3objectlambda-accesspoint-transformationconfiguration.html#cfn-s3objectlambda-accesspoint-transformationconfiguration-contenttransformation", "PrimitiveType": "Json", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -58582,6 +61686,12 @@ "Type": "CapacitySize", "UpdateType": "Mutable" }, + "LinearStepSize": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-endpoint-trafficroutingconfig.html#cfn-sagemaker-endpoint-trafficroutingconfig-linearstepsize", + "Required": false, + "Type": "CapacitySize", + "UpdateType": "Mutable" + }, "Type": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-endpoint-trafficroutingconfig.html#cfn-sagemaker-endpoint-trafficroutingconfig-type", "PrimitiveType": "String", @@ -60422,6 +63532,18 @@ "Required": true, "UpdateType": "Mutable" }, + "SuperuserSecretArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-superusersecretarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SuperuserSecretKmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-superusersecretkmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "VpcSecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-vpcsecuritygroupids", "PrimitiveType": "String", @@ -60918,6 +64040,17 @@ } } }, + "AWS::Synthetics::Canary.ArtifactConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-artifactconfig.html", + "Properties": { + "S3Encryption": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-artifactconfig.html#cfn-synthetics-canary-artifactconfig-s3encryption", + "Required": false, + "Type": "S3Encryption", + "UpdateType": "Mutable" + } + } + }, "AWS::Synthetics::Canary.BaseScreenshot": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-basescreenshot.html", "Properties": { @@ -61001,6 +64134,23 @@ } } }, + "AWS::Synthetics::Canary.S3Encryption": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-s3encryption.html", + "Properties": { + "EncryptionMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-s3encryption.html#cfn-synthetics-canary-s3encryption-encryptionmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "KmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-s3encryption.html#cfn-synthetics-canary-s3encryption-kmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Synthetics::Canary.Schedule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-schedule.html", "Properties": { @@ -61108,6 +64258,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Function": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-function", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "InvocationRole": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-invocationrole", "PrimitiveType": "String", @@ -63150,6 +66306,79 @@ } } }, + "AWS::Wisdom::Assistant.ServerSideEncryptionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-assistant-serversideencryptionconfiguration.html", + "Properties": { + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-assistant-serversideencryptionconfiguration.html#cfn-wisdom-assistant-serversideencryptionconfiguration-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::AssistantAssociation.AssociationData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-assistantassociation-associationdata.html", + "Properties": { + "KnowledgeBaseId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-assistantassociation-associationdata.html#cfn-wisdom-assistantassociation-associationdata-knowledgebaseid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::KnowledgeBase.AppIntegrationsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-appintegrationsconfiguration.html", + "Properties": { + "AppIntegrationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-appintegrationsconfiguration.html#cfn-wisdom-knowledgebase-appintegrationsconfiguration-appintegrationarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ObjectFields": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-appintegrationsconfiguration.html#cfn-wisdom-knowledgebase-appintegrationsconfiguration-objectfields", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::KnowledgeBase.RenderingConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-renderingconfiguration.html", + "Properties": { + "TemplateUri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-renderingconfiguration.html#cfn-wisdom-knowledgebase-renderingconfiguration-templateuri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Wisdom::KnowledgeBase.ServerSideEncryptionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-serversideencryptionconfiguration.html", + "Properties": { + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-serversideencryptionconfiguration.html#cfn-wisdom-knowledgebase-serversideencryptionconfiguration-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::KnowledgeBase.SourceConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-sourceconfiguration.html", + "Properties": { + "AppIntegrations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wisdom-knowledgebase-sourceconfiguration.html#cfn-wisdom-knowledgebase-sourceconfiguration-appintegrations", + "Required": false, + "Type": "AppIntegrationsConfiguration", + "UpdateType": "Immutable" + } + } + }, "AWS::WorkSpaces::ConnectionAlias.ConnectionAliasAssociation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-workspaces-connectionalias-connectionaliasassociation.html", "Properties": { @@ -63503,7 +66732,7 @@ } } }, - "ResourceSpecificationVersion": "43.0.0", + "ResourceSpecificationVersion": "49.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -63703,7 +66932,7 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-aps-rulegroupsnamespace.html#cfn-aps-rulegroupsnamespace-name", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Immutable" }, "Tags": { @@ -64373,6 +67602,11 @@ } }, "AWS::ApiGateway::Authorizer": { + "Attributes": { + "AuthorizerId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html", "Properties": { "AuthType": { @@ -64414,7 +67648,7 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html#cfn-apigateway-authorizer-name", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "ProviderARNs": { @@ -65064,7 +68298,6 @@ }, "Variables": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-variables", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -65165,6 +68398,13 @@ "Required": true, "UpdateType": "Mutable" }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-vpclink.html#cfn-apigateway-vpclink-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "TargetArns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-vpclink.html#cfn-apigateway-vpclink-targetarns", "PrimitiveItemType": "String", @@ -65963,6 +69203,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appconfig-configurationprofile.html#cfn-appconfig-configurationprofile-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "Validators": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appconfig-configurationprofile.html#cfn-appconfig-configurationprofile-validators", "ItemType": "Validators", @@ -66758,6 +70004,167 @@ } } }, + "AWS::AppStream::AppBlock": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DisplayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-displayname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SetupScriptDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-setupscriptdetails", + "Required": true, + "Type": "ScriptDetails", + "UpdateType": "Immutable" + }, + "SourceS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-sources3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppStream::Application": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html", + "Properties": { + "AppBlockArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-appblockarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "AttributesToDelete": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-attributestodelete", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisplayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-displayname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IconS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-icons3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Mutable" + }, + "InstanceFamilies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-instancefamilies", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "LaunchParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-launchparameters", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LaunchPath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-launchpath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Platforms": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-platforms", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "WorkingDirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-workingdirectory", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppStream::ApplicationFleetAssociation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html", + "Properties": { + "ApplicationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html#cfn-appstream-applicationfleetassociation-applicationarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "FleetName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html#cfn-appstream-applicationfleetassociation-fleetname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppStream::DirectoryConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-directoryconfig.html", "Properties": { @@ -66787,7 +70194,7 @@ "Properties": { "ComputeCapacity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-computecapacity", - "Required": true, + "Required": false, "Type": "ComputeCapacity", "UpdateType": "Mutable" }, @@ -66857,6 +70264,12 @@ "Required": true, "UpdateType": "Mutable" }, + "MaxConcurrentSessions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-maxconcurrentsessions", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "MaxUserDurationInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-maxuserdurationinseconds", "PrimitiveType": "Integer", @@ -66869,6 +70282,12 @@ "Required": true, "UpdateType": "Immutable" }, + "Platform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-platform", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "StreamView": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-streamview", "PrimitiveType": "String", @@ -66882,6 +70301,13 @@ "Type": "List", "UpdateType": "Mutable" }, + "UsbDeviceFilterStrings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-usbdevicefilterstrings", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "VpcConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-vpcconfig", "Required": false, @@ -68042,6 +71468,12 @@ "Required": false, "UpdateType": "Mutable" }, + "DesiredCapacityType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-desiredcapacitytype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "HealthCheckGracePeriod": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-healthcheckgraceperiod", "PrimitiveType": "Integer", @@ -68306,52 +71738,52 @@ } }, "AWS::AutoScaling::LifecycleHook": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html", "Properties": { "AutoScalingGroupName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-autoscalinggroupname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-autoscalinggroupname", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" }, "DefaultResult": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-defaultresult", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-defaultresult", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "HeartbeatTimeout": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-heartbeattimeout", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-heartbeattimeout", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "LifecycleHookName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecyclehookname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecyclehookname", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" }, "LifecycleTransition": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-lifecycletransition", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-lifecycletransition", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" }, "NotificationMetadata": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-notificationmetadata", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-notificationmetadata", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "NotificationTargetARN": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-notificationtargetarn", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-notificationtargetarn", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "RoleARN": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-lifecyclehook.html#cfn-as-lifecyclehook-rolearn", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html#cfn-autoscaling-lifecyclehook-rolearn", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" @@ -68771,6 +72203,12 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "UnmanagedvCpus": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-computeenvironment.html#cfn-batch-computeenvironment-unmanagedvcpus", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -68820,6 +72258,12 @@ "Type": "RetryStrategy", "UpdateType": "Mutable" }, + "SchedulingPriority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobdefinition.html#cfn-batch-jobdefinition-schedulingpriority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobdefinition.html#cfn-batch-jobdefinition-tags", "PrimitiveType": "Json", @@ -68862,6 +72306,12 @@ "Required": true, "UpdateType": "Mutable" }, + "SchedulingPolicyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobqueue.html#cfn-batch-jobqueue-schedulingpolicyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "State": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobqueue.html#cfn-batch-jobqueue-state", "PrimitiveType": "String", @@ -68876,6 +72326,35 @@ } } }, + "AWS::Batch::SchedulingPolicy": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html", + "Properties": { + "FairsharePolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy", + "Required": false, + "Type": "FairsharePolicy", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + } + } + }, "AWS::Budgets::Budget": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-budgets-budget.html", "Properties": { @@ -68947,7 +72426,7 @@ "Subscribers": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-budgets-budgetsaction.html#cfn-budgets-budgetsaction-subscribers", "ItemType": "Subscriber", - "Required": false, + "Required": true, "Type": "List", "UpdateType": "Mutable" } @@ -69202,6 +72681,12 @@ "Type": "List", "UpdateType": "Immutable" }, + "DefaultTimeToLive": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cassandra-table.html#cfn-cassandra-table-defaulttimetolive", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "EncryptionSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cassandra-table.html#cfn-cassandra-table-encryptionspecification", "Required": false, @@ -69335,6 +72820,13 @@ "Required": true, "UpdateType": "Immutable" }, + "GuardrailPolicies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-guardrailpolicies", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "IamRoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-iamrolearn", "PrimitiveType": "String", @@ -69365,6 +72857,12 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" + }, + "UserRoleRequired": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-userrolerequired", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -69797,6 +73295,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ManagedExecution": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-stackset.html#cfn-cloudformation-stackset-managedexecution", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "OperationPreferences": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-stackset.html#cfn-cloudformation-stackset-operationpreferences", "Required": false, @@ -70052,12 +73556,6 @@ "Type": "FunctionConfig", "UpdateType": "Mutable" }, - "FunctionMetadata": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html#cfn-cloudfront-function-functionmetadata", - "Required": false, - "Type": "FunctionMetadata", - "UpdateType": "Mutable" - }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html#cfn-cloudfront-function-name", "PrimitiveType": "String", @@ -70161,6 +73659,25 @@ } } }, + "AWS::CloudFront::ResponseHeadersPolicy": { + "Attributes": { + "Id": { + "PrimitiveType": "String" + }, + "LastModifiedTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-responseheaderspolicy.html", + "Properties": { + "ResponseHeadersPolicyConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-responseheaderspolicy.html#cfn-cloudfront-responseheaderspolicy-responseheaderspolicyconfig", + "Required": true, + "Type": "ResponseHeadersPolicyConfig", + "UpdateType": "Mutable" + } + } + }, "AWS::CloudFront::StreamingDistribution": { "Attributes": { "DomainName": { @@ -70455,22 +73972,34 @@ "Type": "List", "UpdateType": "Immutable" }, + "MetricMathAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-metricmathanomalydetector", + "Required": false, + "Type": "MetricMathAnomalyDetector", + "UpdateType": "Immutable" + }, "MetricName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-metricname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Namespace": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-namespace", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Immutable" + }, + "SingleMetricAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector", + "Required": false, + "Type": "SingleMetricAnomalyDetector", "UpdateType": "Immutable" }, "Stat": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-stat", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -71591,12 +75120,24 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html", "Properties": { + "CreatedBy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-createdby", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DetailType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-detailtype", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" }, + "EventTypeId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-eventtypeid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "EventTypeIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-eventtypeids", "DuplicatesAllowed": true, @@ -71629,6 +75170,12 @@ "Required": false, "UpdateType": "Immutable" }, + "TargetAddress": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-targetaddress", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Targets": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html#cfn-codestarnotifications-notificationrule-targets", "DuplicatesAllowed": true, @@ -72711,6 +76258,56 @@ } } }, + "AWS::Connect::HoursOfOperation": { + "Attributes": { + "HoursOfOperationArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html", + "Properties": { + "Config": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-config", + "DuplicatesAllowed": false, + "ItemType": "HoursOfOperationConfig", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "InstanceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-instancearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TimeZone": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html#cfn-connect-hoursofoperation-timezone", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Connect::QuickConnect": { "Attributes": { "QuickConnectArn": { @@ -72753,6 +76350,108 @@ } } }, + "AWS::Connect::User": { + "Attributes": { + "UserArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html", + "Properties": { + "DirectoryUserId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-directoryuserid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "HierarchyGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-hierarchygrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IdentityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-identityinfo", + "Required": false, + "Type": "UserIdentityInfo", + "UpdateType": "Mutable" + }, + "InstanceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-instancearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Password": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-password", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PhoneConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-phoneconfig", + "Required": true, + "Type": "UserPhoneConfig", + "UpdateType": "Mutable" + }, + "RoutingProfileArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-routingprofilearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SecurityProfileArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-securityprofilearns", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Username": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html#cfn-connect-user-username", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::Connect::UserHierarchyGroup": { + "Attributes": { + "UserHierarchyGroupArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-userhierarchygroup.html", + "Properties": { + "InstanceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-userhierarchygroup.html#cfn-connect-userhierarchygroup-instancearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-userhierarchygroup.html#cfn-connect-userhierarchygroup-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ParentGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-userhierarchygroup.html#cfn-connect-userhierarchygroup-parentgrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::CustomerProfiles::Domain": { "Attributes": { "CreatedAt": { @@ -73264,6 +76963,12 @@ "Type": "PostgreSqlSettings", "UpdateType": "Mutable" }, + "RedisSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dms-endpoint.html#cfn-dms-endpoint-redissettings", + "Required": false, + "Type": "RedisSettings", + "UpdateType": "Mutable" + }, "RedshiftSettings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dms-endpoint.html#cfn-dms-endpoint-redshiftsettings", "Required": false, @@ -73722,7 +77427,6 @@ }, "Recipe": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-job.html#cfn-databrew-job-recipe", - "PrimitiveType": "Json", "Required": false, "Type": "Recipe", "UpdateType": "Mutable" @@ -73752,6 +77456,13 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "ValidationConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-job.html#cfn-databrew-job-validationconfigurations", + "ItemType": "ValidationConfiguration", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -73830,6 +77541,44 @@ } } }, + "AWS::DataBrew::Ruleset": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Rules": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-rules", + "ItemType": "Rule", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TargetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-targetarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::DataBrew::Schedule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-schedule.html", "Properties": { @@ -74068,6 +77817,101 @@ } } }, + "AWS::DataSync::LocationHDFS": { + "Attributes": { + "LocationArn": { + "PrimitiveType": "String" + }, + "LocationUri": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html", + "Properties": { + "AgentArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-agentarns", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "AuthenticationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-authenticationtype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "BlockSize": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-blocksize", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "KerberosKeytab": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-kerberoskeytab", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "KerberosKrb5Conf": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-kerberoskrb5conf", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "KerberosPrincipal": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-kerberosprincipal", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "KmsKeyProviderUri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-kmskeyprovideruri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "NameNodes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-namenodes", + "ItemType": "NameNode", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "QopConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-qopconfiguration", + "Required": false, + "Type": "QopConfiguration", + "UpdateType": "Mutable" + }, + "ReplicationFactor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-replicationfactor", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "SimpleUser": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-simpleuser", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Subdirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-subdirectory", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html#cfn-datasync-locationhdfs-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::DataSync::LocationNFS": { "Attributes": { "LocationArn": { @@ -75106,6 +78950,18 @@ "Required": true, "UpdateType": "Immutable" }, + "OutPostArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-outpostarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "PlacementGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-placementgrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "TagSpecifications": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-tagspecifications", "ItemType": "TagSpecification", @@ -75121,6 +78977,74 @@ } } }, + "AWS::EC2::CapacityReservationFleet": { + "Attributes": { + "CapacityReservationFleetId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html", + "Properties": { + "AllocationStrategy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-allocationstrategy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "EndDate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-enddate", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "InstanceMatchCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-instancematchcriteria", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "InstanceTypeSpecifications": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-instancetypespecifications", + "DuplicatesAllowed": false, + "ItemType": "InstanceTypeSpecification", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "NoRemoveEndDate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-noremoveenddate", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "RemoveEndDate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-removeenddate", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "TagSpecifications": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-tagspecifications", + "DuplicatesAllowed": true, + "ItemType": "TagSpecification", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "Tenancy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-tenancy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "TotalTargetCapacity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html#cfn-ec2-capacityreservationfleet-totaltargetcapacity", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::EC2::CarrierGateway": { "Attributes": { "CarrierGatewayId": { @@ -76007,6 +79931,11 @@ } }, "AWS::EC2::InternetGateway": { + "Attributes": { + "InternetGatewayId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html", "Properties": { "Tags": { @@ -76150,10 +80079,15 @@ } }, "AWS::EC2::NetworkAcl": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html", + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html", "Properties": { "Tags": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html#cfn-ec2-networkacl-tags", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html#cfn-ec2-networkacl-tags", "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, @@ -76161,7 +80095,7 @@ "UpdateType": "Mutable" }, "VpcId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl.html#cfn-ec2-networkacl-vpcid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html#cfn-ec2-networkacl-vpcid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" @@ -76169,58 +80103,63 @@ } }, "AWS::EC2::NetworkAclEntry": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html", + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html", "Properties": { "CidrBlock": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-cidrblock", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-cidrblock", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "Egress": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-egress", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-egress", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Immutable" }, "Icmp": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-icmp", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-icmp", "Required": false, "Type": "Icmp", "UpdateType": "Mutable" }, "Ipv6CidrBlock": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-ipv6cidrblock", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-ipv6cidrblock", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "NetworkAclId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-networkaclid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-networkaclid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" }, "PortRange": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-portrange", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-portrange", "Required": false, "Type": "PortRange", "UpdateType": "Mutable" }, "Protocol": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-protocol", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-protocol", "PrimitiveType": "Integer", "Required": true, "UpdateType": "Mutable" }, "RuleAction": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-ruleaction", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-ruleaction", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" }, "RuleNumber": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-acl-entry.html#cfn-ec2-networkaclentry-rulenumber", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkaclentry.html#cfn-ec2-networkaclentry-rulenumber", "PrimitiveType": "Integer", "Required": true, "UpdateType": "Immutable" @@ -76633,10 +80572,15 @@ } }, "AWS::EC2::RouteTable": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html", + "Attributes": { + "RouteTableId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html", "Properties": { "Tags": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html#cfn-ec2-routetable-tags", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html#cfn-ec2-routetable-tags", "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, @@ -76644,7 +80588,7 @@ "UpdateType": "Mutable" }, "VpcId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html#cfn-ec2-routetable-vpcid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html#cfn-ec2-routetable-vpcid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" @@ -77499,6 +81443,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaypeeringattachment.html", "Properties": { + "Options": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaypeeringattachment.html#cfn-ec2-transitgatewaypeeringattachment-options", + "Required": false, + "Type": "TransitGatewayPeeringAttachmentOptions", + "UpdateType": "Mutable" + }, "PeerAccountId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaypeeringattachment.html#cfn-ec2-transitgatewaypeeringattachment-peeraccountid", "PrimitiveType": "String", @@ -77765,16 +81715,21 @@ } }, "AWS::EC2::VPCDHCPOptionsAssociation": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html", + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html", "Properties": { "DhcpOptionsId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "VpcId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" @@ -78693,6 +82648,12 @@ "Type": "List", "UpdateType": "Immutable" }, + "RuntimePlatform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-runtimeplatform", + "Required": false, + "Type": "RuntimePlatform", + "UpdateType": "Immutable" + }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-tags", "ItemType": "Tag", @@ -79039,6 +83000,12 @@ "Type": "KubernetesNetworkConfig", "UpdateType": "Immutable" }, + "Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-logging", + "Required": false, + "Type": "Logging", + "UpdateType": "Mutable" + }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-name", "PrimitiveType": "String", @@ -79049,7 +83016,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-resourcesvpcconfig", "Required": true, "Type": "ResourcesVpcConfig", - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "RoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-rolearn", @@ -79057,6 +83024,14 @@ "Required": true, "UpdateType": "Immutable" }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "Version": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-version", "PrimitiveType": "String", @@ -81713,6 +85688,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "ResourcesCleanUp": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-resourcescleanup", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "SecurityServicePolicyData": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html#cfn-fms-policy-securityservicepolicydata", "PrimitiveType": "Json", @@ -81751,6 +85732,12 @@ "Required": true, "UpdateType": "Immutable" }, + "FileSystemTypeVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-filesystemtypeversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "KmsKeyId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-kmskeyid", "PrimitiveType": "String", @@ -81763,6 +85750,12 @@ "Type": "LustreConfiguration", "UpdateType": "Mutable" }, + "OntapConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-ontapconfiguration", + "Required": false, + "Type": "OntapConfiguration", + "UpdateType": "Mutable" + }, "SecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-securitygroupids", "PrimitiveItemType": "String", @@ -81830,6 +85823,14 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html", "Properties": { + "DataBundles": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-databundles", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, "Description": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-description", "PrimitiveType": "String", @@ -81859,6 +85860,12 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" + }, + "SuperuserParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-superuserparameters", + "Required": false, + "Type": "SuperuserParameters", + "UpdateType": "Immutable" } } }, @@ -83325,7 +87332,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -83387,7 +87394,7 @@ "ItemType": "Tag", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -85586,6 +89593,12 @@ "Required": false, "UpdateType": "Mutable" }, + "InstanceMetadataOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-instancemetadataoptions", + "Required": false, + "Type": "InstanceMetadataOptions", + "UpdateType": "Mutable" + }, "InstanceProfileName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-instanceprofilename", "PrimitiveType": "String", @@ -86183,6 +90196,101 @@ } } }, + "AWS::IoT::JobTemplate": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html", + "Properties": { + "AbortConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-abortconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-description", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Document": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-document", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DocumentSource": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-documentsource", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "JobArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-jobarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "JobExecutionsRolloutConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-jobexecutionsrolloutconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "JobTemplateId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-jobtemplateid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PresignedUrlConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-presignedurlconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "TimeoutConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html#cfn-iot-jobtemplate-timeoutconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::IoT::Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-logging.html", + "Properties": { + "AccountId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-logging.html#cfn-iot-logging-accountid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DefaultLogLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-logging.html#cfn-iot-logging-defaultloglevel", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-logging.html#cfn-iot-logging-rolearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::MitigationAction": { "Attributes": { "MitigationActionArn": { @@ -86314,6 +90422,34 @@ } } }, + "AWS::IoT::ResourceSpecificLogging": { + "Attributes": { + "TargetId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-resourcespecificlogging.html", + "Properties": { + "LogLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-resourcespecificlogging.html#cfn-iot-resourcespecificlogging-loglevel", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TargetName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-resourcespecificlogging.html#cfn-iot-resourcespecificlogging-targetname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "TargetType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-resourcespecificlogging.html#cfn-iot-resourcespecificlogging-targettype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::IoT::ScheduledAudit": { "Attributes": { "ScheduledAuditArn": { @@ -87261,6 +91397,147 @@ } } }, + "AWS::IoTWireless::FuotaTask": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "FuotaTaskStatus": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "LoRaWAN.StartTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html", + "Properties": { + "AssociateMulticastGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-associatemulticastgroup", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AssociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-associatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateMulticastGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-disassociatemulticastgroup", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-disassociatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "FirmwareUpdateImage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-firmwareupdateimage", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "FirmwareUpdateRole": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-firmwareupdaterole", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-lorawan", + "Required": true, + "Type": "LoRaWAN", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoTWireless::MulticastGroup": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "LoRaWAN.NumberOfDevicesInGroup": { + "PrimitiveType": "Integer" + }, + "LoRaWAN.NumberOfDevicesRequested": { + "PrimitiveType": "Integer" + }, + "Status": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html", + "Properties": { + "AssociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-associatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-disassociatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-lorawan", + "Required": true, + "Type": "LoRaWAN", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::IoTWireless::PartnerAccount": { "Attributes": { "Arn": { @@ -88443,6 +92720,12 @@ "Required": false, "UpdateType": "Immutable" }, + "FilterCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-filtercriteria", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "FunctionName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-functionname", "PrimitiveType": "String", @@ -88957,6 +93240,110 @@ } } }, + "AWS::Lightsail::Database": { + "Attributes": { + "DatabaseArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html", + "Properties": { + "AvailabilityZone": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-availabilityzone", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "BackupRetention": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-backupretention", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "CaCertificateIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-cacertificateidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MasterDatabaseName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-masterdatabasename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "MasterUserPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-masteruserpassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MasterUsername": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-masterusername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PreferredBackupWindow": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-preferredbackupwindow", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PreferredMaintenanceWindow": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-preferredmaintenancewindow", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PubliclyAccessible": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-publiclyaccessible", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "RelationalDatabaseBlueprintId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-relationaldatabaseblueprintid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RelationalDatabaseBundleId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-relationaldatabasebundleid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RelationalDatabaseName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-relationaldatabasename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RelationalDatabaseParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-relationaldatabaseparameters", + "DuplicatesAllowed": false, + "ItemType": "RelationalDatabaseParameter", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "RotateMasterUserPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-rotatemasteruserpassword", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html#cfn-lightsail-database-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::Lightsail::Disk": { "Attributes": { "AttachedTo": { @@ -89038,9 +93425,6 @@ "IsStaticIp": { "PrimitiveType": "Boolean" }, - "KeyPairName": { - "PrimitiveType": "String" - }, "Location.AvailabilityZone": { "PrimitiveType": "String" }, @@ -89071,9 +93455,6 @@ "SupportCode": { "PrimitiveType": "String" }, - "UserData": { - "PrimitiveType": "String" - }, "UserName": { "PrimitiveType": "String" } @@ -89117,10 +93498,10 @@ "Required": true, "UpdateType": "Immutable" }, - "Location": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-location", + "KeyPairName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-keypairname", + "PrimitiveType": "String", "Required": false, - "Type": "Location", "UpdateType": "Mutable" }, "Networking": { @@ -89129,12 +93510,6 @@ "Type": "Networking", "UpdateType": "Mutable" }, - "State": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-state", - "Required": false, - "Type": "State", - "UpdateType": "Mutable" - }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-tags", "DuplicatesAllowed": false, @@ -89142,6 +93517,40 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" + }, + "UserData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-userdata", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Lightsail::StaticIp": { + "Attributes": { + "IpAddress": { + "PrimitiveType": "String" + }, + "IsAttached": { + "PrimitiveType": "Boolean" + }, + "StaticIpArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-staticip.html", + "Properties": { + "AttachedTo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-staticip.html#cfn-lightsail-staticip-attachedto", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "StaticIpName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-staticip.html#cfn-lightsail-staticip-staticipname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" } } }, @@ -89361,6 +93770,12 @@ "Required": false, "UpdateType": "Immutable" }, + "PositionFiltering": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-positionfiltering", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "PricingPlan": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html#cfn-location-tracker-pricingplan", "PrimitiveType": "String", @@ -89457,6 +93872,14 @@ "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -89941,8 +94364,8 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mwaa-environment.html#cfn-mwaa-environment-tags", + "PrimitiveType": "Json", "Required": false, - "Type": "TagMap", "UpdateType": "Mutable" }, "WebserverAccessMode": { @@ -91230,7 +95653,7 @@ "ACLName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-aclname", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "AutoMinorVersionUpgrade": { @@ -91239,12 +95662,6 @@ "Required": false, "UpdateType": "Mutable" }, - "ClusterEndpoint": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-clusterendpoint", - "Required": false, - "Type": "Endpoint", - "UpdateType": "Mutable" - }, "ClusterName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-clustername", "PrimitiveType": "String", @@ -91284,7 +95701,7 @@ "NodeType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-nodetype", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "NumReplicasPerShard": { @@ -91396,7 +95813,7 @@ "Family": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-parametergroup.html#cfn-memorydb-parametergroup-family", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Immutable" }, "ParameterGroupName": { @@ -91445,7 +95862,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-subnetgroup.html#cfn-memorydb-subnetgroup-subnetids", "DuplicatesAllowed": false, "PrimitiveItemType": "String", - "Required": false, + "Required": true, "Type": "List", "UpdateType": "Mutable" }, @@ -93403,6 +97820,190 @@ } } }, + "AWS::Panorama::ApplicationInstance": { + "Attributes": { + "ApplicationInstanceId": { + "PrimitiveType": "String" + }, + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "Integer" + }, + "DefaultRuntimeContextDeviceName": { + "PrimitiveType": "String" + }, + "HealthStatus": { + "PrimitiveType": "String" + }, + "LastUpdatedTime": { + "PrimitiveType": "Integer" + }, + "Status": { + "PrimitiveType": "String" + }, + "StatusDescription": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html", + "Properties": { + "ApplicationInstanceIdToReplace": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-applicationinstanceidtoreplace", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DefaultRuntimeContextDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-defaultruntimecontextdevice", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DeviceId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-deviceid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ManifestOverridesPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-manifestoverridespayload", + "Required": false, + "Type": "ManifestOverridesPayload", + "UpdateType": "Immutable" + }, + "ManifestPayload": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-manifestpayload", + "Required": true, + "Type": "ManifestPayload", + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "RuntimeRoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-runtimerolearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "StatusFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-statusfilter", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html#cfn-panorama-applicationinstance-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Panorama::Package": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "Integer" + }, + "PackageId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-package.html", + "Properties": { + "PackageName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-package.html#cfn-panorama-package-packagename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-package.html#cfn-panorama-package-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Panorama::PackageVersion": { + "Attributes": { + "IsLatestPatch": { + "PrimitiveType": "Boolean" + }, + "PackageArn": { + "PrimitiveType": "String" + }, + "PackageName": { + "PrimitiveType": "String" + }, + "RegisteredTime": { + "PrimitiveType": "Integer" + }, + "Status": { + "PrimitiveType": "String" + }, + "StatusDescription": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html", + "Properties": { + "MarkLatest": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-marklatest", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "OwnerAccount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-owneraccount", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "PackageId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-packageid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PackageVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-packageversion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PatchVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-patchversion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "UpdatedLatestPatchVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html#cfn-panorama-packageversion-updatedlatestpatchversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Pinpoint::ADMChannel": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-admchannel.html", "Properties": { @@ -93820,6 +98421,12 @@ "Required": true, "UpdateType": "Mutable" }, + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-campaign.html#cfn-pinpoint-campaign-priority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "Schedule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-campaign.html#cfn-pinpoint-campaign-schedule", "Required": true, @@ -93997,6 +98604,53 @@ } } }, + "AWS::Pinpoint::InAppTemplate": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html", + "Properties": { + "Content": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-content", + "ItemType": "InAppMessageContent", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "CustomConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-customconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "Layout": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-layout", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-tags", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "TemplateDescription": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-templatedescription", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TemplateName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html#cfn-pinpoint-inapptemplate-templatename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::Pinpoint::PushTemplate": { "Attributes": { "Arn": { @@ -94487,7 +99141,7 @@ }, "SourceEntity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-analysis.html#cfn-quicksight-analysis-sourceentity", - "Required": false, + "Required": true, "Type": "AnalysisSourceEntity", "UpdateType": "Mutable" }, @@ -94562,7 +99216,7 @@ }, "SourceEntity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-dashboard.html#cfn-quicksight-dashboard-sourceentity", - "Required": false, + "Required": true, "Type": "DashboardSourceEntity", "UpdateType": "Mutable" }, @@ -94824,7 +99478,7 @@ }, "SourceEntity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-template.html#cfn-quicksight-template-sourceentity", - "Required": false, + "Required": true, "Type": "TemplateSourceEntity", "UpdateType": "Mutable" }, @@ -96138,12 +100792,6 @@ "Required": false, "UpdateType": "Mutable" }, - "Endpoint": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html#cfn-redshift-cluster-endpoint", - "Required": false, - "Type": "Endpoint", - "UpdateType": "Mutable" - }, "EnhancedVpcRouting": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html#cfn-redshift-cluster-enhancedvpcrouting", "PrimitiveType": "Boolean", @@ -96416,6 +101064,273 @@ } } }, + "AWS::Redshift::EndpointAccess": { + "Attributes": { + "Address": { + "PrimitiveType": "String" + }, + "EndpointCreateTime": { + "PrimitiveType": "String" + }, + "EndpointStatus": { + "PrimitiveType": "String" + }, + "Port": { + "PrimitiveType": "Integer" + }, + "VpcSecurityGroups": { + "ItemType": "VpcSecurityGroup", + "Type": "List" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html", + "Properties": { + "ClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html#cfn-redshift-endpointaccess-clusteridentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "EndpointName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html#cfn-redshift-endpointaccess-endpointname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ResourceOwner": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html#cfn-redshift-endpointaccess-resourceowner", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "SubnetGroupName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html#cfn-redshift-endpointaccess-subnetgroupname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "VpcSecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html#cfn-redshift-endpointaccess-vpcsecuritygroupids", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::EndpointAuthorization": { + "Attributes": { + "AllowedAllVPCs": { + "PrimitiveType": "Boolean" + }, + "AllowedVPCs": { + "PrimitiveItemType": "String", + "Type": "List" + }, + "AuthorizeTime": { + "PrimitiveType": "String" + }, + "ClusterStatus": { + "PrimitiveType": "String" + }, + "EndpointCount": { + "PrimitiveType": "Integer" + }, + "Grantee": { + "PrimitiveType": "String" + }, + "Grantor": { + "PrimitiveType": "String" + }, + "Status": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html", + "Properties": { + "Account": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html#cfn-redshift-endpointauthorization-account", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html#cfn-redshift-endpointauthorization-clusteridentifier", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Force": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html#cfn-redshift-endpointauthorization-force", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "VpcIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html#cfn-redshift-endpointauthorization-vpcids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::EventSubscription": { + "Attributes": { + "CustSubscriptionId": { + "PrimitiveType": "String" + }, + "CustomerAwsId": { + "PrimitiveType": "String" + }, + "EventCategoriesList": { + "PrimitiveItemType": "String", + "Type": "List" + }, + "SourceIdsList": { + "PrimitiveItemType": "String", + "Type": "List" + }, + "Status": { + "PrimitiveType": "String" + }, + "SubscriptionCreationTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "EventCategories": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-eventcategories", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Severity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-severity", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SnsTopicArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-snstopicarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SourceIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-sourceids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SourceType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-sourcetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SubscriptionName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-subscriptionname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html#cfn-redshift-eventsubscription-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Redshift::ScheduledAction": { + "Attributes": { + "NextInvocations": { + "PrimitiveItemType": "String", + "Type": "List" + }, + "State": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html", + "Properties": { + "Enable": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-enable", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "EndTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-endtime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IamRole": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-iamrole", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Schedule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-schedule", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ScheduledActionDescription": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-scheduledactiondescription", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ScheduledActionName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-scheduledactionname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "StartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-starttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TargetAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html#cfn-redshift-scheduledaction-targetaction", + "Required": false, + "Type": "ScheduledActionType", + "UpdateType": "Mutable" + } + } + }, + "AWS::Rekognition::Project": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rekognition-project.html", + "Properties": { + "ProjectName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rekognition-project.html#cfn-rekognition-project-projectname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::ResourceGroups::Group": { "Attributes": { "Arn": { @@ -96751,7 +101666,7 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html#cfn-route53-hostedzone-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "QueryLoggingConfig": { @@ -96947,6 +101862,13 @@ "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-cluster.html#cfn-route53recoverycontrol-cluster-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" } } }, @@ -96978,6 +101900,13 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-controlpanel.html#cfn-route53recoverycontrol-controlpanel-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" } } }, @@ -97052,6 +101981,13 @@ "Required": true, "Type": "RuleConfig", "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-safetyrule.html#cfn-route53recoverycontrol-safetyrule-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" } } }, @@ -97374,6 +102310,34 @@ } } }, + "AWS::Route53Resolver::ResolverConfig": { + "Attributes": { + "AutodefinedReverse": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "OwnerId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverconfig.html", + "Properties": { + "AutodefinedReverseFlag": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverconfig.html#cfn-route53resolver-resolverconfig-autodefinedreverseflag", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ResourceId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverconfig.html#cfn-route53resolver-resolverconfig-resourceid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::Route53Resolver::ResolverDNSSECConfig": { "Attributes": { "Id": { @@ -97549,7 +102513,9 @@ "PrimitiveType": "String" }, "TargetIps": { - "PrimitiveType": "String" + "DuplicatesAllowed": true, + "ItemType": "TargetAddress", + "Type": "List" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverrule.html", @@ -97580,6 +102546,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverrule.html#cfn-route53resolver-resolverrule-tags", + "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -97587,6 +102554,7 @@ }, "TargetIps": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverrule.html#cfn-route53resolver-resolverrule-targetips", + "DuplicatesAllowed": true, "ItemType": "TargetAddress", "Required": false, "Type": "List", @@ -97943,12 +102911,12 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3objectlambda-accesspoint.html#cfn-s3objectlambda-accesspoint-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "ObjectLambdaConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3objectlambda-accesspoint.html#cfn-s3objectlambda-accesspoint-objectlambdaconfiguration", - "Required": false, + "Required": true, "Type": "ObjectLambdaConfiguration", "UpdateType": "Mutable" } @@ -99729,6 +104697,12 @@ "Required": false, "UpdateType": "Mutable" }, + "RetainDeploymentConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-endpoint.html#cfn-sagemaker-endpoint-retaindeploymentconfig", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-endpoint.html#cfn-sagemaker-endpoint-tags", "ItemType": "Tag", @@ -100361,6 +105335,12 @@ "Required": false, "UpdateType": "Immutable" }, + "PlatformIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-notebookinstance.html#cfn-sagemaker-notebookinstance-platformidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "RoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-notebookinstance.html#cfn-sagemaker-notebookinstance-rolearn", "PrimitiveType": "String", @@ -101785,6 +106765,9 @@ }, "AWS::StepFunctions::Activity": { "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, "Name": { "PrimitiveType": "String" } @@ -101799,6 +106782,7 @@ }, "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", @@ -101893,6 +106877,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html", "Properties": { + "ArtifactConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-artifactconfig", + "Required": false, + "Type": "ArtifactConfig", + "UpdateType": "Mutable" + }, "ArtifactS3Location": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-artifacts3location", "PrimitiveType": "String", @@ -102886,6 +107876,150 @@ } } }, + "AWS::Wisdom::Assistant": { + "Attributes": { + "AssistantArn": { + "PrimitiveType": "String" + }, + "AssistantId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html#cfn-wisdom-assistant-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html#cfn-wisdom-assistant-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ServerSideEncryptionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html#cfn-wisdom-assistant-serversideencryptionconfiguration", + "Required": false, + "Type": "ServerSideEncryptionConfiguration", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html#cfn-wisdom-assistant-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html#cfn-wisdom-assistant-type", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::AssistantAssociation": { + "Attributes": { + "AssistantArn": { + "PrimitiveType": "String" + }, + "AssistantAssociationArn": { + "PrimitiveType": "String" + }, + "AssistantAssociationId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html", + "Properties": { + "AssistantId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html#cfn-wisdom-assistantassociation-assistantid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Association": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html#cfn-wisdom-assistantassociation-association", + "Required": true, + "Type": "AssociationData", + "UpdateType": "Immutable" + }, + "AssociationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html#cfn-wisdom-assistantassociation-associationtype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html#cfn-wisdom-assistantassociation-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::Wisdom::KnowledgeBase": { + "Attributes": { + "KnowledgeBaseArn": { + "PrimitiveType": "String" + }, + "KnowledgeBaseId": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "KnowledgeBaseType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-knowledgebasetype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RenderingConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-renderingconfiguration", + "Required": false, + "Type": "RenderingConfiguration", + "UpdateType": "Mutable" + }, + "ServerSideEncryptionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-serversideencryptionconfiguration", + "Required": false, + "Type": "ServerSideEncryptionConfiguration", + "UpdateType": "Immutable" + }, + "SourceConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-sourceconfiguration", + "Required": false, + "Type": "SourceConfiguration", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html#cfn-wisdom-knowledgebase-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, "AWS::WorkSpaces::ConnectionAlias": { "Attributes": { "AliasId": { diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-lint/StatefulResources/000.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-lint/StatefulResources/000.json index 7037f14238497..dbfa3dfcdd033 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-lint/StatefulResources/000.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-lint/StatefulResources/000.json @@ -25,6 +25,7 @@ "AWS::SQS::Queue" : {}, "AWS::S3::Bucket" : { "DeleteRequiresEmptyResource": true - } + }, + "AWS::SecretsManager::Secret" : {} } } diff --git a/packages/@aws-cdk/cfnspec/test/filtered-specification.test.ts b/packages/@aws-cdk/cfnspec/test/filtered-specification.test.ts index a4bd359517da1..e9f18346699ea 100644 --- a/packages/@aws-cdk/cfnspec/test/filtered-specification.test.ts +++ b/packages/@aws-cdk/cfnspec/test/filtered-specification.test.ts @@ -14,7 +14,7 @@ test('filteredSpecification(s => s.startsWith("AWS::S3::")', () => { }); for (const name of resourceTypes().sort()) { - test(`filteredSpecification(${JSON.stringify(name)})`, () => { + describe(`filteredSpecification(${JSON.stringify(name)})`, () => { const filteredSpec = filteredSpecification(name); expect(filteredSpec).not.toEqual(specification); expect(filteredSpec.ResourceTypes).not.toEqual({}); diff --git a/packages/@aws-cdk/cfnspec/test/libary-creation.test.ts b/packages/@aws-cdk/cfnspec/test/libary-creation.test.ts new file mode 100644 index 0000000000000..354952325f314 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/test/libary-creation.test.ts @@ -0,0 +1,59 @@ +import { createModuleDefinitionFromCfnNamespace } from '../lib'; + +describe('createModuleDefinitionFromCfnNamespace', () => { + + test('base case', () => { + const module = createModuleDefinitionFromCfnNamespace('AWS::EC2'); + + expect(module).toEqual({ + namespace: 'AWS::EC2', + moduleName: 'aws-ec2', + moduleFamily: 'AWS', + moduleBaseName: 'EC2', + packageName: '@aws-cdk/aws-ec2', + dotnetPackage: 'Amazon.CDK.AWS.EC2', + javaGroupId: 'software.amazon.awscdk', + javaPackage: 'services.ec2', + javaArtifactId: 'ec2', + pythonDistName: 'aws-cdk.aws-ec2', + pythonModuleName: 'aws_cdk.aws_ec2', + }); + }); + + test('Serverless is special-cased to SAM', () => { + const module = createModuleDefinitionFromCfnNamespace('AWS::Serverless'); + + expect(module).toEqual({ + namespace: 'AWS::Serverless', + moduleName: 'aws-sam', + moduleFamily: 'AWS', + moduleBaseName: 'SAM', + packageName: '@aws-cdk/aws-sam', + dotnetPackage: 'Amazon.CDK.AWS.SAM', + javaGroupId: 'software.amazon.awscdk', + javaPackage: 'services.sam', + javaArtifactId: 'sam', + pythonDistName: 'aws-cdk.aws-sam', + pythonModuleName: 'aws_cdk.aws_sam', + }); + }); + + test('Java artifacts use different package/artifact when module family is not AWS', () => { + const module = createModuleDefinitionFromCfnNamespace('Alexa::ASK'); + + expect(module).toEqual({ + namespace: 'Alexa::ASK', + moduleName: 'alexa-ask', + moduleFamily: 'Alexa', + moduleBaseName: 'ASK', + packageName: '@aws-cdk/alexa-ask', + dotnetPackage: 'Amazon.CDK.Alexa.ASK', + javaGroupId: 'software.amazon.awscdk', + javaPackage: 'alexa.ask', + javaArtifactId: 'alexa-ask', + pythonDistName: 'aws-cdk.alexa-ask', + pythonModuleName: 'aws_cdk.alexa_ask', + }); + }); + +}); diff --git a/packages/@aws-cdk/cfnspec/test/spec-validators.ts b/packages/@aws-cdk/cfnspec/test/spec-validators.ts index 408febb3e1b50..c8993885332fb 100644 --- a/packages/@aws-cdk/cfnspec/test/spec-validators.ts +++ b/packages/@aws-cdk/cfnspec/test/spec-validators.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-export */ import * as schema from '../lib/schema'; export function validateSpecification(specification: schema.Specification) { @@ -7,26 +8,30 @@ export function validateSpecification(specification: schema.Specification) { function validateResourceTypes(specification: schema.Specification) { for (const typeName of Object.keys(specification.ResourceTypes)) { - expect(typeName).toBeTruthy(); - const type = specification.ResourceTypes[typeName]; - expect(type.Documentation).not.toBeNull(); - if (type.ScrutinyType) { - expect(schema.isResourceScrutinyType(type.ScrutinyType)).toBeTruthy(); - } - if (type.Properties) { validateProperties(typeName, type.Properties, specification); } - if (type.Attributes) { validateAttributes(typeName, type.Attributes, specification); } + describe(typeName, () => { + expect(typeName).toBeTruthy(); + const type = specification.ResourceTypes[typeName]; + expect(type.Documentation).not.toBeNull(); + if (type.ScrutinyType) { + expect(schema.isResourceScrutinyType(type.ScrutinyType)).toBeTruthy(); + } + if (type.Properties) { validateProperties(typeName, type.Properties, specification); } + if (type.Attributes) { validateAttributes(typeName, type.Attributes, specification); } + }); } } function validatePropertyTypes(specification: schema.Specification) { for (const typeName of Object.keys(specification.PropertyTypes)) { - expect(typeName).toBeTruthy(); - const type = specification.PropertyTypes[typeName]; - if (schema.isRecordType(type)) { - validateProperties(typeName, type.Properties, specification); - } else { - validateProperties(typeName, { '': type }, specification); - } + describe(`PropertyType ${typeName}`, () => { + expect(typeName).toBeTruthy(); + const type = specification.PropertyTypes[typeName]; + if (schema.isRecordType(type)) { + validateProperties(typeName, type.Properties, specification); + } else { + validateProperties(typeName, { '': type }, specification); + } + }); } } @@ -37,84 +42,87 @@ function validateProperties( const expectedKeys = ['Documentation', 'Required', 'UpdateType', 'ScrutinyType']; for (const name of Object.keys(properties)) { - const property = properties[name]; - expect(property.Documentation).not.toEqual(''); - expect(!property.UpdateType || schema.isUpdateType(property.UpdateType)).toBeTruthy(); - if (property.ScrutinyType !== undefined) { - expect(schema.isPropertyScrutinyType(property.ScrutinyType)).toBeTruthy(); - } + test(`Property ${name}`, () => { + const property = properties[name]; + expect(property.Documentation).not.toEqual(''); + expect(!property.UpdateType || schema.isUpdateType(property.UpdateType)).toBeTruthy(); + if (property.ScrutinyType !== undefined) { + expect(schema.isPropertyScrutinyType(property.ScrutinyType)).toBeTruthy(); + } + + if (schema.isPrimitiveProperty(property)) { + expect(schema.isPrimitiveType(property.PrimitiveType)).toBeTruthy(); + expectedKeys.push('PrimitiveType'); - if (schema.isPrimitiveProperty(property)) { - expect(schema.isPrimitiveType(property.PrimitiveType)).toBeTruthy(); - expectedKeys.push('PrimitiveType'); + } else if (schema.isPrimitiveListProperty(property)) { + expectedKeys.push('Type', 'DuplicatesAllowed', 'PrimitiveItemType'); + expect(schema.isPrimitiveType(property.PrimitiveItemType)).toBeTruthy(); - } else if (schema.isPrimitiveListProperty(property)) { - expectedKeys.push('Type', 'DuplicatesAllowed', 'PrimitiveItemType'); - expect(schema.isPrimitiveType(property.PrimitiveItemType)).toBeTruthy(); + } else if (schema.isPrimitiveMapProperty(property)) { + expectedKeys.push('Type', 'DuplicatesAllowed', 'PrimitiveItemType', 'Type'); + expect(schema.isPrimitiveType(property.PrimitiveItemType)).toBeTruthy(); + expect(property.DuplicatesAllowed).toBeFalsy(); - } else if (schema.isPrimitiveMapProperty(property)) { - expectedKeys.push('Type', 'DuplicatesAllowed', 'PrimitiveItemType', 'Type'); - expect(schema.isPrimitiveType(property.PrimitiveItemType)).toBeTruthy(); - expect(property.DuplicatesAllowed).toBeFalsy(); + } else if (schema.isComplexListProperty(property)) { + expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'Type'); + expect(property.ItemType).toBeTruthy(); + if (property.ItemType !== 'Tag') { + const fqn = `${typeName.split('.')[0]}.${property.ItemType}`; + const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; + expect(resolvedType).toBeTruthy(); + } - } else if (schema.isComplexListProperty(property)) { - expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'Type'); - expect(property.ItemType).toBeTruthy(); - if (property.ItemType !== 'Tag') { + } else if (schema.isMapOfStructsProperty(property)) { + expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'Type'); + expect(property.ItemType).toBeTruthy(); const fqn = `${typeName.split('.')[0]}.${property.ItemType}`; const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; expect(resolvedType).toBeTruthy(); - } + expect(property.DuplicatesAllowed).toBeFalsy(); + + } else if (schema.isMapOfListsOfPrimitivesProperty(property)) { + expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'PrimitiveItemItemType', 'Type'); + expect(schema.isPrimitiveType(property.PrimitiveItemItemType)).toBeTruthy(); + expect(property.DuplicatesAllowed).toBeFalsy(); + + } else if (schema.isComplexProperty(property)) { + expectedKeys.push('Type'); + expect(property.Type).toBeTruthy(); + const fqn = `${typeName.split('.')[0]}.${property.Type}`; + const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; + expect(resolvedType).toBeTruthy(); - } else if (schema.isMapOfStructsProperty(property)) { - expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'Type'); - expect(property.ItemType).toBeTruthy(); - const fqn = `${typeName.split('.')[0]}.${property.ItemType}`; - const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; - expect(resolvedType).toBeTruthy(); - expect(property.DuplicatesAllowed).toBeFalsy(); - - } else if (schema.isMapOfListsOfPrimitivesProperty(property)) { - expectedKeys.push('Type', 'DuplicatesAllowed', 'ItemType', 'PrimitiveItemItemType', 'Type'); - expect(schema.isPrimitiveType(property.PrimitiveItemItemType)).toBeTruthy(); - expect(property.DuplicatesAllowed).toBeFalsy(); - - } else if (schema.isComplexProperty(property)) { - expectedKeys.push('Type'); - expect(property.Type).toBeTruthy(); - const fqn = `${typeName.split('.')[0]}.${property.Type}`; - const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; - expect(resolvedType).toBeTruthy(); - - } else if (schema.isUnionProperty(property)) { - expectedKeys.push('PrimitiveTypes', 'PrimitiveItemTypes', 'ItemTypes', 'Types'); - if (property.PrimitiveTypes) { - for (const type of property.PrimitiveTypes) { - expect(schema.isPrimitiveType(type)).toBeTruthy(); + } else if (schema.isUnionProperty(property)) { + expectedKeys.push('PrimitiveTypes', 'PrimitiveItemTypes', 'ItemTypes', 'Types'); + if (property.PrimitiveTypes) { + for (const type of property.PrimitiveTypes) { + expect(schema.isPrimitiveType(type)).toBeTruthy(); + } } - } - if (property.ItemTypes) { - for (const type of property.ItemTypes) { - const fqn = `${typeName.split('.')[0]}.${type}`; - const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; - expect(resolvedType).toBeTruthy(); + if (property.ItemTypes) { + for (const type of property.ItemTypes) { + const fqn = `${typeName.split('.')[0]}.${type}`; + const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; + expect(resolvedType).toBeTruthy(); + } } - } - if (property.Types) { - for (const type of property.Types) { - const fqn = `${typeName.split('.')[0]}.${type}`; - const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; - expect(resolvedType).toBeTruthy(); + if (property.Types) { + for (const type of property.Types) { + const fqn = `${typeName.split('.')[0]}.${type}`; + const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; + expect(resolvedType).toBeTruthy(); + } } - } - } else { - // eslint-disable-next-line no-console - console.error(`${typeName}.Properties.${name} has known type: ${JSON.stringify(property)}`); - expect(false).toBeTruthy(); - } + } else { + // eslint-disable-next-line no-console + console.error(`${typeName}.Properties.${name} does not declare a type.` + + `Property definition is: ${JSON.stringify(property, undefined, 2)}`); + expect(false).toBeTruthy(); + } - expect(without(Object.keys(property), expectedKeys)).toEqual([]); + expect(without(Object.keys(property), expectedKeys)).toEqual([]); + }); } } @@ -123,29 +131,31 @@ function validateAttributes( attributes: { [name: string]: schema.Attribute }, specification: schema.Specification) { for (const name of Object.keys(attributes)) { - const attribute = attributes[name]; - expect(('Type' in attribute)).not.toEqual(('PrimitiveType' in attribute)); - if (schema.isPrimitiveAttribute(attribute)) { - expect(schema.isListAttribute(attribute)).toBeFalsy(); - expect(schema.isPrimitiveType(attribute.PrimitiveType)).toBeTruthy(); - expect(('PrimitiveItemType' in attribute)).toBeFalsy(); - expect(('ItemType' in attribute)).toBeFalsy(); - } else if (schema.isPrimitiveListAttribute(attribute)) { - expect(schema.isComplexListAttribute(attribute)).toBeFalsy(); - expect(schema.isPrimitiveType(attribute.PrimitiveItemType)).toBeTruthy(); - expect(('ItemType' in attribute)).toBeFalsy(); - } else if (schema.isComplexListAttribute(attribute)) { - expect(attribute.ItemType).toBeTruthy(); - const fqn = `${typeName.split('.')[0]}.${attribute.ItemType}`; - const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; - expect(resolvedType).toBeTruthy(); - expect(('PrimitiveItemType' in attribute)).toBeFalsy(); - } else if (schema.isPrimitiveMapAttribute(attribute)) { - expect(schema.isPrimitiveType(attribute.PrimitiveItemType)).toBeTruthy(); - expect(('ItemType' in attribute)).toBeFalsy(); - } else { - expect(false).toBeTruthy(); // `${typeName}.Attributes.${name} has a valid type`); - } + test(`Attribute ${name}`, () => { + const attribute = attributes[name]; + expect(('Type' in attribute)).not.toEqual(('PrimitiveType' in attribute)); + if (schema.isPrimitiveAttribute(attribute)) { + expect(schema.isListAttribute(attribute)).toBeFalsy(); + expect(schema.isPrimitiveType(attribute.PrimitiveType)).toBeTruthy(); + expect(('PrimitiveItemType' in attribute)).toBeFalsy(); + expect(('ItemType' in attribute)).toBeFalsy(); + } else if (schema.isPrimitiveListAttribute(attribute)) { + expect(schema.isComplexListAttribute(attribute)).toBeFalsy(); + expect(schema.isPrimitiveType(attribute.PrimitiveItemType)).toBeTruthy(); + expect(('ItemType' in attribute)).toBeFalsy(); + } else if (schema.isComplexListAttribute(attribute)) { + expect(attribute.ItemType).toBeTruthy(); + const fqn = `${typeName.split('.')[0]}.${attribute.ItemType}`; + const resolvedType = specification.PropertyTypes && specification.PropertyTypes[fqn]; + expect(resolvedType).toBeTruthy(); + expect(('PrimitiveItemType' in attribute)).toBeFalsy(); + } else if (schema.isPrimitiveMapAttribute(attribute)) { + expect(schema.isPrimitiveType(attribute.PrimitiveItemType)).toBeTruthy(); + expect(('ItemType' in attribute)).toBeFalsy(); + } else { + expect(false).toBeTruthy(); // `${typeName}.Attributes.${name} has a valid type`); + } + }); } } diff --git a/packages/@aws-cdk/cloud-assembly-schema/.gitignore b/packages/@aws-cdk/cloud-assembly-schema/.gitignore index eec83f25377a7..8efa23cb23766 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.gitignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.gitignore @@ -1,6 +1,7 @@ lib/**/*.js test/**/*.js scripts/*.js +.warnings.jsii.js *.js.map *.d.ts node_modules diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts index ebed927f1828e..97ede25af2498 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts @@ -416,8 +416,24 @@ export interface SecurityGroupContextQuery { /** * Security group id + * + * @default - None + */ + readonly securityGroupId?: string; + + /** + * Security group name + * + * @default - None */ - readonly securityGroupId: string; + readonly securityGroupName?: string; + + /** + * VPC ID + * + * @default - None + */ + readonly vpcId?: string; } /** diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index e6aee07a67f09..e341cacf324ea 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -32,7 +32,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": true + "strict": false } } } @@ -62,10 +62,10 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/mock-fs": "^4.13.1", - "@types/semver": "^7.3.8", - "jest": "^26.6.3", + "@types/semver": "^7.3.9", + "jest": "^27.3.1", "mock-fs": "^4.14.0", "typescript-json-schema": "^0.51.0" }, diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json index d52512102e9a6..9241ae62ef0ff 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -761,14 +761,21 @@ "type": "string" }, "securityGroupId": { - "description": "Security group id", + "description": "Security group id (Default - None)", + "type": "string" + }, + "securityGroupName": { + "description": "Security group name (Default - None)", + "type": "string" + }, + "vpcId": { + "description": "VPC ID (Default - None)", "type": "string" } }, "required": [ "account", - "region", - "securityGroupId" + "region" ] }, "KeyContextQuery": { diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json index c1ee7b9a1f8ad..01d4f111912e9 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"14.0.0"} \ No newline at end of file +{"version":"15.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts index b3f282802b675..6e06e56f90af1 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts @@ -99,13 +99,17 @@ function calculateTemplateDiff(currentTemplate: { [key: string]: any }, newTempl for (const key of unionOf(Object.keys(currentTemplate), Object.keys(newTemplate)).sort()) { const oldValue = currentTemplate[key]; const newValue = newTemplate[key]; - if (deepEqual(oldValue, newValue)) { continue; } + if (deepEqual(oldValue, newValue)) { + continue; + } const handler: DiffHandler = DIFF_HANDLERS[key] || ((_diff, oldV, newV) => unknown[key] = impl.diffUnknown(oldV, newV)); handler(differences, oldValue, newValue); } - if (Object.keys(unknown).length > 0) { differences.unknown = new types.DifferenceCollection(unknown); } + if (Object.keys(unknown).length > 0) { + differences.unknown = new types.DifferenceCollection(unknown); + } return new types.TemplateDiff(differences); } diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts index 59c8606be0a35..1cbd4b1a111d7 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts @@ -138,20 +138,10 @@ export function unionOf(lv: string[] | Set, rv: string[] | Set): * A parseFloat implementation that does the right thing for * strings like '0.0.0' * (for which JavaScript's parseFloat() returns 0). + * We return NaN for all of these strings that do not represent numbers, + * and so comparing them fails, + * and doesn't short-circuit the diff logic. */ function safeParseFloat(str: string): number { - const ret = parseFloat(str); - const nonNumericRegex = /\d*\.\d+\./; - if (ret === 0) { - // if the str is exactly '0', that's OK; - // but parseFloat() also returns 0 for things like '0.0'; - // in this case, return NaN, so we'll fall back to string comparison - return str === '0' ? ret : NaN; - } else if (nonNumericRegex.test(str)) { - // if the str contains non-numeric characters, - // return NaN, so we'll fall back to string comparison - return NaN; - } else { - return ret; - } + return Number(str); } diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 373bdec4b156a..1e9b1c5a412ca 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -29,16 +29,16 @@ "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", "string-width": "^4.2.3", - "table": "^6.7.2" + "table": "^6.7.3" }, "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/string-width": "^4.0.1", - "fast-check": "^2.17.0", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "fast-check": "^2.19.0", + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts b/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts index 9241d0e8e28eb..d43ec99808d31 100644 --- a/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts +++ b/packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts @@ -582,70 +582,57 @@ test('when a property changes including equivalent DependsOn', () => { expect(differences.resources.differenceCount).toBe(1); }); -test('when a property with a number-like format changes', () => { - const bucketName = 'ShineyBucketName'; - const tagChanges = { - '0.31.1-prod': '0.31.2-prod', - '8.0.5.5.4-identifier': '8.0.5.5.5-identifier', - '1.1.1.1': '1.1.2.2', - '1.2.3': '1.2.4', - '2.2.2.2': '2.2.3.2', - '3.3.3.3': '3.4.3.3', - }; - const oldTags = Object.keys(tagChanges); - const newTags = Object.values(tagChanges); +test.each([ + ['0.31.1-prod', '0.31.2-prod'], + ['8.0.5.5.4-identifier', '8.0.5.5.5-identifier'], + ['1.1.1.1', '1.1.1.2'], + ['1.2.3', '1.2.4'], + ['2.2.2.2', '2.2.3.2'], + ['3.3.3.3', '3.4.3.3'], + ['2021-10-23T06:07:08.000Z', '2021-10-23T09:10:11.123Z'], +])("reports a change when a string property with a number-like format changes from '%s' to '%s'", (oldValue, newValue) => { + // GIVEN const currentTemplate = { Resources: { - QueueResource: { - Type: 'AWS::SQS::Queue', - }, BucketResource: { Type: 'AWS::S3::Bucket', Properties: { - BucketName: bucketName, - Tags: oldTags, + Tags: [oldValue], }, }, }, }; const newTemplate = { Resources: { - QueueResource: { - Type: 'AWS::SQS::Queue', - }, BucketResource: { Type: 'AWS::S3::Bucket', Properties: { - BucketName: bucketName, - Tags: newTags, + Tags: [newValue], }, }, }, }; - + // WHEN const differences = diffTemplate(currentTemplate, newTemplate); + + // THEN expect(differences.differenceCount).toBe(1); expect(differences.resources.differenceCount).toBe(1); const difference = differences.resources.changes.BucketResource; expect(difference).not.toBeUndefined(); expect(difference?.oldResourceType).toEqual('AWS::S3::Bucket'); expect(difference?.propertyUpdates).toEqual({ - Tags: { oldValue: oldTags, newValue: newTags, changeImpact: ResourceImpact.WILL_UPDATE, isDifferent: true }, + Tags: { oldValue: [oldValue], newValue: [newValue], changeImpact: ResourceImpact.WILL_UPDATE, isDifferent: true }, }); }); test('when a property with a number-like format doesn\'t change', () => { - const bucketName = 'ShineyBucketName'; const tags = ['0.31.1-prod', '8.0.5.5.4-identifier', '1.1.1.1', '1.2.3']; const currentTemplate = { Resources: { - QueueResource: { - Type: 'AWS::SQS::Queue', - }, BucketResource: { Type: 'AWS::S3::Bucket', Properties: { - BucketName: bucketName, Tags: tags, }, }, @@ -653,13 +640,9 @@ test('when a property with a number-like format doesn\'t change', () => { }; const newTemplate = { Resources: { - QueueResource: { - Type: 'AWS::SQS::Queue', - }, BucketResource: { Type: 'AWS::S3::Bucket', Properties: { - BucketName: bucketName, Tags: tags, }, }, @@ -671,4 +654,4 @@ test('when a property with a number-like format doesn\'t change', () => { expect(differences.resources.differenceCount).toBe(0); const difference = differences.resources.changes.BucketResource; expect(difference).toBeUndefined(); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 2c67aeb4d554b..560718ea1449c 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -46,8 +46,6 @@ Resources: It can be included in a CDK application with the following code: ```ts -import * as cfn_inc from '@aws-cdk/cloudformation-include'; - const cfnTemplate = new cfn_inc.CfnInclude(this, 'Template', { templateFile: 'my-template.json', }); @@ -82,8 +80,7 @@ If you know the class of the CDK object that corresponds to that resource, you can cast the returned object to the correct type: ```ts -import * as s3 from '@aws-cdk/aws-s3'; - +declare const cfnTemplate: cfn_inc.CfnInclude; const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; // cfnBucket is of type s3.CfnBucket ``` @@ -98,6 +95,8 @@ Any modifications made to that resource will be reflected in the resulting CDK t for example, the name of the bucket can be changed: ```ts +declare const cfnTemplate: cfn_inc.CfnInclude; +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; cfnBucket.bucketName = 'my-bucket-name'; ``` @@ -107,7 +106,8 @@ including the higher-level ones for example: ```ts -import * as iam from '@aws-cdk/aws-iam'; +declare const cfnTemplate: cfn_inc.CfnInclude; +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; const role = new iam.Role(this, 'Role', { assumedBy: new iam.AnyPrincipal(), @@ -136,8 +136,7 @@ for example, for KMS Keys, that would be the `Kms.fromCfnKey()` method - and passing the L1 instance as an argument: ```ts -import * as kms from '@aws-cdk/aws-kms'; - +declare const cfnTemplate: cfn_inc.CfnInclude; const cfnKey = cfnTemplate.getResource('Key') as kms.CfnKey; const key = kms.Key.fromCfnKey(cfnKey); ``` @@ -203,16 +202,23 @@ Each L2 class has static factory methods with names like `from*Name()`, You can obtain an L2 resource from an L1 by passing the correct properties of the L1 as the arguments to those methods: ```ts +declare const cfnTemplate: cfn_inc.CfnInclude; + // using from*Name() +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; const bucket = s3.Bucket.fromBucketName(this, 'L2Bucket', cfnBucket.ref); // using from*Arn() +const cfnKey = cfnTemplate.getResource('Key') as kms.CfnKey; const key = kms.Key.fromKeyArn(this, 'L2Key', cfnKey.attrArn); // using from*Attributes() +declare const privateCfnSubnet1: ec2.CfnSubnet; +declare const privateCfnSubnet2: ec2.CfnSubnet; +const cfnVpc = cfnTemplate.getResource('Vpc') as ec2.CfnVPC; const vpc = ec2.Vpc.fromVpcAttributes(this, 'L2Vpc', { vpcId: cfnVpc.ref, - availabilityZones: cdk.Fn.getAzs(), + availabilityZones: core.Fn.getAzs(), privateSubnetIds: [privateCfnSubnet1.ref, privateCfnSubnet2.ref], }); ``` @@ -231,71 +237,68 @@ you can also retrieve and mutate all other template elements: * [Parameters](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; - - const param: core.CfnParameter = cfnTemplate.getParameter('MyParameter'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const param: core.CfnParameter = cfnTemplate.getParameter('MyParameter'); - // mutating the parameter - param.default = 'MyDefault'; - ``` + // mutating the parameter + param.default = 'MyDefault'; + ``` * [Conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const condition: core.CfnCondition = cfnTemplate.getCondition('MyCondition'); - const condition: core.CfnCondition = cfnTemplate.getCondition('MyCondition'); - - // mutating the condition - condition.expression = core.Fn.conditionEquals(1, 2); - ``` + // mutating the condition + condition.expression = core.Fn.conditionEquals(1, 2); + ``` * [Mappings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; - - const mapping: core.CfnMapping = cfnTemplate.getMapping('MyMapping'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const mapping: core.CfnMapping = cfnTemplate.getMapping('MyMapping'); - // mutating the mapping - mapping.setValue('my-region', 'AMI', 'ami-04681a1dbd79675a5'); - ``` + // mutating the mapping + mapping.setValue('my-region', 'AMI', 'ami-04681a1dbd79675a5'); + ``` * [Service Catalog template Rules](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html): - ```ts - import * as core from '@aws-cdk/core'; - - const rule: core.CfnRule = cfnTemplate.getRule('MyRule'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const rule: core.CfnRule = cfnTemplate.getRule('MyRule'); - // mutating the rule - rule.addAssertion(core.Fn.conditionContains(['m1.small'], myParameter.value), - 'MyParameter has to be m1.small'); - ``` + // mutating the rule + declare const myParameter: core.CfnParameter; + rule.addAssertion(core.Fn.conditionContains(['m1.small'], myParameter.valueAsString), + 'MyParameter has to be m1.small'); + ``` * [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); - const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); - - // mutating the output - output.value = cfnBucket.attrArn; - ``` + // mutating the output + declare const cfnBucket: s3.CfnBucket; + output.value = cfnBucket.attrArn; + ``` * [Hooks for blue-green deployments](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/blue-green.html): - ```ts - import * as core from '@aws-cdk/core'; - - const hook: core.CfnHook = cfnTemplate.getHook('MyOutput'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const hook: core.CfnHook = cfnTemplate.getHook('MyOutput'); - // mutating the hook - const codeDeployHook = hook as core.CfnCodeDeployBlueGreenHook; - codeDeployHook.serviceRole = myRole.roleArn; - ``` + // mutating the hook + declare const myRole: iam.Role; + const codeDeployHook = hook as core.CfnCodeDeployBlueGreenHook; + codeDeployHook.serviceRole = myRole.roleArn; + ``` ## Parameter replacement @@ -304,7 +307,7 @@ you may want to remove them in favor of build-time values. You can do that using the `parameters` property: ```ts -new inc.CfnInclude(this, 'includeTemplate', { +new cfn_inc.CfnInclude(this, 'includeTemplate', { templateFile: 'path/to/my/template', parameters: { 'MyParam': 'my-value', @@ -350,7 +353,7 @@ You can include both the parent stack, and the nested stack in your CDK application as follows: ```ts -const parentTemplate = new inc.CfnInclude(this, 'ParentStack', { +const parentTemplate = new cfn_inc.CfnInclude(this, 'ParentStack', { templateFile: 'path/to/my-parent-template.json', loadNestedStacks: { 'ChildStack': { @@ -371,6 +374,8 @@ will be modified to point to that asset. The included nested stack can be accessed with the `getNestedStack` method: ```ts +declare const parentTemplate: cfn_inc.CfnInclude; + const includedChildStack = parentTemplate.getNestedStack('ChildStack'); const childStack: core.NestedStack = includedChildStack.stack; const childTemplate: cfn_inc.CfnInclude = includedChildStack.includedTemplate; @@ -380,10 +385,12 @@ Now you can reference resources from `ChildStack`, and modify them like any other included template: ```ts +declare const childTemplate: cfn_inc.CfnInclude; + const cfnBucket = childTemplate.getResource('MyBucket') as s3.CfnBucket; cfnBucket.bucketName = 'my-new-bucket-name'; -const role = new iam.Role(childStack, 'MyRole', { +const role = new iam.Role(this, 'MyRole', { assumedBy: new iam.AccountRootPrincipal(), }); @@ -401,6 +408,7 @@ You can also include the nested stack after the `CfnInclude` object was created, instead of doing it on construction: ```ts +declare const parentTemplate: cfn_inc.CfnInclude; const includedChildStack = parentTemplate.loadNestedStack('ChildTemplate', { templateFile: 'path/to/my-nested-template.json', }); @@ -413,7 +421,9 @@ but more like specialized fragments, implementing a particular pattern or best p If you have templates like that, you can use the `CfnInclude` class to vend them as CDK Constructs: -```ts +```ts nofixture +import { Construct } from 'constructs'; +import * as cfn_inc from '@aws-cdk/cloudformation-include'; import * as path from 'path'; export class MyConstruct extends Construct { diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 28e63ecaed94c..fcaecdf08c0c5 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -195,6 +202,7 @@ "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-opsworks": "0.0.0", "@aws-cdk/aws-opsworkscm": "0.0.0", + "@aws-cdk/aws-panorama": "0.0.0", "@aws-cdk/aws-pinpoint": "0.0.0", "@aws-cdk/aws-pinpointemail": "0.0.0", "@aws-cdk/aws-qldb": "0.0.0", @@ -202,6 +210,7 @@ "@aws-cdk/aws-ram": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-redshift": "0.0.0", + "@aws-cdk/aws-rekognition": "0.0.0", "@aws-cdk/aws-resourcegroups": "0.0.0", "@aws-cdk/aws-robomaker": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", @@ -234,6 +243,7 @@ "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0", + "@aws-cdk/aws-wisdom": "0.0.0", "@aws-cdk/aws-workspaces": "0.0.0", "@aws-cdk/aws-xray": "0.0.0", "@aws-cdk/core": "0.0.0", @@ -370,6 +380,7 @@ "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-opsworks": "0.0.0", "@aws-cdk/aws-opsworkscm": "0.0.0", + "@aws-cdk/aws-panorama": "0.0.0", "@aws-cdk/aws-pinpoint": "0.0.0", "@aws-cdk/aws-pinpointemail": "0.0.0", "@aws-cdk/aws-qldb": "0.0.0", @@ -377,6 +388,7 @@ "@aws-cdk/aws-ram": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-redshift": "0.0.0", + "@aws-cdk/aws-rekognition": "0.0.0", "@aws-cdk/aws-resourcegroups": "0.0.0", "@aws-cdk/aws-robomaker": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", @@ -409,6 +421,7 @@ "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0", + "@aws-cdk/aws-wisdom": "0.0.0", "@aws-cdk/aws-workspaces": "0.0.0", "@aws-cdk/aws-xray": "0.0.0", "@aws-cdk/core": "0.0.0", @@ -419,9 +432,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3", - "ts-jest": "^26.5.6" + "@types/jest": "^27.0.2", + "jest": "^27.3.1", + "ts-jest": "^27.0.7" }, "bundledDependencies": [ "yaml" diff --git a/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture b/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..307736228527e --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture @@ -0,0 +1,18 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as core from '@aws-cdk/core'; +import * as path from 'path'; +import * as cfn_inc from '@aws-cdk/cloudformation-include'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as ec2 from '@aws-cdk/aws-ec2'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index a6d4d2efc5a9b..d6b58901838c2 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -48,7 +48,7 @@ logical application. You can then treat that new unit the same way you used to be able to treat a single stack: by instantiating it multiple times for different instances of your application. -You can define a custom subclass of `Construct`, holding one or more +You can define a custom subclass of `Stage`, holding one or more `Stack`s, to represent a single logical instance of your application. As a final note: `Stack`s are not a unit of reuse. They describe physical @@ -172,6 +172,13 @@ Duration.days(7) // 7 days Duration.parse('PT5M') // 5 minutes ``` +Durations can be added or subtracted together: + +```ts +Duration.minutes(1).plus(Duration.seconds(60)); // 2 minutes +Duration.minutes(5).minus(Duration.seconds(10)); // 290 secondes +``` + ## Size (Digital Information Quantity) To make specification of digital storage quantities unambiguous, a class called @@ -230,6 +237,8 @@ this purpose. use the region and account of the stack you're calling it on: ```ts +declare const stack: Stack; + // Builds "arn::lambda:::function:MyFunction" stack.formatArn({ service: 'lambda', @@ -245,6 +254,8 @@ but in case of a deploy-time value be aware that the result will be another deploy-time value which cannot be inspected in the CDK application. ```ts +declare const stack: Stack; + // Extracts the function name out of an AWS Lambda Function ARN const arnComponents = stack.parseArn(arn, ':'); const functionName = arnComponents.resourceName; @@ -376,7 +387,11 @@ examples ensures that only a single SNS topic is defined: function getOrCreate(scope: Construct): sns.Topic { const stack = Stack.of(scope); const uniqueid = 'GloballyUniqueIdForSingleton'; // For example, a UUID from `uuidgen` - return stack.node.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); + const existing = stack.node.tryFindChild(uniqueid); + if (existing) { + return existing as sns.Topic; + } + return new sns.Topic(stack, uniqueid); } ``` @@ -753,16 +768,19 @@ CloudFormation [mappings][cfn-mappings] are created and queried using the ```ts const regionTable = new CfnMapping(this, 'RegionTable', { mapping: { - regionName: { - 'us-east-1': 'US East (N. Virginia)', - 'us-east-2': 'US East (Ohio)', + 'us-east-1': { + regionName: 'US East (N. Virginia)', + // ... + }, + 'us-east-2': { + regionName: 'US East (Ohio)', // ... }, // ... } }); -regionTable.findInMap('regionName', Aws.REGION); +regionTable.findInMap(Aws.REGION, 'regionName') ``` This will yield the following template: @@ -770,9 +788,10 @@ This will yield the following template: ```yaml Mappings: RegionTable: - regionName: - us-east-1: US East (N. Virginia) - us-east-2: US East (Ohio) + us-east-1: + regionName: US East (N. Virginia) + us-east-2: + regionName: US East (Ohio) ``` Mappings can also be synthesized "lazily"; lazy mappings will only render a "Mappings" @@ -787,24 +806,27 @@ call to `findInMap` will be able to resolve the value during synthesis and simpl ```ts const regionTable = new CfnMapping(this, 'RegionTable', { mapping: { - regionName: { - 'us-east-1': 'US East (N. Virginia)', - 'us-east-2': 'US East (Ohio)', + 'us-east-1': { + regionName: 'US East (N. Virginia)', + }, + 'us-east-2': { + regionName: 'US East (Ohio)', }, }, lazy: true, }); -regionTable.findInMap('regionName', 'us-east-2'); +regionTable.findInMap('us-east-2', 'regionName'); ``` On the other hand, the following code will produce the "Mappings" section shown above, -since the second-level key is an unresolved token. The call to `findInMap` will return a -token that resolves to `{ Fn::FindInMap: [ 'RegionTable', 'regionName', { Ref: AWS::Region -} ] }`. +since the top-level key is an unresolved token. The call to `findInMap` will return a token that resolves to +`{ "Fn::FindInMap": [ "RegionTable", { "Ref": "AWS::Region" }, "regionName" ] }`. ```ts -regionTable.findInMap('regionName', Aws.REGION); +declare const regionTable: CfnMapping; + +regionTable.findInMap(Aws.REGION, 'regionName'); ``` [cfn-mappings]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index 6719995d2b837..09ad1a3f81b79 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -25,6 +25,12 @@ export interface AppProps { /** * The output directory into which to emit synthesized artifacts. * + * You should never need to set this value. By default, the value you pass to + * the CLI's `--output` flag will be used, and if you change it to a different + * directory the CLI will fail to pick up the generated Cloud Assembly. + * + * This property is intended for internal and testing use. + * * @default - If this value is _not_ set, considers the environment variable `CDK_OUTDIR`. * If `CDK_OUTDIR` is not defined, uses a temp directory. */ diff --git a/packages/@aws-cdk/core/lib/arn.ts b/packages/@aws-cdk/core/lib/arn.ts index 343eb553e2cc7..ac10aef173535 100644 --- a/packages/@aws-cdk/core/lib/arn.ts +++ b/packages/@aws-cdk/core/lib/arn.ts @@ -312,7 +312,7 @@ export class Arn { // Apparently we could just parse this right away. Validate that we got the right // resource type (to notify authors of incorrect assumptions right away). - const parsed = Arn.parse(arn, '/', true); + const parsed = Arn.split(arn, ArnFormat.SLASH_RESOURCE_NAME); if (!Token.isUnresolved(parsed.resource) && parsed.resource !== resourceType) { throw new Error(`Expected resource type '${resourceType}' in ARN, got '${parsed.resource}' in '${arn}'`); } diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index 7cd9b9c973f9f..3732cc4fc19b8 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -247,14 +247,14 @@ export interface FileAssetLocation { /** * The HTTP URL of this asset on Amazon S3. * - * @example https://s3-us-east-1.amazonaws.com/mybucket/myobject + * Example value: `https://s3-us-east-1.amazonaws.com/mybucket/myobject` */ readonly httpUrl: string; /** * The S3 URL of this asset on Amazon S3. * - * @example s3://mybucket/myobject + * Example value: `s3://mybucket/myobject` */ readonly s3ObjectUrl: string; diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 1ce4633354511..b472515026eaf 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -2,6 +2,7 @@ import { spawnSync, SpawnSyncOptions } from 'child_process'; import * as crypto from 'crypto'; import { isAbsolute, join } from 'path'; import { FileSystem } from './fs'; +import { quiet, reset } from './private/jsii-deprecated'; /** * Bundling options @@ -16,7 +17,7 @@ export interface BundlingOptions { /** * The entrypoint to run in the Docker container. * - * @example ['/bin/sh', '-c'] + * Example value: `['/bin/sh', '-c']` * * @see https://docs.docker.com/engine/reference/builder/#entrypoint * @@ -27,7 +28,7 @@ export interface BundlingOptions { /** * The command to run in the Docker container. * - * @example ['npm', 'install'] + * Example value: `['npm', 'install']` * * @see https://docs.docker.com/engine/reference/run/ * @@ -295,9 +296,19 @@ export class DockerImage extends BundlingDockerImage { return new DockerImage(image); } - /** @param image The Docker image */ - constructor(public readonly image: string, _imageHash?: string) { + /** The Docker image */ + public readonly image: string; + + constructor(image: string, _imageHash?: string) { + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + super(image, _imageHash); + + reset(deprecated); + this.image = image; } /** @@ -306,14 +317,30 @@ export class DockerImage extends BundlingDockerImage { * @return The overridden image name if set or image hash name in that order */ public toJSON() { - return super.toJSON(); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const json = super.toJSON(); + + reset(deprecated); + return json; } /** * Runs a Docker image */ public run(options: DockerRunOptions = {}) { - return super.run(options); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const result = super.run(options); + + reset(deprecated); + return result; } /** @@ -326,7 +353,15 @@ export class DockerImage extends BundlingDockerImage { * @returns the destination path */ public cp(imagePath: string, outputPath?: string): string { - return super.cp(imagePath, outputPath); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const result = super.cp(imagePath, outputPath); + + reset(deprecated); + return result; } } @@ -447,7 +482,7 @@ export interface DockerBuildOptions { /** * Set platform if server is multi-platform capable. _Requires Docker Engine API v1.38+_. * - * @example 'linux/amd64' + * Example value: `linux/amd64` * * @default - no platform specified */ diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index e296ecce34375..0dfab3d63f16e 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -134,7 +134,7 @@ export class CfnOutput extends CfnElement { */ public get importValue() { // We made _exportName mutable so this will have to be lazy. - return Fn.importValue(Lazy.stringValue({ + return Fn.importValue(Lazy.uncachedString({ produce: (ctx) => { if (Stack.of(ctx.scope) === this.stack) { throw new Error(`'importValue' property of '${this.node.path}' should only be used in a different Stack`); diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index cc5921fb73465..0e55678c2ef71 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -326,7 +326,9 @@ export class ConstructNode { * all components of the tree. * * @deprecated use `node.addr` to obtain a consistent 42 character address for - * this node (see https://github.com/aws/constructs/pull/314) + * this node (see https://github.com/aws/constructs/pull/314). + * Alternatively, to get a CloudFormation-compatible unique identifier, use + * `Names.uniqueId()`. */ public get uniqueId(): string { return this._actualNode.uniqueId; } @@ -343,7 +345,7 @@ export class ConstructNode { * will be excluded from the calculation. In those cases constructs in the * same tree may have the same addreess. * - * @example c83a2846e506bcc5f10682b564084bca2d275709ee + * Example value: `c83a2846e506bcc5f10682b564084bca2d275709ee` */ public get addr(): string { return this._actualNode.addr; } diff --git a/packages/@aws-cdk/core/lib/context-provider.ts b/packages/@aws-cdk/core/lib/context-provider.ts index 526414a9cf790..b8d6c16abac75 100644 --- a/packages/@aws-cdk/core/lib/context-provider.ts +++ b/packages/@aws-cdk/core/lib/context-provider.ts @@ -96,7 +96,7 @@ export class ContextProvider { // if context is missing or an error occurred during context retrieval, // report and return a dummy value. if (value === undefined || providerError !== undefined) { - stack.reportMissingContext({ + stack.reportMissingContextKey({ key, provider: options.provider as cxschema.ContextProvider, props: props as cxschema.ContextQueryProperties, diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts index 06f257fa18ff3..013c1f49b8ab8 100644 --- a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { AssetStaging } from '../asset-staging'; import { FileAssetPackaging } from '../assets'; @@ -36,12 +37,23 @@ export interface CustomResourceProviderProps { * A set of IAM policy statements to include in the inline policy of the * provider's lambda function. * + * **Please note**: these are direct IAM JSON policy blobs, *not* `iam.PolicyStatement` + * objects like you will see in the rest of the CDK. + * * @default - no additional inline policy * * @example - * - * [{ Effect: 'Allow', Action: 's3:PutObject*', Resource: '*' }] - * + * const provider = CustomResourceProvider.getOrCreateProvider(this, 'Custom::MyCustomResourceType', { + * codeDirectory: `${__dirname}/my-handler`, + * runtime: CustomResourceProviderRuntime.NODEJS_12_X, + * policyStatements: [ + * { + * Effect: 'Allow', + * Action: 's3:PutObject*', + * Resource: '*', + * } + * ], + * }); */ readonly policyStatements?: any[]; @@ -144,11 +156,15 @@ export class CustomResourceProvider extends CoreConstruct { * `serviceToken` when defining a custom resource. * * @example - * new CustomResource(this, 'MyCustomResource', { - * // ... - * serviceToken: myProvider.serviceToken, // <--- here - * }) + * declare const myProvider: CustomResourceProvider; * + * new CustomResource(this, 'MyCustomResource', { + * serviceToken: myProvider.serviceToken, + * properties: { + * myPropertyOne: 'one', + * myPropertyTwo: 'two', + * }, + * }); */ public readonly serviceToken: string; @@ -174,9 +190,11 @@ export class CustomResourceProvider extends CoreConstruct { sourcePath: props.codeDirectory, }); - const asset = stack.addFileAsset({ - fileName: staging.relativeStagedPath(stack), - sourceHash: staging.sourceHash, + const assetFileName = staging.relativeStagedPath(stack); + + const asset = stack.synthesizer.addFileAsset({ + fileName: assetFileName, + sourceHash: staging.assetHash, packaging: FileAssetPackaging.ZIP_DIRECTORY, }); @@ -227,6 +245,11 @@ export class CustomResourceProvider extends CoreConstruct { handler.addDependsOn(role); + if (this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + handler.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PATH_KEY, assetFileName); + handler.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY, 'Code'); + } + this.serviceToken = Token.asString(handler.getAtt('Arn')); } diff --git a/packages/@aws-cdk/core/lib/duration.ts b/packages/@aws-cdk/core/lib/duration.ts index 0061e7928e900..5c7bb804b917c 100644 --- a/packages/@aws-cdk/core/lib/duration.ts +++ b/packages/@aws-cdk/core/lib/duration.ts @@ -105,8 +105,17 @@ export class Duration { */ public plus(rhs: Duration): Duration { const targetUnit = finestUnit(this.unit, rhs.unit); - const total = convert(this.amount, this.unit, targetUnit, {}) + convert(rhs.amount, rhs.unit, targetUnit, {}); - return new Duration(total, targetUnit); + const res = convert(this.amount, this.unit, targetUnit, {}) + convert(rhs.amount, rhs.unit, targetUnit, {}); + return new Duration(res, targetUnit); + } + + /** + * Substract two Durations together + */ + public minus(rhs: Duration): Duration { + const targetUnit = finestUnit(this.unit, rhs.unit); + const res = convert(this.amount, this.unit, targetUnit, {}) - convert(rhs.amount, rhs.unit, targetUnit, {}); + return new Duration(res, targetUnit); } /** diff --git a/packages/@aws-cdk/core/lib/fs/fingerprint.ts b/packages/@aws-cdk/core/lib/fs/fingerprint.ts index 4abfbe93c61bc..050240f821601 100644 --- a/packages/@aws-cdk/core/lib/fs/fingerprint.ts +++ b/packages/@aws-cdk/core/lib/fs/fingerprint.ts @@ -53,24 +53,26 @@ export function fingerprint(fileOrDirectory: string, options: FingerprintOptions return hash.digest('hex'); function _processFileOrDirectory(symbolicPath: string, isRootDir: boolean = false, realPath = symbolicPath) { - const relativePath = path.relative(fileOrDirectory, symbolicPath); - if (!isRootDir && ignoreStrategy.ignores(symbolicPath)) { return; } const stat = fs.lstatSync(realPath); + // Use relative path as hash component. Normalize it with forward slashes to ensure + // same hash on Windows and Linux. + const hashComponent = path.relative(fileOrDirectory, symbolicPath).replace(/\\/g, '/'); + if (stat.isSymbolicLink()) { const linkTarget = fs.readlinkSync(realPath); const resolvedLinkTarget = path.resolve(path.dirname(realPath), linkTarget); if (shouldFollow(follow, rootDirectory, resolvedLinkTarget)) { _processFileOrDirectory(symbolicPath, false, resolvedLinkTarget); } else { - _hashField(hash, `link:${relativePath}`, linkTarget); + _hashField(hash, `link:${hashComponent}`, linkTarget); } } else if (stat.isFile()) { - _hashField(hash, `file:${relativePath}`, contentFingerprint(realPath)); + _hashField(hash, `file:${hashComponent}`, contentFingerprint(realPath)); } else if (stat.isDirectory()) { for (const item of fs.readdirSync(realPath).sort()) { _processFileOrDirectory(path.join(symbolicPath, item), false, path.join(realPath, item)); diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index fd8c47f6a0731..39cc59ebd366f 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -1,4 +1,5 @@ import * as crypto from 'crypto'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; import { FileAssetPackaging } from './assets'; import { Fn } from './cfn-fn'; @@ -155,8 +156,8 @@ export class NestedStack extends Stack { * - If this is referenced from the parent stack, it will return a token that parses the name from the stack ID. * - If this is referenced from the context of the nested stack, it will return `{ "Ref": "AWS::StackName" }` * + * Example value: `mystack-mynestedstack-sggfrhxhum7w` * @attribute - * @example mystack-mynestedstack-sggfrhxhum7w */ public get stackName() { return this._contextualStackName; @@ -169,8 +170,8 @@ export class NestedStack extends Stack { * - If this is referenced from the parent stack, it will return `{ "Ref": "LogicalIdOfNestedStackResource" }`. * - If this is referenced from the context of the nested stack, it will return `{ "Ref": "AWS::StackId" }` * + * Example value: `arn:aws:cloudformation:us-east-2:123456789012:stack/mystack-mynestedstack-sggfrhxhum7w/f449b250-b969-11e0-a185-5081d0136786` * @attribute - * @example "arn:aws:cloudformation:us-east-2:123456789012:stack/mystack-mynestedstack-sggfrhxhum7w/f449b250-b969-11e0-a185-5081d0136786" */ public get stackId() { return this._contextualStackId; @@ -207,12 +208,14 @@ export class NestedStack extends Stack { const cfn = JSON.stringify(this._toCloudFormation()); const templateHash = crypto.createHash('sha256').update(cfn).digest('hex'); - const templateLocation = this._parentStack.addFileAsset({ + const templateLocation = this._parentStack.synthesizer.addFileAsset({ packaging: FileAssetPackaging.FILE, sourceHash: templateHash, fileName: this.templateFile, }); + this.addResourceMetadata(this.resource, 'TemplateURL'); + // if bucketName/objectKey are cfn parameters from a stack other than the parent stack, they will // be resolved as cross-stack references like any other (see "multi" tests). this._templateUrl = `https://s3.${this._parentStack.region}.${this._parentStack.urlSuffix}/${templateLocation.bucketName}/${templateLocation.objectKey}`; @@ -230,6 +233,18 @@ export class NestedStack extends Stack { }, }); } + + private addResourceMetadata(resource: CfnResource, resourceProperty: string) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + // tell tools such as SAM CLI that the "TemplateURL" property of this resource + // points to the nested stack template for local emulation + resource.cfnOptions.metadata = resource.cfnOptions.metadata || { }; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.templateFile; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty; + } } /** diff --git a/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts b/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts new file mode 100644 index 0000000000000..73ad8a6e824d6 --- /dev/null +++ b/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts @@ -0,0 +1,13 @@ +export function quiet(): string | undefined { + const deprecated = process.env.JSII_DEPRECATED; + process.env.JSII_DEPRECATED = 'quiet'; + return deprecated; +} + +export function reset(deprecated: string | undefined) { + if (deprecated === undefined) { + delete process.env.JSII_DEPRECATED; + } else { + process.env.JSII_DEPRECATED = deprecated; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/removal-policy.ts b/packages/@aws-cdk/core/lib/removal-policy.ts index d815967fa2bf0..f5249949f99ab 100644 --- a/packages/@aws-cdk/core/lib/removal-policy.ts +++ b/packages/@aws-cdk/core/lib/removal-policy.ts @@ -19,8 +19,10 @@ * as shown in the following example: * * ```ts - * const cfnBucket = bucket.node.findChild('Resource') as cdk.CfnResource; - * cfnBucket.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); + * declare const bucket: s3.Bucket; + * + * const cfnBucket = bucket.node.findChild('Resource') as CfnResource; + * cfnBucket.applyRemovalPolicy(RemovalPolicy.DESTROY); * ``` */ export enum RemovalPolicy { diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index c61e9ba69955e..afcadc6529963 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -1,4 +1,4 @@ -import { ArnComponents } from './arn'; +import { ArnComponents, ArnFormat } from './arn'; import { CfnResource } from './cfn-resource'; import { IConstruct, Construct as CoreConstruct } from './construct-compat'; import { IStringProducer, Lazy } from './lazy'; @@ -145,7 +145,10 @@ export abstract class Resource extends CoreConstruct implements IResource { this.stack = Stack.of(this); - const parsedArn = props.environmentFromArn ? this.stack.parseArn(props.environmentFromArn) : undefined; + const parsedArn = props.environmentFromArn ? + // Since we only want the region and account, NO_RESOURE_NAME is good enough + this.stack.splitArn(props.environmentFromArn, ArnFormat.NO_RESOURCE_NAME) + : undefined; this.env = { account: props.account ?? parsedArn?.account ?? this.stack.account, region: props.region ?? parsedArn?.region ?? this.stack.region, diff --git a/packages/@aws-cdk/core/lib/secret-value.ts b/packages/@aws-cdk/core/lib/secret-value.ts index 2cd1b772f3207..b57b704bd9ed6 100644 --- a/packages/@aws-cdk/core/lib/secret-value.ts +++ b/packages/@aws-cdk/core/lib/secret-value.ts @@ -1,6 +1,7 @@ import { CfnDynamicReference, CfnDynamicReferenceService } from './cfn-dynamic-reference'; import { CfnParameter } from './cfn-parameter'; import { Intrinsic } from './private/intrinsic'; +import { Token } from './token'; /** * Work with secret values in the CDK @@ -39,7 +40,7 @@ export class SecretValue extends Intrinsic { throw new Error('secretId cannot be empty'); } - if (!secretId.startsWith('arn:') && secretId.includes(':')) { + if (!Token.isUnresolved(secretId) && !secretId.startsWith('arn:') && secretId.includes(':')) { throw new Error(`secret id "${secretId}" is not an ARN but contains ":"`); } diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts index 211413df2b5ed..a9b9e0d7acb5d 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts @@ -79,9 +79,9 @@ function collectStackMetadata(stack: Stack) { return; } - if (node.node.metadata.length > 0) { + if (node.node.metadataEntry.length > 0) { // Make the path absolute - output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxschema.MetadataEntry); + output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadataEntry.map(md => stack.resolve(md) as cxschema.MetadataEntry); } for (const child of node.node.children) { diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index 5c4072c3efc5c..d8e1f8818abc4 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -168,11 +168,20 @@ export interface DefaultStackSynthesizerProps { /** * bucketPrefix to use while storing S3 Assets * - * * @default - DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PREFIX */ readonly bucketPrefix?: string; + /** + * A prefix to use while tagging and uploading Docker images to ECR. + * + * This does not add any separators - the source hash will be appended to + * this string directly. + * + * @default - DefaultStackSynthesizer.DEFAULT_DOCKER_ASSET_PREFIX + */ + readonly dockerTagPrefix?: string; + /** * Bootstrap stack version SSM parameter. * @@ -242,6 +251,10 @@ export class DefaultStackSynthesizer extends StackSynthesizer { * Default file asset prefix */ public static readonly DEFAULT_FILE_ASSET_PREFIX = ''; + /** + * Default Docker asset prefix + */ + public static readonly DEFAULT_DOCKER_ASSET_PREFIX = ''; /** * Default bootstrap stack version SSM parameter. @@ -258,6 +271,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { private lookupRoleArn?: string; private qualifier?: string; private bucketPrefix?: string; + private dockerTagPrefix?: string; private bootstrapStackVersionSsmParameter?: string; private readonly files: NonNullable = {}; @@ -319,6 +333,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { this.imageAssetPublishingRoleArn = specialize(this.props.imageAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN); this.lookupRoleArn = specialize(this.props.lookupRoleArn ?? DefaultStackSynthesizer.DEFAULT_LOOKUP_ROLE_ARN); this.bucketPrefix = specialize(this.props.bucketPrefix ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PREFIX); + this.dockerTagPrefix = specialize(this.props.dockerTagPrefix ?? DefaultStackSynthesizer.DEFAULT_DOCKER_ASSET_PREFIX); this.bootstrapStackVersionSsmParameter = replaceAll( this.props.bootstrapStackVersionSsmParameter ?? DefaultStackSynthesizer.DEFAULT_BOOTSTRAP_STACK_VERSION_SSM_PARAMETER, '${Qualifier}', @@ -372,7 +387,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { assertBound(this.repositoryName); validateDockerImageAssetSource(asset); - const imageTag = asset.sourceHash; + const imageTag = this.dockerTagPrefix + asset.sourceHash; // Add to manifest this.dockerImages[asset.sourceHash] = { @@ -600,7 +615,7 @@ function addBootstrapVersionRule(stack: Stack, requiredVersion: number, bootstra const param = new CfnParameter(stack, 'BootstrapVersion', { type: 'AWS::SSM::Parameter::Value', - description: 'Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store.', + description: `Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. ${cxapi.SSMPARAM_NO_INVALIDATE}`, default: bootstrapStackVersionSsmParameter, }); diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts index bf699b271878d..e9c08990f673b 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts @@ -75,7 +75,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { } this.cycle = true; try { - return this.stack.addFileAsset(asset); + return this.stack.synthesizer.addFileAsset(asset); } finally { this.cycle = false; } @@ -91,7 +91,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { } this.cycle = true; try { - return this.stack.addDockerImageAsset(asset); + return this.stack.synthesizer.addDockerImageAsset(asset); } finally { this.cycle = false; } diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index ee80e56334101..6645805519b81 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -280,7 +280,7 @@ export class Stack extends CoreConstruct implements ITaggable { * The name of the CloudFormation template file emitted to the output * directory during synthesis. * - * @example 'MyStack.template.json' + * Example value: `MyStack.template.json` */ public readonly templateFile: string; @@ -711,11 +711,13 @@ export class Stack extends CoreConstruct implements ITaggable { * * Duplicate values are removed when stack is synthesized. * - * @example stack.addTransform('AWS::Serverless-2016-10-31') - * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html - * * @param transform The transform to add + * + * @example + * declare const stack: Stack; + * + * stack.addTransform('AWS::Serverless-2016-10-31') */ public addTransform(transform: string) { if (!this.templateOptions.transforms) { diff --git a/packages/@aws-cdk/core/lib/tag-manager.ts b/packages/@aws-cdk/core/lib/tag-manager.ts index cdc224500ef1b..8f4893d5036f0 100644 --- a/packages/@aws-cdk/core/lib/tag-manager.ts +++ b/packages/@aws-cdk/core/lib/tag-manager.ts @@ -1,5 +1,7 @@ import { TagType } from './cfn-resource'; import { CfnTag } from './cfn-tag'; +import { Lazy } from './lazy'; +import { IResolvable } from './resolvable'; interface Tag { key: string; @@ -172,7 +174,7 @@ class KeyValueFormatter implements ITagFormatter { Value: tag.value, }); }); - return tags; + return tags.length > 0 ? tags : undefined; } } @@ -228,7 +230,32 @@ export interface TagManagerOptions { } /** - * TagManager facilitates a common implementation of tagging for Constructs. + * TagManager facilitates a common implementation of tagging for Constructs + * + * Normally, you do not need to use this class, as the CloudFormation specification + * will indicate which resources are taggable. However, sometimes you will need this + * to make custom resources taggable. Used `tagManager.renderedTags` to obtain a + * value that will resolve to the tags at synthesis time. + * + * @example + * import * as cdk from '@aws-cdk/core'; + * + * class MyConstruct extends cdk.Resource implements cdk.ITaggable { + * public readonly tags = new cdk.TagManager(cdk.TagType.KEY_VALUE, 'Whatever::The::Type'); + * + * constructor(scope: cdk.Construct, id: string) { + * super(scope, id); + * + * new cdk.CfnResource(this, 'Resource', { + * type: 'Whatever::The::Type', + * properties: { + * // ... + * Tags: this.tags.renderedTags, + * }, + * }); + * } + * } + * */ export class TagManager { @@ -247,6 +274,14 @@ export class TagManager { */ public readonly tagPropertyName: string; + /** + * A lazy value that represents the rendered tags at synthesis time + * + * If you need to make a custom construct taggable, use the value of this + * property to pass to the `tags` property of the underlying construct. + */ + public readonly renderedTags: IResolvable; + private readonly tags = new Map(); private readonly priorities = new Map(); private readonly tagFormatter: ITagFormatter; @@ -260,6 +295,8 @@ export class TagManager { this._setTag(...this.tagFormatter.parseTags(tagStructure, this.initialTagPriority)); } this.tagPropertyName = options.tagPropertyName || 'tags'; + + this.renderedTags = Lazy.any({ produce: () => this.renderTags() }); } /** @@ -287,6 +324,11 @@ export class TagManager { /** * Renders tags into the proper format based on TagType + * + * This method will eagerly render the tags currently applied. In + * most cases, you should be using `tagManager.renderedTags` instead, + * which will return a `Lazy` value that will resolve to the correct + * tags at synthesis time. */ public renderTags(): any { return this.tagFormatter.formatTags(this.sortedTags); diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index f2318107d60d3..578fac2dbbff0 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -171,18 +178,18 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", + "@types/aws-lambda": "^8.10.85", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", - "@types/lodash": "^4.14.175", + "@types/jest": "^27.0.2", + "@types/lodash": "^4.14.177", "@types/minimatch": "^3.0.5", "@types/node": "^10.17.60", "@types/sinon": "^9.0.11", - "fast-check": "^2.17.0", - "jest": "^26.6.3", + "fast-check": "^2.19.0", + "jest": "^27.3.1", "lodash": "^4.17.21", "sinon": "^9.2.4", - "ts-mock-imports": "^1.3.7" + "ts-mock-imports": "^1.3.8" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -191,7 +198,7 @@ "@balena/dockerignore": "^1.0.2", "constructs": "^3.3.69", "fs-extra": "^9.1.0", - "ignore": "^5.1.8", + "ignore": "^5.1.9", "minimatch": "^3.0.4" }, "bundledDependencies": [ diff --git a/packages/@aws-cdk/core/rosetta/default.ts-fixture b/packages/@aws-cdk/core/rosetta/default.ts-fixture index 558cc09b1c049..26a25736acb17 100644 --- a/packages/@aws-cdk/core/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/core/rosetta/default.ts-fixture @@ -27,6 +27,7 @@ import { Duration, Fn, IConstruct, + RemovalPolicy, SecretValue, Size, SizeRoundingBehavior, @@ -47,15 +48,27 @@ declare const functionProps: lambda.FunctionProps; declare const isCompleteHandler: lambda.Function; declare const myBucket: s3.IBucket; declare const myFunction: lambda.IFunction; -declare const myProvider: CustomResourceProvider; declare const myTopic: sns.ITopic; declare const onEventHandler: lambda.Function; declare const resourceProps: CfnResourceProps; -declare const stack: Stack; declare class MyStack extends Stack {} declare class YourStack extends Stack {} +class StackThatProvidesABucket extends Stack { + public readonly bucket!: s3.IBucket; +} + +interface StackThatExpectsABucketProps extends StackProps { + readonly bucket: s3.IBucket; +} + +class StackThatExpectsABucket extends Stack { + constructor(scope: Construct, id: string, props: StackThatExpectsABucketProps) { + super(scope, id, props); + } +} + class fixture$construct extends Construct { public constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/core/test/app.test.ts b/packages/@aws-cdk/core/test/app.test.ts index c73538067f9ef..d805d0d0f41fe 100644 --- a/packages/@aws-cdk/core/test/app.test.ts +++ b/packages/@aws-cdk/core/test/app.test.ts @@ -195,7 +195,7 @@ describe('app', () => { constructor(scope: App, id: string, props?: StackProps) { super(scope, id, props); - this.reportMissingContext({ + this.reportMissingContextKey({ key: 'missing-context-key', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { @@ -205,7 +205,7 @@ describe('app', () => { }, ); - this.reportMissingContext({ + this.reportMissingContextKey({ key: 'missing-context-key-2', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { diff --git a/packages/@aws-cdk/core/test/arn.test.ts b/packages/@aws-cdk/core/test/arn.test.ts index f1d1c862a5ca4..28d0ebf22a446 100644 --- a/packages/@aws-cdk/core/test/arn.test.ts +++ b/packages/@aws-cdk/core/test/arn.test.ts @@ -1,3 +1,4 @@ +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Arn, ArnComponents, ArnFormat, Aws, CfnOutput, ScopedAws, Stack, Token } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { evaluateCFN } from './evaluate-cfn'; @@ -53,7 +54,7 @@ describe('arn', () => { }); - test('resourcePathSep can be set to ":" instead of the default "/"', () => { + testDeprecated('resourcePathSep can be set to ":" instead of the default "/"', () => { const stack = new Stack(); const arn = stack.formatArn({ @@ -70,7 +71,7 @@ describe('arn', () => { }); - test('resourcePathSep can be set to "" instead of the default "/"', () => { + testDeprecated('resourcePathSep can be set to "" instead of the default "/"', () => { const stack = new Stack(); const arn = stack.formatArn({ @@ -98,7 +99,7 @@ describe('arn', () => { }); - describe('Arn.parse(s)', () => { + describeDeprecated('Arn.parse(s)', () => { describe('fails', () => { test('if doesn\'t start with "arn:"', () => { @@ -335,7 +336,7 @@ describe('arn', () => { }); - test('parse other fields if only some are tokens', () => { + testDeprecated('parse other fields if only some are tokens', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/core/test/aspect.test.ts b/packages/@aws-cdk/core/test/aspect.test.ts index 0ed634cf5e58c..076dbbc2e7c3c 100644 --- a/packages/@aws-cdk/core/test/aspect.test.ts +++ b/packages/@aws-cdk/core/test/aspect.test.ts @@ -50,10 +50,10 @@ describe('aspect', () => { }, }); app.synth(); - expect(root.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(root.node.metadata[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied'); + expect(root.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(root.node.metadataEntry[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied'); // warning is not added to child construct - expect(child.node.metadata.length).toEqual(0); + expect(child.node.metadataEntry.length).toEqual(0); }); @@ -63,13 +63,13 @@ describe('aspect', () => { const child = new MyConstruct(root, 'ChildConstruct'); Aspects.of(root).add(new MyAspect()); app.synth(); - expect(root.node.metadata[0].type).toEqual('foo'); - expect(root.node.metadata[0].data).toEqual('bar'); - expect(child.node.metadata[0].type).toEqual('foo'); - expect(child.node.metadata[0].data).toEqual('bar'); + expect(root.node.metadataEntry[0].type).toEqual('foo'); + expect(root.node.metadataEntry[0].data).toEqual('bar'); + expect(child.node.metadataEntry[0].type).toEqual('foo'); + expect(child.node.metadataEntry[0].data).toEqual('bar'); // no warning is added - expect(root.node.metadata.length).toEqual(1); - expect(child.node.metadata.length).toEqual(1); + expect(root.node.metadataEntry.length).toEqual(1); + expect(child.node.metadataEntry.length).toEqual(1); }); diff --git a/packages/@aws-cdk/core/test/assets.test.ts b/packages/@aws-cdk/core/test/assets.test.ts index 206362c79e809..936688145073e 100644 --- a/packages/@aws-cdk/core/test/assets.test.ts +++ b/packages/@aws-cdk/core/test/assets.test.ts @@ -8,14 +8,14 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addFileAsset({ + stack.synthesizer.addFileAsset({ fileName: 'file-name', packaging: FileAssetPackaging.ZIP_DIRECTORY, sourceHash: 'source-hash', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -52,7 +52,7 @@ describe('assets', () => { const stack = new Stack(); // WHEN - const assetLocation = stack.addFileAsset({ + const assetLocation = stack.synthesizer.addFileAsset({ fileName: 'file-name', packaging: FileAssetPackaging.ZIP_DIRECTORY, sourceHash: 'source-hash', @@ -75,14 +75,13 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', - repositoryName: 'repository-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -91,7 +90,6 @@ describe('assets', () => { expect(data.packaging).toEqual('container-image'); expect(data.path).toEqual('directory-name'); expect(data.sourceHash).toEqual('source-hash'); - expect(data.repositoryName).toEqual('repository-name'); expect(data.imageTag).toEqual('source-hash'); } @@ -104,13 +102,13 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -119,7 +117,6 @@ describe('assets', () => { expect(data.packaging).toEqual('container-image'); expect(data.path).toEqual('directory-name'); expect(data.sourceHash).toEqual('source-hash'); - expect(data.repositoryName).toEqual('aws-cdk/assets'); expect(data.imageTag).toEqual('source-hash'); } @@ -133,13 +130,13 @@ describe('assets', () => { stack.node.setContext('assets-ecr-repository-name', 'my-custom-repo-name'); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index a22968f4d17a0..5030603bb7c41 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -2,7 +2,7 @@ import * as child_process from 'child_process'; import * as crypto from 'crypto'; import * as path from 'path'; import * as sinon from 'sinon'; -import { BundlingDockerImage, DockerImage, FileSystem } from '../lib'; +import { DockerImage, FileSystem } from '../lib'; describe('bundling', () => { afterEach(() => { @@ -21,7 +21,7 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); image.run({ command: ['cool', 'command'], environment: { @@ -136,7 +136,7 @@ describe('bundling', () => { error: new Error('UnknownError'), }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(() => image.run()).toThrow(/UnknownError/); }); @@ -151,13 +151,13 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(() => image.run()).toThrow(/\[Status -1\]/); }); test('BundlerDockerImage json is the bundler image name by default', () => { - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(image.toJSON()).toEqual('alpine'); @@ -199,7 +199,7 @@ describe('bundling', () => { }); const imagePath = path.join(__dirname, 'fs/fixtures/test1'); - BundlingDockerImage.fromAsset(imagePath, { + DockerImage.fromAsset(imagePath, { file: 'my-dockerfile', }); @@ -221,7 +221,7 @@ describe('bundling', () => { }); const imagePath = path.join(__dirname, 'fs/fixtures/test1'); - const image = BundlingDockerImage.fromAsset(imagePath, { + const image = DockerImage.fromAsset(imagePath, { file: 'my-dockerfile', }); expect(image).toBeDefined(); @@ -240,7 +240,7 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); image.run({ entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], command: ['cool', 'command'], @@ -281,7 +281,7 @@ describe('bundling', () => { }); // WHEN - BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + DockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); // THEN expect(spawnSyncStub.calledWith(sinon.match.any, ['create', 'alpine'], sinon.match.any)).toEqual(true); @@ -315,7 +315,7 @@ describe('bundling', () => { // WHEN expect(() => { - BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + DockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); }).toThrow(/Failed.*copy/i); // THEN diff --git a/packages/@aws-cdk/core/test/cloudformation-json.test.ts b/packages/@aws-cdk/core/test/cloudformation-json.test.ts index 977795fc35d9d..204019f128a68 100644 --- a/packages/@aws-cdk/core/test/cloudformation-json.test.ts +++ b/packages/@aws-cdk/core/test/cloudformation-json.test.ts @@ -79,11 +79,11 @@ describe('tokens that return literals', () => { test('String-encoded lazies do not have quotes applied if they return objects', () => { // This is unfortunately crazy behavior, but we have some clients already taking a - // dependency on the fact that `Lazy.stringValue({ produce: () => [...some list...] })` + // dependency on the fact that `Lazy.string({ produce: () => [...some list...] })` // does not apply quotes but just renders the list. // GIVEN - const someList = Lazy.stringValue({ produce: () => [1, 2, 3] as any }); + const someList = Lazy.string({ produce: () => [1, 2, 3] as any }); // WHEN expect(evaluateCFN(stack.resolve(stack.toJsonString({ someList })))).toEqual('{"someList":[1,2,3]}'); @@ -137,7 +137,7 @@ describe('tokens that return literals', () => { test('Doubly nested strings evaluate correctly in JSON context', () => { // WHEN - const fidoSays = Lazy.stringValue({ produce: () => 'woof' }); + const fidoSays = Lazy.string({ produce: () => 'woof' }); // WHEN const resolved = stack.resolve(stack.toJsonString({ @@ -150,7 +150,7 @@ describe('tokens that return literals', () => { test('Quoted strings in embedded JSON context are escaped', () => { // GIVEN - const fidoSays = Lazy.stringValue({ produce: () => '"woof"' }); + const fidoSays = Lazy.string({ produce: () => '"woof"' }); // WHEN const resolved = stack.resolve(stack.toJsonString({ diff --git a/packages/@aws-cdk/core/test/construct.test.ts b/packages/@aws-cdk/core/test/construct.test.ts index e8b7c5f00435d..b61d29d4763e1 100644 --- a/packages/@aws-cdk/core/test/construct.test.ts +++ b/packages/@aws-cdk/core/test/construct.test.ts @@ -1,3 +1,4 @@ +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App as Root, Aws, Construct, ConstructNode, ConstructOrder, IConstruct, Lazy, ValidationError } from '../lib'; import { Annotations } from '../lib/annotations'; @@ -73,7 +74,7 @@ describe('construct', () => { }); - test('construct.uniqueId returns a tree-unique alphanumeric id of this construct', () => { + testDeprecated('construct.uniqueId returns a tree-unique alphanumeric id of this construct', () => { const root = new Root(); const child1 = new Construct(root, 'This is the first child'); @@ -88,7 +89,7 @@ describe('construct', () => { }); - test('cannot calculate uniqueId if the construct path is ["Default"]', () => { + testDeprecated('cannot calculate uniqueId if the construct path is ["Default"]', () => { const root = new Root(); const c = new Construct(root, 'Default'); expect(() => c.node.uniqueId).toThrow(/Unable to calculate a unique id for an empty set of components/); @@ -250,18 +251,18 @@ describe('construct', () => { const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); - expect(con.node.metadata).toEqual([]); + expect(con.node.metadataEntry).toEqual([]); con.node.addMetadata('key', 'value'); con.node.addMetadata('number', 103); con.node.addMetadata('array', [123, 456]); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual('key'); - expect(con.node.metadata[0].data).toEqual('value'); - expect(con.node.metadata[1].data).toEqual(103); - expect(con.node.metadata[2].data).toEqual([123, 456]); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace[1].indexOf('FIND_ME')).toEqual(-1); + expect(con.node.metadataEntry[0].type).toEqual('key'); + expect(con.node.metadataEntry[0].data).toEqual('value'); + expect(con.node.metadataEntry[1].data).toEqual(103); + expect(con.node.metadataEntry[2].data).toEqual([123, 456]); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace[1].indexOf('FIND_ME')).toEqual(-1); }); @@ -274,7 +275,7 @@ describe('construct', () => { con.node.addMetadata('False', false); con.node.addMetadata('Empty', ''); - const exists = (key: string) => con.node.metadata.find(x => x.type === key); + const exists = (key: string) => con.node.metadataEntry.find(x => x.type === key); expect(exists('Null')).toBeUndefined(); expect(exists('Undefined')).toBeUndefined(); @@ -291,9 +292,9 @@ describe('construct', () => { Annotations.of(con).addWarning('This construct is deprecated, use the other one instead'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(con.node.metadata[0].data).toEqual('This construct is deprecated, use the other one instead'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(con.node.metadataEntry[0].data).toEqual('This construct is deprecated, use the other one instead'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); @@ -304,9 +305,9 @@ describe('construct', () => { Annotations.of(con).addError('Stop!'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.ERROR); - expect(con.node.metadata[0].data).toEqual('Stop!'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.ERROR); + expect(con.node.metadataEntry[0].data).toEqual('Stop!'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); @@ -317,9 +318,9 @@ describe('construct', () => { Annotations.of(con).addInfo('Hey there, how do you do?'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.INFO); - expect(con.node.metadata[0].data).toEqual('Hey there, how do you do?'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.INFO); + expect(con.node.metadataEntry[0].data).toEqual('Hey there, how do you do?'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); diff --git a/packages/@aws-cdk/core/test/context.test.ts b/packages/@aws-cdk/core/test/context.test.ts index b8e0e85c169a4..b052e74e8f1b7 100644 --- a/packages/@aws-cdk/core/test/context.test.ts +++ b/packages/@aws-cdk/core/test/context.test.ts @@ -161,7 +161,7 @@ describe('context', () => { }); // THEN - const error = construct.node.metadata.find(m => m.type === 'aws:cdk:error'); + const error = construct.node.metadataEntry.find(m => m.type === 'aws:cdk:error'); expect(error && error.data).toEqual('I had a boo-boo'); diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts index c8d9082447f54..ecda2d2741c5d 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import { App, AssetStaging, CustomResourceProvider, CustomResourceProviderRuntime, DockerImageAssetLocation, DockerImageAssetSource, Duration, FileAssetLocation, FileAssetSource, ISynthesisSession, Size, Stack } from '../../lib'; import { toCloudFormation } from '../util'; @@ -23,7 +24,7 @@ describe('custom resource provider', () => { // The asset hash constantly changes, so in order to not have to chase it, just look // it up from the output. const staging = stack.node.tryFindChild('Custom:MyResourceTypeCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; - const assetHash = staging.sourceHash; + const assetHash = staging.assetHash; const paramNames = Object.keys(cfn.Parameters); const bucketParam = paramNames[0]; const keyParam = paramNames[1]; @@ -122,6 +123,28 @@ describe('custom resource provider', () => { }); + test('asset metadata added to custom resource that contains code definition', () => { + // GIVEN + const stack = new Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + + // WHEN + CustomResourceProvider.getOrCreate(stack, 'Custom:MyResourceType', { + codeDirectory: TEST_HANDLER, + runtime: CustomResourceProviderRuntime.NODEJS_12_X, + }); + + // Then + const lambda = toCloudFormation(stack).Resources.CustomMyResourceTypeCustomResourceProviderHandler29FBDD2A; + expect(lambda).toHaveProperty('Metadata'); + expect(lambda.Metadata).toEqual({ + 'aws:asset:path': `${__dirname}/mock-provider`, + 'aws:asset:property': 'Code', + }); + + }); + test('custom resource provided creates asset in new-style synthesis with relative path', () => { // GIVEN diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/nodejs-entrypoint.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/nodejs-entrypoint.test.ts index 98b2ee9d41924..888c38da918cb 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/nodejs-entrypoint.test.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/nodejs-entrypoint.test.ts @@ -121,7 +121,7 @@ describe('nodejs entrypoint', () => { }); - test('DELETE after CREATE is ignored with success', async (done) => { + test('DELETE after CREATE is ignored with success', async () => { // GIVEN const event = makeEvent({ RequestType: 'Delete', @@ -130,7 +130,7 @@ describe('nodejs entrypoint', () => { // WHEN const response = await invokeHandler(event, async _ => { - done.fail('handler should not be called'); + throw new Error('handler should not be called'); }); // THEN @@ -143,8 +143,6 @@ describe('nodejs entrypoint', () => { LogicalResourceId: '', }); - done(); - }); }); diff --git a/packages/@aws-cdk/core/test/duration.test.ts b/packages/@aws-cdk/core/test/duration.test.ts index 68da12881d3ba..c22de1e808ee9 100644 --- a/packages/@aws-cdk/core/test/duration.test.ts +++ b/packages/@aws-cdk/core/test/duration.test.ts @@ -1,3 +1,4 @@ +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Lazy, Stack, Token } from '../lib'; describe('duration', () => { @@ -76,7 +77,7 @@ describe('duration', () => { }); - test('toISOString', () => { + testDeprecated('toISOString', () => { expect(Duration.millis(0).toISOString()).toEqual('PT0S'); expect(Duration.seconds(0).toISOString()).toEqual('PT0S'); expect(Duration.minutes(0).toISOString()).toEqual('PT0S'); @@ -170,8 +171,12 @@ describe('duration', () => { expect(Duration.minutes(1).plus(Duration.seconds(30)).toSeconds()).toEqual(Duration.seconds(90).toSeconds()); expect(Duration.minutes(1).plus(Duration.seconds(30)).toMinutes({ integral: false })) .toEqual(Duration.seconds(90).toMinutes({ integral: false })); + }); - + test('subtract two durations', () => { + expect(Duration.minutes(1).minus(Duration.seconds(30)).toSeconds()).toEqual(Duration.seconds(30).toSeconds()); + expect(Duration.minutes(1).minus(Duration.seconds(30)).toMinutes({ integral: false })) + .toEqual(Duration.seconds(30).toMinutes({ integral: false })); }); test('get unit label from duration', () => { diff --git a/packages/@aws-cdk/core/test/fs/fs-fingerprint.test.ts b/packages/@aws-cdk/core/test/fs/fs-fingerprint.test.ts index 8187778de5453..f1d22f891a197 100644 --- a/packages/@aws-cdk/core/test/fs/fs-fingerprint.test.ts +++ b/packages/@aws-cdk/core/test/fs/fs-fingerprint.test.ts @@ -178,4 +178,22 @@ describe('fs fingerprint', () => { }); }); + + test('normalizes relative path', () => { + // Simulate a Windows path.relative() + const originalPathRelative = path.relative; + const pathRelativeSpy = jest.spyOn(path, 'relative').mockImplementation((from: string, to: string): string => { + return originalPathRelative(from, to).replace(/\//g, '\\'); + }); + + const hash1 = FileSystem.fingerprint(path.join(__dirname, 'fixtures', 'test1')); + + // Restore Linux behavior + pathRelativeSpy.mockRestore(); + + const hash2 = FileSystem.fingerprint(path.join(__dirname, 'fixtures', 'test1')); + + // Relative paths are normalized + expect(hash1).toEqual(hash2); + }); }); diff --git a/packages/@aws-cdk/core/test/include.test.ts b/packages/@aws-cdk/core/test/include.test.ts index 3973107536bf6..235bcba228bc7 100644 --- a/packages/@aws-cdk/core/test/include.test.ts +++ b/packages/@aws-cdk/core/test/include.test.ts @@ -1,7 +1,8 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnInclude, CfnOutput, CfnParameter, CfnResource, Stack } from '../lib'; import { toCloudFormation } from './util'; -describe('include', () => { +describeDeprecated('include', () => { test('the Include construct can be used to embed an existing template as-is into a stack', () => { const stack = new Stack(); diff --git a/packages/@aws-cdk/core/test/private/tree-metadata.test.ts b/packages/@aws-cdk/core/test/private/tree-metadata.test.ts index 752d6f3fc5f42..1e6f7d9739133 100644 --- a/packages/@aws-cdk/core/test/private/tree-metadata.test.ts +++ b/packages/@aws-cdk/core/test/private/tree-metadata.test.ts @@ -337,7 +337,7 @@ describe('tree metadata', () => { const treenode = app.node.findChild('Tree'); - const warn = treenode.node.metadata.find((md) => { + const warn = treenode.node.metadataEntry.find((md) => { return md.type === cxschema.ArtifactMetadataEntryType.WARN && /Forcing an inspect error/.test(md.data as string) && /mycfnresource/.test(md.data as string); diff --git a/packages/@aws-cdk/core/test/secret-value.test.ts b/packages/@aws-cdk/core/test/secret-value.test.ts index a32702b98771d..2efcdffbe808d 100644 --- a/packages/@aws-cdk/core/test/secret-value.test.ts +++ b/packages/@aws-cdk/core/test/secret-value.test.ts @@ -1,4 +1,4 @@ -import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, SecretValue, Stack } from '../lib'; +import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, SecretValue, Stack, Token } from '../lib'; describe('secret value', () => { test('plainText', () => { @@ -28,6 +28,30 @@ describe('secret value', () => { }); + test('secretsManager with secret-id from token', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.secretsManager(Token.asString({ Ref: 'secret-id' }), { + jsonField: 'json-key', + versionStage: 'version-stage', + }); + + // THEN + expect(stack.resolve(v)).toEqual({ + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { Ref: 'secret-id' }, + ':SecretString:json-key:version-stage:}}', + ], + ], + }); + + }); + test('secretsManager with defaults', () => { // GIVEN const stack = new Stack(); @@ -40,6 +64,27 @@ describe('secret value', () => { }); + test('secretsManager with defaults, secret-id from token', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.secretsManager(Token.asString({ Ref: 'secret-id' })); + + // THEN + expect(stack.resolve(v)).toEqual({ + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { Ref: 'secret-id' }, + ':SecretString:::}}', + ], + ], + }); + + }); + test('secretsManager with an empty ID', () => { expect(() => SecretValue.secretsManager('')).toThrow(/secretId cannot be empty/); diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 1528e91205f80..4ced5a1e9afeb 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -70,6 +70,7 @@ describe('new style synthesis', () => { const template = app.synth().getStackByName('Stack').template; expect(template?.Parameters?.BootstrapVersion?.Type).toEqual('AWS::SSM::Parameter::Value'); expect(template?.Parameters?.BootstrapVersion?.Default).toEqual('/cdk-bootstrap/hnb659fds/version'); + expect(template?.Parameters?.BootstrapVersion?.Description).toContain(cxapi.SSMPARAM_NO_INVALIDATE); const assertions = template?.Rules?.CheckBootstrapVersion?.Assertions ?? []; expect(assertions.length).toEqual(1); @@ -286,7 +287,7 @@ describe('new style synthesis', () => { // THEN const asm = myapp.synth(); - const stackArtifact = asm.getStack(mystack.stackName); + const stackArtifact = asm.getStackByName(mystack.stackName); expect(stackArtifact.assumeRoleExternalId).toEqual('deploy-external-id'); @@ -336,6 +337,30 @@ describe('new style synthesis', () => { }); + test('synthesis with dockerPrefix', () => { + // GIVEN + const myapp = new App(); + + // WHEN + const mystack = new Stack(myapp, 'mystack-dockerPrefix', { + synthesizer: new DefaultStackSynthesizer({ + dockerTagPrefix: 'test-prefix-', + }), + }); + + mystack.synthesizer.addDockerImageAsset({ + directoryName: 'some-folder', + sourceHash: 'docker-asset-hash', + }); + + const asm = myapp.synth(); + + // THEN + const manifest = readAssetManifest(getAssetManifest(asm)); + const imageTag = manifest.dockerImages?.['docker-asset-hash']?.destinations?.['current_account-current_region'].imageTag; + expect(imageTag).toEqual('test-prefix-docker-asset-hash'); + }); + test('cannot use same synthesizer for multiple stacks', () => { // GIVEN const synthesizer = new DefaultStackSynthesizer(); diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index 7c67f545d4b87..160ff09278b50 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -1,5 +1,5 @@ +import { testDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { App, CfnCondition, CfnInclude, CfnOutput, CfnParameter, CfnResource, Construct, Lazy, ScopedAws, Stack, validateString, @@ -241,7 +241,7 @@ describe('stack', () => { }); - test('Include should support non-hash top-level template elements like "Description"', () => { + testDeprecated('Include should support non-hash top-level template elements like "Description"', () => { const stack = new Stack(); const template = { @@ -661,6 +661,29 @@ describe('stack', () => { })); }); + test('asset metadata added to NestedStack resource that contains asset path and property', () => { + const app = new App(); + + // WHEN + const parentStack = new Stack(app, 'parent'); + parentStack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + const childStack = new NestedStack(parentStack, 'child'); + new CfnResource(childStack, 'ChildResource', { type: 'Resource::Child' }); + + const assembly = app.synth(); + expect(assembly.getStackByName(parentStack.stackName).template).toEqual(expect.objectContaining({ + Resources: { + childNestedStackchildNestedStackResource7408D03F: expect.objectContaining({ + Metadata: { + 'aws:asset:path': 'parentchild13F9359B.nested.template.json', + 'aws:asset:property': 'TemplateURL', + }, + }), + }, + })); + + }); + test('cross-stack reference (substack references parent stack)', () => { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/core/test/stage.test.ts b/packages/@aws-cdk/core/test/stage.test.ts index 4178060822d68..6c7f27c8cfec4 100644 --- a/packages/@aws-cdk/core/test/stage.test.ts +++ b/packages/@aws-cdk/core/test/stage.test.ts @@ -321,7 +321,7 @@ test('missing context in Stages is propagated up to root assembly', () => { new CfnResource(stack, 'Resource', { type: 'Something' }); // WHEN - stack.reportMissingContext({ + stack.reportMissingContextKey({ key: 'missing-context-key', provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { diff --git a/packages/@aws-cdk/core/test/staging.test.ts b/packages/@aws-cdk/core/test/staging.test.ts index f9313f5095d2d..9c2c0a0f111b1 100644 --- a/packages/@aws-cdk/core/test/staging.test.ts +++ b/packages/@aws-cdk/core/test/staging.test.ts @@ -1,10 +1,11 @@ import * as os from 'os'; import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { FileAssetPackaging } from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; -import { App, AssetHashType, AssetStaging, BundlingDockerImage, BundlingOptions, BundlingOutput, FileSystem, Stack, Stage } from '../lib'; +import { App, AssetHashType, AssetStaging, DockerImage, BundlingOptions, BundlingOutput, FileSystem, Stack, Stage } from '../lib'; const STUB_INPUT_FILE = '/tmp/docker-stub.input'; const STUB_INPUT_CONCAT_FILE = '/tmp/docker-stub.input.concat'; @@ -48,9 +49,9 @@ describe('staging', () => { // WHEN const staging = new AssetStaging(stack, 's1', { sourcePath }); - expect(staging.sourceHash).toEqual(FIXTURE_TEST1_HASH); + expect(staging.assetHash).toEqual(FIXTURE_TEST1_HASH); expect(staging.sourcePath).toEqual(sourcePath); - expect(path.basename(staging.stagedPath)).toEqual(`asset.${FIXTURE_TEST1_HASH}`); + expect(path.basename(staging.absoluteStagedPath)).toEqual(`asset.${FIXTURE_TEST1_HASH}`); expect(path.basename(staging.relativeStagedPath(stack))).toEqual(`asset.${FIXTURE_TEST1_HASH}`); expect(staging.packaging).toEqual(FileAssetPackaging.ZIP_DIRECTORY); expect(staging.isArchive).toEqual(true); @@ -160,9 +161,9 @@ describe('staging', () => { // WHEN const staging = new AssetStaging(stack, 's1', { sourcePath }); - expect(staging.sourceHash).toEqual(FIXTURE_TEST1_HASH); + expect(staging.assetHash).toEqual(FIXTURE_TEST1_HASH); expect(staging.sourcePath).toEqual(sourcePath); - expect(staging.stagedPath).toEqual(sourcePath); + expect(staging.absoluteStagedPath).toEqual(sourcePath); expect(staging.relativeStagedPath(stack)).toEqual(sourcePath); }); @@ -225,9 +226,9 @@ describe('staging', () => { const withExtra = new AssetStaging(stack, 'withExtra', { sourcePath: directory, extraHash: 'boom' }); // THEN - expect(withoutExtra.sourceHash).not.toEqual(withExtra.sourceHash); - expect(withoutExtra.sourceHash).toEqual(FIXTURE_TEST1_HASH); - expect(withExtra.sourceHash).toEqual('c95c915a5722bb9019e2c725d11868e5a619b55f36172f76bcbcaa8bb2d10c5f'); + expect(withoutExtra.assetHash).not.toEqual(withExtra.assetHash); + expect(withoutExtra.assetHash).toEqual(FIXTURE_TEST1_HASH); + expect(withExtra.assetHash).toEqual('c95c915a5722bb9019e2c725d11868e5a619b55f36172f76bcbcaa8bb2d10c5f'); }); @@ -242,7 +243,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -278,7 +279,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -294,7 +295,7 @@ describe('staging', () => { 'tree.json', ]); - expect(asset.sourceHash).toEqual('b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4'); + expect(asset.assetHash).toEqual('b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4'); expect(asset.sourcePath).toEqual(directory); const resolvedStagePath = asset.relativeStagedPath(stack); @@ -315,7 +316,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -323,7 +324,7 @@ describe('staging', () => { new AssetStaging(stack, 'AssetDuplicate', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -360,7 +361,7 @@ describe('staging', () => { sourcePath: directory, assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -370,7 +371,7 @@ describe('staging', () => { assetHashType: AssetHashType.OUTPUT, bundling: { // Same bundling but with keys ordered differently command: [DockerStubCommand.SUCCESS], - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), }, }); @@ -408,7 +409,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -416,7 +417,7 @@ describe('staging', () => { new AssetStaging(stack, 'AssetWithDifferentBundlingOptions', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], environment: { UNIQUE_ENV_VAR: 'SOMEVALUE', @@ -459,9 +460,9 @@ describe('staging', () => { // WHEN new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -494,7 +495,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.FAIL], }, })).toThrow(/Failed.*bundl.*asset.*-error/); @@ -523,7 +524,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -540,7 +541,7 @@ describe('staging', () => { new AssetStaging(stack2, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -576,7 +577,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS_NO_OUTPUT], }, })).toThrow(/Bundling did not produce any output/); @@ -588,7 +589,7 @@ describe('staging', () => { }); - test('bundling with BUNDLE asset hash type', () => { + testDeprecated('bundling with BUNDLE asset hash type', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'stack'); @@ -598,7 +599,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHashType: AssetHashType.BUNDLE, @@ -625,7 +626,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], securityOpt: 'no-new-privileges', }, @@ -652,7 +653,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHashType: AssetHashType.OUTPUT, @@ -693,17 +694,17 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHash: 'my-custom-hash', - assetHashType: AssetHashType.BUNDLE, - })).toThrow(/Cannot specify `bundle` for `assetHashType`/); + assetHashType: AssetHashType.OUTPUT, + })).toThrow(/Cannot specify `output` for `assetHashType`/); }); - test('throws with BUNDLE hash type and no bundling', () => { + testDeprecated('throws with BUNDLE hash type and no bundling', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'stack'); @@ -761,7 +762,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('this-is-an-invalid-docker-image'), + image: DockerImage.fromRegistry('this-is-an-invalid-docker-image'), command: [DockerStubCommand.FAIL], }, })).toThrow(/Failed to bundle asset stack\/Asset/); @@ -785,7 +786,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], local: { tryBundle(outputDir: string, options: BundlingOptions): boolean { @@ -820,7 +821,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], local: { tryBundle(_bundleDir: string): boolean { @@ -846,9 +847,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -872,9 +873,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -898,9 +899,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -924,7 +925,7 @@ describe('staging', () => { const staging = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], }, }); @@ -967,7 +968,7 @@ describe('staging', () => { const staging1 = new AssetStaging(stack1, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.ARCHIVED, }, @@ -981,7 +982,7 @@ describe('staging', () => { const staging2 = new AssetStaging(stack2, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.ARCHIVED, }, @@ -1006,7 +1007,7 @@ describe('staging', () => { const staging = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.NOT_ARCHIVED, }, @@ -1037,7 +1038,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.MULTIPLE_FILES], outputType: BundlingOutput.ARCHIVED, }, diff --git a/packages/@aws-cdk/core/test/synthesis.test.ts b/packages/@aws-cdk/core/test/synthesis.test.ts index 42dbb2f3fbc4f..496fd76fdbd91 100644 --- a/packages/@aws-cdk/core/test/synthesis.test.ts +++ b/packages/@aws-cdk/core/test/synthesis.test.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '../lib'; @@ -174,7 +175,7 @@ describe('synthesis', () => { }); - test('it should be possible to synthesize without an app', () => { + testDeprecated('it should be possible to synthesize without an app', () => { const calls = new Array(); class SynthesizeMe extends cdk.Construct { diff --git a/packages/@aws-cdk/core/test/tokens.test.ts b/packages/@aws-cdk/core/test/tokens.test.ts index 1aca7312357fa..48e07c1fc720f 100644 --- a/packages/@aws-cdk/core/test/tokens.test.ts +++ b/packages/@aws-cdk/core/test/tokens.test.ts @@ -671,7 +671,7 @@ describe('tokens', () => { test('creation stack is omitted without CDK_DEBUG=true', () => { function showMeInTheStackTrace() { - return Lazy.stringValue({ produce: () => { throw new Error('fooError'); } }); + return Lazy.string({ produce: () => { throw new Error('fooError'); } }); } const previousValue = process.env.CDK_DEBUG; diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index 8583e04bc087f..90892fb7906c7 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -301,8 +301,8 @@ This module includes a few examples for custom resource implementations: Provisions an object in an S3 bucket with textual contents. See the source code for the -[construct](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts) and -[handler](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py). +[construct](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts) and +[handler](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts). The following example will create the file `folder/file1.txt` inside `myBucket` with the contents `hello!`. diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index 68156b6412958..e88b441637cfc 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -167,7 +167,7 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent const params = { RoleArn: call.assumedRoleArn, - RoleSessionName: `${physicalResourceId}-${timestamp}`, + RoleSessionName: `${timestamp}-${physicalResourceId}`.substring(0, 64), }; AWS.config.credentials = new AWS.ChainableTemporaryCredentials({ @@ -176,6 +176,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent } + if (!Object.prototype.hasOwnProperty.call(AWS, call.service)) { + throw Error(`Service ${call.service} does not exist in AWS SDK version ${AWS.VERSION}.`); + } const awsService = new (AWS as any)[call.service]({ apiVersion: call.apiVersion, region: call.region, diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index d8377c231a06b..b41dc9d64ceed 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -80,14 +80,14 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/aws-lambda": "^8.10.83", + "@types/aws-lambda": "^8.10.85", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", "fs-extra": "^9.1.0", - "nock": "^13.1.3", + "nock": "^13.2.1", "sinon": "^9.2.4" }, "dependencies": { diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts index 8a4786a138468..aa8a9589d1fdb 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts @@ -467,3 +467,36 @@ test('installs the latest SDK', async () => { // clean up aws-sdk install await fs.remove(tmpPath); }); + +test('invalid service name throws explicit error', async () => { + const publishFake = sinon.fake.resolves({}); + + AWS.mock('SNS', 'publish', publishFake); + + const event: AWSLambda.CloudFormationCustomResourceCreateEvent = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + Create: JSON.stringify({ + service: 'thisisnotarealservice', + action: 'publish', + parameters: { + Message: 'message', + TopicArn: 'topic', + }, + physicalResourceId: PhysicalResourceId.of('id'), + } as AwsSdkCall), + }, + }; + + const request = createRequest(body => + body.Status === 'FAILED' && + body.Reason!.startsWith('Service thisisnotarealservice does not exist'), + ); + + await handler(event, {} as AWSLambda.Context); + + expect(request.isDone()).toBeTruthy(); +}); + diff --git a/packages/@aws-cdk/cx-api/lib/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts index c15dbcd82776f..0b3eaa52cefb5 100644 --- a/packages/@aws-cdk/cx-api/lib/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/assets.ts @@ -10,6 +10,9 @@ export const ASSET_RESOURCE_METADATA_ENABLED_CONTEXT = 'aws:cdk:enable-asset-met * to resources. */ export const ASSET_RESOURCE_METADATA_PATH_KEY = 'aws:asset:path'; +export const ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY = 'aws:asset:dockerfile-path'; +export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY = 'aws:asset:docker-build-args'; +export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY = 'aws:asset:docker-build-target'; export const ASSET_RESOURCE_METADATA_PROPERTY_KEY = 'aws:asset:property'; /** diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 33d83e74ea45c..9b179e9a71b5f 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -31,4 +31,13 @@ export const CLI_VERSION_ENV = 'CDK_CLI_VERSION'; /** * If a context value is an object with this key, it indicates an error */ -export const PROVIDER_ERROR_KEY = '$providerError'; \ No newline at end of file +export const PROVIDER_ERROR_KEY = '$providerError'; + + +/** + * This SSM parameter does not invalidate the template + * + * If this string occurs in the description of an SSM parameter, the CLI + * will not assume that the stack must always be redeployed. + */ +export const SSMPARAM_NO_INVALIDATE = '[cdk:skip]'; \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index 554c41929651d..37c9aace0ce86 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -175,9 +175,9 @@ export const CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021 = '@aws-cdk/aws-cl */ export const FUTURE_FLAGS: { [key: string]: any } = { [APIGATEWAY_USAGEPLANKEY_ORDERINSENSITIVE_ID]: true, - [ENABLE_STACK_NAME_DUPLICATES_CONTEXT]: 'true', - [ENABLE_DIFF_NO_FAIL_CONTEXT]: 'true', - [STACK_RELATIVE_EXPORTS_CONTEXT]: 'true', + [ENABLE_STACK_NAME_DUPLICATES_CONTEXT]: true, + [ENABLE_DIFF_NO_FAIL_CONTEXT]: true, + [STACK_RELATIVE_EXPORTS_CONTEXT]: true, [DOCKER_IGNORE_SUPPORT]: true, [SECRETS_MANAGER_PARSE_OWNED_SECRET_NAME]: true, [KMS_DEFAULT_KEY_POLICIES]: true, @@ -220,6 +220,6 @@ const FUTURE_FLAGS_DEFAULTS: { [key: string]: boolean } = { [CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021]: false, }; -export function futureFlagDefault(flag: string): boolean { +export function futureFlagDefault(flag: string): boolean | undefined { return FUTURE_FLAGS_DEFAULTS[flag]; } diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 8d4293f2b5ee4..0a842759dd30e 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -32,7 +32,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": true + "strict": false } } } @@ -68,10 +68,10 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/mock-fs": "^4.13.1", - "@types/semver": "^7.3.8", - "jest": "^26.6.3", + "@types/semver": "^7.3.9", + "jest": "^27.3.1", "mock-fs": "^4.14.0" }, "repository": { diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts index ddd7538308ec3..2151e2345620c 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CloudAssembly } from '../lib'; const FIXTURES = path.join(__dirname, 'fixtures'); @@ -105,7 +106,7 @@ test('fails for invalid dependencies', () => { expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-depends'))).toThrow('Artifact StackC depends on non-existing artifact StackX'); }); -test('stack artifacts can specify an explicit stack name that is different from the artifact id', () => { +testDeprecated('stack artifacts can specify an explicit stack name that is different from the artifact id', () => { const assembly = new CloudAssembly(path.join(FIXTURES, 'explicit-stack-name')); expect(assembly.getStackByName('TheStackName').stackName).toStrictEqual('TheStackName'); diff --git a/packages/@aws-cdk/cx-api/test/features.test.ts b/packages/@aws-cdk/cx-api/test/features.test.ts index 2aaa774b7b1c5..afc9c0838d7da 100644 --- a/packages/@aws-cdk/cx-api/test/features.test.ts +++ b/packages/@aws-cdk/cx-api/test/features.test.ts @@ -7,6 +7,10 @@ test('all future flags have defaults configured', () => { }); }); +test('futureFlagDefault returns undefined if non existent flag was given', () => { + expect(feats.futureFlagDefault('non-existent-flag')).toEqual(undefined); +}); + testLegacyBehavior('FUTURE_FLAGS_EXPIRED must be empty in CDKv1', Object, () => { expect(feats.FUTURE_FLAGS_EXPIRED.length).toEqual(0); }); diff --git a/packages/@aws-cdk/example-construct-library/lib/example-resource.ts b/packages/@aws-cdk/example-construct-library/lib/example-resource.ts index 6c4630dab6ae4..45a91919d69a0 100644 --- a/packages/@aws-cdk/example-construct-library/lib/example-resource.ts +++ b/packages/@aws-cdk/example-construct-library/lib/example-resource.ts @@ -137,7 +137,7 @@ export interface IExampleResource extends * The details of which metric methods you should have of course depends on the * resource that is being modeled. */ - metricCount(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; + metricCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** @@ -212,12 +212,12 @@ abstract class ExampleResourceBase extends core.Resource implements IExampleReso } /** Implement the {@link IExampleResource.metricCount} method. */ - public metricCount(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ // of course, you would put your resource-specific values here namespace: 'AWS/ExampleResource', - dimensions: { ExampleResource: this.exampleResourceName }, - metricName, + metricName: 'Count', + dimensionsMap: { ExampleResource: this.exampleResourceName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/example-construct-library/package.json b/packages/@aws-cdk/example-construct-library/package.json index 672ba0e4a50e2..7554004c160fc 100644 --- a/packages/@aws-cdk/example-construct-library/package.json +++ b/packages/@aws-cdk/example-construct-library/package.json @@ -70,8 +70,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts index db1e8d68d4830..cbfb43344ad25 100644 --- a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts +++ b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts @@ -6,9 +6,11 @@ import { Match, Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as core from '@aws-cdk/core'; + // Always import the module you're testing qualified - // don't import individual classes from it! // Importing it qualified tests whether everything that needs to be exported @@ -18,6 +20,12 @@ import * as er from '../lib'; /* We allow quotes in the object keys used for CloudFormation template assertions */ /* eslint-disable quote-props */ +const EXAMPLE_RESOURCE_NAME = { + 'Fn::Select': [1, { + 'Fn::Split': ['/', { 'Ref': 'ExampleResourceAC53F4AE' }], + }], +}; + describe('Example Resource', () => { let stack: core.Stack; @@ -111,6 +119,40 @@ describe('Example Resource', () => { }, }); }); + + test('onEvent adds an Event Rule', () => { + exampleResource.onEvent('MyEvent'); + + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + EventPattern: { + detail: { + 'example-resource-name': [EXAMPLE_RESOURCE_NAME], + }, + }, + }); + }); + + test('metricCount returns a metric with correct dimensions', () => { + const countMetric = exampleResource.metricCount(); + + new cloudwatch.Alarm(stack, 'Alarm', { + metric: countMetric, + threshold: 10, + evaluationPeriods: 2, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + EvaluationPeriods: 2, + Dimensions: [ + { + Name: 'ExampleResource', + Value: EXAMPLE_RESOURCE_NAME, + }, + ], + MetricName: 'Count', + Namespace: 'AWS/ExampleResource', + }); + }); }); describe('created with a VPC', () => { @@ -146,6 +188,10 @@ describe('Example Resource', () => { 'my-example-resource-name'); }); + test('throws when accessing connections', () => { + expect(() => exampleResource.connections).toThrow(); + }); + test('has the same name as it was imported with', () => { expect(exampleResource.exampleResourceName).toEqual('my-example-resource-name'); }); diff --git a/packages/@aws-cdk/lambda-layer-awscli/README.md b/packages/@aws-cdk/lambda-layer-awscli/README.md index baad6362f5e21..e36677c222de0 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/README.md +++ b/packages/@aws-cdk/lambda-layer-awscli/README.md @@ -15,8 +15,11 @@ This module exports a single class called `AwsCliLayer` which is a `lambda.Layer Usage: ```ts -const fn = new lambda.Function(...); -fn.addLayers(new AwsCliLayer(stack, 'AwsCliLayer')); +// AwsCliLayer bundles the AWS CLI in a lambda layer +import { AwsCliLayer } from '@aws-cdk/lambda-layer-awscli'; + +declare const fn: lambda.Function; +fn.addLayers(new AwsCliLayer(this, 'AwsCliLayer')); ``` The CLI will be installed under `/opt/awscli/aws`. diff --git a/packages/@aws-cdk/lambda-layer-awscli/package.json b/packages/@aws-cdk/lambda-layer-awscli/package.json index 773e292d92020..1a66cd1bae0fb 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/package.json +++ b/packages/@aws-cdk/lambda-layer-awscli/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -70,8 +77,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture b/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d3534321d7f54 --- /dev/null +++ b/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/lambda-layer-kubectl/README.md b/packages/@aws-cdk/lambda-layer-kubectl/README.md index 3782174b1aa72..2a90c534c9f24 100644 --- a/packages/@aws-cdk/lambda-layer-kubectl/README.md +++ b/packages/@aws-cdk/lambda-layer-kubectl/README.md @@ -11,14 +11,18 @@ This module exports a single class called `KubectlLayer` which is a `lambda.Layer` that bundles the [`kubectl`](https://kubernetes.io/docs/reference/kubectl/kubectl/) and the [`helm`](https://helm.sh/) command line. -> - Helm Version: 1.20.0 -> - Kubectl Version: 3.4.2 +> - Helm Version: 3.5.4 +> - Kubectl Version: 1.20.0 +> Usage: ```ts -const fn = new lambda.Function(...); -fn.addLayers(new KubectlLayer(stack, 'KubectlLayer')); +// KubectlLayer bundles the 'kubectl' and 'helm' command lines +import { KubectlLayer } from '@aws-cdk/lambda-layer-kubectl'; + +declare const fn: lambda.Function; +fn.addLayers(new KubectlLayer(this, 'KubectlLayer')); ``` `kubectl` will be installed under `/opt/kubectl/kubectl`, and `helm` will be installed under `/opt/helm/helm`. diff --git a/packages/@aws-cdk/lambda-layer-kubectl/package.json b/packages/@aws-cdk/lambda-layer-kubectl/package.json index 11e287144b29f..b62ace7ad6246 100644 --- a/packages/@aws-cdk/lambda-layer-kubectl/package.json +++ b/packages/@aws-cdk/lambda-layer-kubectl/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -70,8 +77,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "pkglint": { "attribution": [ diff --git a/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture b/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d3534321d7f54 --- /dev/null +++ b/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.dockerignore b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.dockerignore index 69b73f61d249a..88a84e55aa43b 100644 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.dockerignore +++ b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.dockerignore @@ -1,2 +1 @@ build.sh -.no-packagejson-validator diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/Dockerfile b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/Dockerfile index 8c747a0e2b95f..2e3f644258652 100644 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/Dockerfile +++ b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/Dockerfile @@ -1,6 +1,8 @@ # base lambda image FROM public.ecr.aws/lambda/nodejs:latest +ARG PROXY_AGENT_VERSION=5.0.0 + USER root RUN mkdir -p /opt WORKDIR /tmp @@ -17,8 +19,7 @@ RUN yum update -y \ # RUN mkdir -p /opt/nodejs -COPY package.json /opt/nodejs -RUN cd /opt/nodejs && npm install +RUN cd /opt/nodejs && npm install proxy-agent@${PROXY_AGENT_VERSION} # # create the bundle diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package.json b/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package.json deleted file mode 100644 index 102dd83c99391..0000000000000 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "node-proxy-agent-layer", - "private": true, - "version": "0.0.1", - "license": "Apache-2.0", - "devDependencies": { - "proxy-agent": "^5.0.0" - } -} diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/lib/node-proxy-agent-layer.ts b/packages/@aws-cdk/lambda-layer-node-proxy-agent/lib/node-proxy-agent-layer.ts index 9e6471a2da2c6..60b8c8697dea7 100644 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/lib/node-proxy-agent-layer.ts +++ b/packages/@aws-cdk/lambda-layer-node-proxy-agent/lib/node-proxy-agent-layer.ts @@ -11,8 +11,8 @@ export class NodeProxyAgentLayer extends lambda.LayerVersion { constructor(scope: Construct, id: string) { super(scope, id, { code: lambda.Code.fromAsset(path.join(__dirname, 'layer.zip'), { - // we hash the package.json (it contains the tools versions) because hashing the zip is non-deterministic - assetHash: hashFile(path.join(__dirname, '..', 'layer', 'package.json')), + // we hash the Dockerfile (it contains the tools versions) because hashing the zip is non-deterministic + assetHash: hashFile(path.join(__dirname, '..', 'layer', 'Dockerfile')), }), description: '/opt/nodejs/node_modules/proxy-agent', }); diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/package.json b/packages/@aws-cdk/lambda-layer-node-proxy-agent/package.json index 46345afc7f796..7be547ea61a57 100644 --- a/packages/@aws-cdk/lambda-layer-node-proxy-agent/package.json +++ b/packages/@aws-cdk/lambda-layer-node-proxy-agent/package.json @@ -70,8 +70,8 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", - "jest": "^26.6.3" + "@types/jest": "^27.0.2", + "jest": "^27.3.1" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/pipelines/ORIGINAL_API.md b/packages/@aws-cdk/pipelines/ORIGINAL_API.md index 73ac108d9c67f..447e39bb09bc2 100644 --- a/packages/@aws-cdk/pipelines/ORIGINAL_API.md +++ b/packages/@aws-cdk/pipelines/ORIGINAL_API.md @@ -41,7 +41,9 @@ all commands necessary to do a full CDK build and synth, so do include installing dependencies and running the CDK CLI. For example, the old API: ```ts -SimpleSynthAction.standardNpmSynth({ +const sourceArtifact = new codepipeline.Artifact(); +const cloudAssemblyArtifact = new codepipeline.Artifact(); +pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -54,8 +56,10 @@ SimpleSynthAction.standardNpmSynth({ Becomes: ```ts -new ShellStep('Synth', { - input: /* source */, +new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), commands: [ 'npm ci', 'npm run build', @@ -71,7 +75,7 @@ You can use any of the factory functions on `CodePipelineSource`. For example, for a GitHub source, the following old API: ```ts -sourceAction: new codepipeline_actions.GitHubSourceAction({ +sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, // Replace these with your actual GitHub project name @@ -84,8 +88,8 @@ sourceAction: new codepipeline_actions.GitHubSourceAction({ Translates into: ```ts -input: CodePipelineSource.gitHub('OWNER/REPO', 'main', { - authentication: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), +input: pipelines.CodePipelineSource.gitHub('OWNER/REPO', 'main', { + authentication: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), }), ``` @@ -111,8 +115,9 @@ putting manual approvals in `pre` steps, and automated approvals in `post` steps For example, specifying a manual approval on a stage deployment in old API: ```ts +declare const pipeline: pipelines.CdkPipeline; const stage = pipeline.addApplicationStage(...); -stage.addAction(new ManualApprovalAction({ +stage.addAction(new pipelines.ManualApprovalAction({ actionName: 'ManualApproval', runOrder: testingStage.nextSequentialRunOrder(), })); @@ -121,9 +126,10 @@ stage.addAction(new ManualApprovalAction({ Becomes: ```ts -pipeline.addStage(..., { +const stage = new MyApplicationStage(this, 'MyApplication'); +pipeline.addStage(stage, { pre: [ - new ManualApprovalStep('ManualApproval'), + new pipelines.ManualApprovalStep('ManualApproval'), ], }); ``` @@ -139,7 +145,7 @@ For example, specifying an automated approval after a stage is deployed in the f ```ts const stage = pipeline.addApplicationStage(...); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ actionName: 'MyValidation', commands: ['curl -Ssf $VAR'], useOutputs: { @@ -153,10 +159,10 @@ stage.addActions(new ShellScriptAction({ Becomes: ```ts -const stage = new MyStage(...); +const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { post: [ - new CodeBuildStep('MyValidation', { + new pipelines.CodeBuildStep('MyValidation', { commands: ['curl -Ssf $VAR'], envFromCfnOutput: { VAR: stage.cfnOutput, @@ -174,7 +180,19 @@ customizations (like `buildEnvironment`). #### Change set approvals In the old API, there were two properties that were used to add actions to the pipeline -in between the `CreateChangeSet` and `ExecuteChangeSet` actions: `manualApprovals` and `extraRunOrderSpace`. These are not supported in the new API. +in between the `CreateChangeSet` and `ExecuteChangeSet` actions: `manualApprovals` and `extraRunOrderSpace`. +This can be achieved in the modern API via the `stackSteps` property, which allows steps to be added +at the stack level: + +```ts +const stage = new MyApplicationStage(this, 'MyApplication'); +pipeline.addStage(stage, { + stackSteps: [{ + stack: stage.stack1, + changeSet: [new pipelines.ManualApprovalStep('ChangeSet Approval')], + }], +}); +``` ### Custom CodePipeline Actions @@ -190,7 +208,6 @@ artifacts: ```ts import { Construct, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; -import { CdkPipeline } from '@aws-cdk/pipelines'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; /** @@ -203,20 +220,20 @@ class MyPipelineStack extends Stack { const sourceArtifact = new codepipeline.Artifact(); const cloudAssemblyArtifact = new codepipeline.Artifact(); - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { cloudAssemblyArtifact, - sourceAction: new codepipeline_actions.GitHubSourceAction({ + sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, - oauthToken: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), + oauthToken: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), // Replace these with your actual GitHub project name owner: 'OWNER', repo: 'REPO', branch: 'main', // default: 'master' }), - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -274,21 +291,21 @@ class MyPipelineStack extends Stack { const sourceArtifact = new codepipeline.Artifact(); const cloudAssemblyArtifact = new codepipeline.Artifact(); - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { pipelineName: 'MyAppPipeline', cloudAssemblyArtifact, - sourceAction: new codepipeline_actions.GitHubSourceAction({ + sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, - oauthToken: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), + oauthToken: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), // Replace these with your actual GitHub project name owner: 'OWNER', repo: 'REPO', branch: 'main', // default: 'master' }), - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -316,7 +333,7 @@ If you prefer more control over the underlying CodePipeline object, you can create one yourself, including custom Source and Build stages: ```ts -const codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { +const codePipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', { stages: [ { stageName: 'CustomSource', @@ -330,7 +347,7 @@ const codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { }); const app = new App(); -const cdkPipeline = new CdkPipeline(app, 'CdkPipeline', { +const cdkPipeline = new pipelines.CdkPipeline(app, 'CdkPipeline', { codePipeline, cloudAssemblyArtifact, }); @@ -360,9 +377,9 @@ using these, the source repository does not need to have a `buildspec.yml`. An e of using `SimpleSynthAction` to run a Maven build followed by a CDK synth: ```ts -const pipeline = new CdkPipeline(this, 'Pipeline', { +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { // ... - synthAction: new SimpleSynthAction({ + synthAction: new pipelines.SimpleSynthAction({ sourceArtifact, cloudAssemblyArtifact, installCommands: ['npm install -g aws-cdk'], @@ -396,16 +413,16 @@ from the CA repo instead of NPM. class MyPipelineStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { ... - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { ... - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, // Use this to customize and a permissions required for the build // and synth rolePolicyStatements: [ - new PolicyStatement({ + new iam.PolicyStatement({ actions: ['codeartifact:*', 'sts:GetServiceBearerToken'], resources: ['arn:codeartifact:repo:arn'], }), @@ -477,7 +494,7 @@ const testingStage = pipeline.addApplicationStage(new MyApplication(this, 'Testi // Add a action -- in this case, a Manual Approval action // (for illustration purposes: testingStage.addManualApprovalAction() is a // convenience shorthand that does the same) -testingStage.addAction(new ManualApprovalAction({ +testingStage.addAction(new pipelines.ManualApprovalAction({ actionName: 'ManualApproval', runOrder: testingStage.nextSequentialRunOrder(), })); @@ -522,7 +539,7 @@ In its simplest form, adding validation actions looks like this: ```ts const stage = pipeline.addApplicationStage(new MyApplication(/* ... */)); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ actionName: 'MyValidation', commands: ['curl -Ssf https://my.webservice.com/'], // Optionally specify a VPC if, for example, the service is deployed with a private load balancer @@ -563,7 +580,7 @@ const lbApp = new MyLbApplication(this, 'MyApp', { env: { /* ... */ } }); const stage = pipeline.addApplicationStage(lbApp); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ // ... useOutputs: { // When the test is executed, this will make $URL contain the @@ -594,7 +611,7 @@ two ways. Either pass additional policy statements in the `rolePolicyStatements` property: ```ts -new ShellScriptAction({ +new pipelines.ShellScriptAction({ // ... rolePolicyStatements: [ new iam.PolicyStatement({ @@ -608,7 +625,7 @@ new ShellScriptAction({ The Action can also be used as a Grantable after having been added to a Pipeline: ```ts -const action = new ShellScriptAction({ /* ... */ }); +const action = new pipelines.ShellScriptAction({ /* ... */ }); pipeline.addStage('Test').addActions(action); bucket.grantRead(action); @@ -623,11 +640,11 @@ if they are executable shell scripts themselves). Pass the `sourceArtifact`: ```ts const sourceArtifact = new codepipeline.Artifact(); -const pipeline = new CdkPipeline(this, 'Pipeline', { +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { // ... }); -const validationAction = new ShellScriptAction({ +const validationAction = new pipelines.ShellScriptAction({ actionName: 'TestUsingSourceArtifact', additionalArtifacts: [sourceArtifact], @@ -651,8 +668,8 @@ in the `ShellScriptAction`'s `additionalArtifacts`: const cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); const integTestsArtifact = new codepipeline.Artifact('IntegTests'); -const pipeline = new CdkPipeline(this, 'Pipeline', { - synthAction: SimpleSynthAction.standardNpmSynth({ +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, buildCommands: ['npm run build'], @@ -666,7 +683,7 @@ const pipeline = new CdkPipeline(this, 'Pipeline', { // ... }); -const validationAction = new ShellScriptAction({ +const validationAction = new pipelines.ShellScriptAction({ actionName: 'TestUsingBuildArtifact', additionalArtifacts: [integTestsArtifact], // 'test.js' was produced from 'test/test.ts' during the synth step @@ -715,12 +732,11 @@ create an SNS Topic, subscribe your own email address, and pass it in via ```ts import * as sns from '@aws-cdk/aws-sns'; import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; -import * as pipelines from '@aws-cdk/pipelines'; const topic = new sns.Topic(this, 'SecurityChangesTopic'); topic.addSubscription(new subscriptions.EmailSubscription('test@email.com')); -const pipeline = new CdkPipeline(app, 'Pipeline', { /* ... */ }); +const pipeline = new pipelines.CdkPipeline(app, 'Pipeline', { /* ... */ }); const stage = pipeline.addApplicationStage(new MyApplication(this, 'PreProd'), { confirmBroadeningPermissions: true, securityNotificationTopic: topic, diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 3013140344bef..ac345c75f3776 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -38,19 +38,36 @@ with the same amount of code. The *CDK Pipelines* library takes care of the details. CDK Pipelines supports multiple *deployment engines* (see below), and comes with -a deployment engine that deployes CDK apps using AWS CodePipeline. To use the +a deployment engine that deploys CDK apps using AWS CodePipeline. To use the CodePipeline engine, define a `CodePipeline` construct. The following example creates a CodePipeline that deploys an application from GitHub: ```ts -/** The stacks for our app are defined in my-stacks.ts. The internals of these +/** The stacks for our app are minimally defined here. The internals of these * stacks aren't important, except that DatabaseStack exposes an attribute * "table" for a database table it defines, and ComputeStack accepts a reference * to this table in its properties. */ -import { DatabaseStack, ComputeStack } from '../lib/my-stacks'; -import { Construct, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; -import { CodePipeline, CodePipelineSource, ShellStep } from '@aws-cdk/pipelines'; +class DatabaseStack extends Stack { + public readonly table: dynamodb.Table; + + constructor(scope: Construct, id: string) { + super(scope, id); + this.table = new dynamodb.Table(this, 'Table', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING } + }); + } +} + +interface ComputeProps { + readonly table: dynamodb.Table; +} + +class ComputeStack extends Stack { + constructor(scope: Construct, id: string, props: ComputeProps) { + super(scope, id); + } +} /** * Stack to hold the pipeline @@ -59,11 +76,11 @@ class MyPipelineStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { + const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { // Use a connection created using the AWS console to authenticate to GitHub // Other sources are available. - input: CodePipelineSource.connection('my-org/my-app', 'main', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', }), commands: [ @@ -81,7 +98,7 @@ class MyPipelineStack extends Stack { env: { account: '123456789012', region: 'eu-west-1', - } + }, })); } } @@ -106,7 +123,7 @@ class MyApplication extends Stage { } // In your main file -new MyPipelineStack(app, 'PipelineStack', { +new MyPipelineStack(this, 'PipelineStack', { env: { account: '123456789012', region: 'eu-west-1', @@ -138,8 +155,8 @@ by adding the following to `cdk.json`: ## Provisioning the pipeline -To provision the pipeline you have defined, making sure the target environment -has been bootstrapped (see below), and then executing deploying the +To provision the pipeline you have defined, make sure the target environment +has been bootstrapped (see below), and then execute deploying the `PipelineStack` *once*. Afterwards, the pipeline will keep itself up-to-date. > **Important**: be sure to `git commit` and `git push` before deploying the @@ -158,7 +175,7 @@ $ cdk deploy PipelineStack ``` Administrative permissions to the account are only necessary up until -this point. We recommend you shed access to these credentials after doing this. +this point. We recommend you remove access to these credentials after doing this. ### Working on the pipeline @@ -172,15 +189,25 @@ off temporarily, by passing `selfMutation: false` property, example: ```ts // Modern API -const pipeline = new CodePipeline(this, 'Pipeline', { +const modernPipeline = new pipelines.CodePipeline(this, 'Pipeline', { selfMutation: false, - ... + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), }); // Original API -const pipeline = new CdkPipeline(this, 'Pipeline', { +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const originalPipeline = new pipelines.CdkPipeline(this, 'Pipeline', { selfMutating: false, - ... + cloudAssemblyArtifact, }); ``` @@ -204,10 +231,10 @@ commands required will depend on the programming language you are using. For a typical NPM-based project, the synth will look like this: ```ts -const source = /* the repository source */; +declare const source: pipelines.IFileSetProducer; // the repository source -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'npm ci', @@ -224,8 +251,10 @@ CDK project lives in a subdirectory, be sure to adjust the `primaryOutputDirectory` to match: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'cd mysubdir', @@ -254,8 +283,10 @@ look like in a number of different situations. For Yarn, the install commands are different: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'yarn install --frozen-lockfile', @@ -270,8 +301,10 @@ For Python projects, remember to install the CDK CLI globally (as there is no `package.json` to automatically install it for you): ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'pip install -r requirements.txt', @@ -288,8 +321,10 @@ and the Maven compilation step is automatically executed for you as you run `cdk synth`: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'npm install -g aws-cdk', @@ -314,7 +349,7 @@ You will first use the AWS Console to authenticate to the source control provider, and then use the connection ARN in your pipeline definition: ```ts -CodePipelineSource.connection('org/repo', 'branch', { +pipelines.CodePipelineSource.connection('org/repo', 'branch', { connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', }); ``` @@ -328,9 +363,9 @@ you can change the name. The token should have the **repo** and **admin:repo_hoo scopes. ```ts -CodePipelineSource.gitHub('org/repo', 'branch', { +pipelines.CodePipelineSource.gitHub('org/repo', 'branch', { // This is optional - authentication: SecretValue.secretsManager('my-token'), + authentication: cdk.SecretValue.secretsManager('my-token'), }); ``` @@ -341,8 +376,8 @@ that the CodeCommit repository and then use `CodePipelineSource.codeCommit` to reference it: ```ts -const repository = codecommit.fromRepositoryName(this, 'Repository', 'my-repository'); -CodePipelineSource.codeCommit(repository); +const repository = codecommit.Repository.fromRepositoryName(this, 'Repository', 'my-repository'); +pipelines.CodePipelineSource.codeCommit(repository, 'main'); ``` ##### S3 @@ -352,7 +387,7 @@ triggered every time the file in S3 is changed: ```ts const bucket = s3.Bucket.fromBucketName(this, 'Bucket', 'my-bucket'); -CodePipelineSource.s3(bucket, 'my/source.zip'); +pipelines.CodePipelineSource.s3(bucket, 'my/source.zip'); ``` #### Additional inputs @@ -363,17 +398,17 @@ output file set can be used as an input, such as a `CodePipelineSource`, but also other `ShellStep`: ```ts -const prebuild = new ShellStep('Prebuild', { - input: CodePipelineSource.gitHub('myorg/repo1'), +const prebuild = new pipelines.ShellStep('Prebuild', { + input: pipelines.CodePipelineSource.gitHub('myorg/repo1', 'main'), primaryOutputDirectory: './build', commands: ['./build.sh'], }); -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { - input: CodePipelineSource.gitHub('myorg/repo2'), +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.gitHub('myorg/repo2', 'main'), additionalInputs: { - 'subdir': CodePipelineSource.gitHub('myorg/repo3'), + 'subdir': pipelines.CodePipelineSource.gitHub('myorg/repo3', 'main'), '../siblingdir': prebuild, }, @@ -389,6 +424,7 @@ more CDK `Stages` which will be deployed to their target environments. To do so, call `pipeline.addStage()` on the Stage object: ```ts +declare const pipeline: pipelines.CodePipeline; // Do this as many times as necessary with any account and region // Account and region may different from the pipeline's. pipeline.addStage(new MyApplicationStage(this, 'Prod', { @@ -421,6 +457,7 @@ deployed in sequence. For example, the following will deploy two copies of your application to `eu-west-1` and `eu-central-1` in parallel: ```ts +declare const pipeline: pipelines.CodePipeline; const europeWave = pipeline.addWave('Europe'); europeWave.addStage(new MyApplicationStage(this, 'Ireland', { env: { region: 'eu-west-1' } @@ -445,9 +482,19 @@ KMS key. Example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { // Encrypt artifacts, required for cross-account deployments crossAccountKeys: true, + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), }); ``` @@ -464,19 +511,20 @@ a manual approval in the form of a `ManualApprovalStep` added to the pipeline. B pass in order to promote from the `PreProd` to the `Prod` environment: ```ts -const preprod = new MyApplicationStage(this, 'PreProd', { ... }); -const prod = new MyApplicationStage(this, 'Prod', { ... }); +declare const pipeline: pipelines.CodePipeline; +const preprod = new MyApplicationStage(this, 'PreProd'); +const prod = new MyApplicationStage(this, 'Prod'); pipeline.addStage(preprod, { post: [ - new ShellStep('Validate Endpoint', { + new pipelines.ShellStep('Validate Endpoint', { commands: ['curl -Ssf https://my.webservice.com/'], }), ], }); pipeline.addStage(prod, { pre: [ - new ManualApprovalStep('PromoteToProd'), + new pipelines.ManualApprovalStep('PromoteToProd'), ], }); ``` @@ -484,15 +532,29 @@ pipeline.addStage(prod, { You can also specify steps to be executed at the stack level. To achieve this, you can specify the stack and step via the `stackSteps` property: ```ts +class MyStacksStage extends Stage { + public readonly stack1: Stack; + public readonly stack2: Stack; + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + this.stack1 = new Stack(this, 'stack1'); + this.stack2 = new Stack(this, 'stack2'); + } +} + +declare const pipeline: pipelines.CodePipeline; +const prod = new MyStacksStage(this, 'Prod'); + pipeline.addStage(prod, { stackSteps: [{ stack: prod.stack1, - pre: [new ManualApprovalStep('Pre-Stack Check')], // Executed before stack is prepared - changeSet: [new ManualApprovalStep('ChangeSet Approval')], // Executed after stack is prepared but before the stack is deployed - post: [new ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed + pre: [new pipelines.ManualApprovalStep('Pre-Stack Check')], // Executed before stack is prepared + changeSet: [new pipelines.ManualApprovalStep('ChangeSet Approval')], // Executed after stack is prepared but before the stack is deployed + post: [new pipelines.ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed }, { stack: prod.stack2, - post: [new ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed + post: [new pipelines.ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed }], }); ``` @@ -507,21 +569,26 @@ To use Stack Outputs, expose the `CfnOutput` object you're interested in, and pass it to `envFromCfnOutputs` of the `ShellStep`: ```ts -class MyApplicationStage extends Stage { +class MyOutputStage extends Stage { public readonly loadBalancerAddress: CfnOutput; - // ... + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + this.loadBalancerAddress = new CfnOutput(this, 'Output', {value: 'value'}); + } } -const lbApp = new MyApplicationStage(this, 'MyApp', { /* ... */ }); +const lbApp = new MyOutputStage(this, 'MyApp'); +declare const pipeline: pipelines.CodePipeline; pipeline.addStage(lbApp, { post: [ - new ShellStep('HitEndpoint', { + new pipelines.ShellStep('HitEndpoint', { envFromCfnOutputs: { // Make the load balancer address available as $URL inside the commands URL: lbApp.loadBalancerAddress, }, commands: ['curl -Ssf $URL'], - }); + }), ], }); ``` @@ -539,12 +606,13 @@ Here's an example that captures an additional output directory in the synth step and runs tests from there: ```ts -const synth = new ShellStep('Synth', { /* ... */ }); -const pipeline = new CodePipeline(this, 'Pipeline', { synth }); +declare const synth: pipelines.ShellStep; +const stage = new MyApplicationStage(this, 'MyApplication'); +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { synth }); -pipeline.addStage(/* ... */, { +pipeline.addStage(stage, { post: [ - new ShellStep('Approve', { + new pipelines.ShellStep('Approve', { // Use the contents of the 'integ' directory from the synth step as the input input: synth.addOutputDirectory('integ'), commands: ['cd integ && ./run.sh'], @@ -562,7 +630,9 @@ generated, use a `CodeBuildStep` instead of a `ShellStep`. This class has a numb of properties that allow you to customize various aspects of the projects: ```ts -new CodeBuildStep('Synth', { +declare const vpc: ec2.Vpc; +declare const mySecurityGroup: ec2.SecurityGroup; +new pipelines.CodeBuildStep('Synth', { // ...standard ShellStep props... commands: [/* ... */], env: { /* ... */ }, @@ -602,8 +672,20 @@ or just for the synth, asset publishing, and self-mutation projects by passing ` `assetPublishingCodeBuildDefaults`, or `selfMutationCodeBuildDefaults`: ```ts -new CodePipeline(this, 'Pipeline', { - // ... +declare const vpc: ec2.Vpc; +declare const mySecurityGroup: ec2.SecurityGroup; +new pipelines.CodePipeline(this, 'Pipeline', { + // Standard CodePipeline properties + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), // Defaults for all CodeBuild projects codeBuildDefaults: { @@ -644,15 +726,19 @@ doesn't have a matching class yet, you can define your own step class that exten Here's an example that adds a Jenkins step: ```ts -class MyJenkinsStep extends Step implements ICodePipelineActionFactory { - constructor(private readonly provider: codepipeline_actions.JenkinsProvider, private readonly input: FileSet) { +class MyJenkinsStep extends pipelines.Step implements pipelines.ICodePipelineActionFactory { + constructor( + private readonly provider: cpactions.JenkinsProvider, + private readonly input: pipelines.FileSet, + ) { + super('MyJenkinsStep'); } - public produceAction(stage: codepipeline.IStage, options: ProduceActionOptions): CodePipelineActionFactoryResult { + public produceAction(stage: codepipeline.IStage, options: pipelines.ProduceActionOptions): pipelines.CodePipelineActionFactoryResult { // This is where you control what type of Action gets added to the // CodePipeline - stage.addAction(new codepipeline_actions.JenkinsAction({ + stage.addAction(new cpactions.JenkinsAction({ // Copy 'actionName' and 'runOrder' from the options actionName: options.actionName, runOrder: options.runOrder, @@ -700,8 +786,13 @@ stacks the pipeline is deploying), for example by the use of `LinuxBuildImage.fr you need to pass `dockerEnabledForSelfMutation: true` to the pipeline. For example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - // ... +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), // Turn this on because the pipeline uses Docker image assets dockerEnabledForSelfMutation: true, @@ -709,16 +800,16 @@ const pipeline = new CodePipeline(this, 'Pipeline', { pipeline.addWave('MyWave', { post: [ - new CodeBuildStep('RunApproval', { + new pipelines.CodeBuildStep('RunApproval', { commands: ['command-from-image'], buildEnvironment: { // The user of a Docker image asset in the pipeline requires turning on // 'dockerEnabledForSelfMutation'. - buildImage: LinuxBuildImage.fromAsset(this, 'Image', { + buildImage: codebuild.LinuxBuildImage.fromAsset(this, 'Image', { directory: './docker-image', - }) + }), }, - }) + }), ], }); ``` @@ -734,8 +825,13 @@ if you add a construct like `@aws-cdk/aws-lambda-nodejs`), you need to pass `dockerEnabledForSynth: true` to the pipeline. For example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - // ... +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), // Turn this on because the application uses bundled file assets dockerEnabledForSynth: true, @@ -756,16 +852,21 @@ different environment (e.g., ECR repo) or to avoid throttling (e.g., DockerHub). ```ts const dockerHubSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'DHSecret', 'arn:aws:...'); const customRegSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'CRSecret', 'arn:aws:...'); -const repo1 = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo1'); -const repo2 = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2'); +const repo1 = ecr.Repository.fromRepositoryArn(this, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo1'); +const repo2 = ecr.Repository.fromRepositoryArn(this, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2'); -const pipeline = new CodePipeline(this, 'Pipeline', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { dockerCredentials: [ - DockerCredential.dockerHub(dockerHubSecret), - DockerCredential.customRegistry('dockerregistry.example.com', customRegSecret), - DockerCredential.ecr([repo1, repo2]); + pipelines.DockerCredential.dockerHub(dockerHubSecret), + pipelines.DockerCredential.customRegistry('dockerregistry.example.com', customRegSecret), + pipelines.DockerCredential.ecr([repo1, repo2]), ], - // ... + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), }); ``` @@ -784,7 +885,7 @@ the **Synth**, **Self-Update**, and **Asset Publishing** actions within the ```ts const dockerHubSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'DHSecret', 'arn:aws:...'); // Only the image asset publishing actions will be granted read access to the secret. -const creds = DockerCredential.dockerHub(dockerHubSecret, { usages: [DockerCredentialUsage.ASSET_PUBLISHING] }); +const creds = pipelines.DockerCredential.dockerHub(dockerHubSecret, { usages: [pipelines.DockerCredentialUsage.ASSET_PUBLISHING] }); ``` ## CDK Environment Bootstrapping @@ -953,9 +1054,11 @@ give the synth CodeBuild execution role permissions to assume the bootstrapped lookup roles. As an example, doing so would look like this: ```ts -new CodePipeline(this, 'Pipeline', { - synth: new CodeBuildStep('Synth', { - input: // ...input... +new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.CodeBuildStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), commands: [ // Commands to load cdk.context.json from somewhere here '...', @@ -1034,10 +1137,11 @@ Pipeline You can insert the security check by using a `ConfirmPermissionsBroadening` step: ```ts +declare const pipeline: pipelines.CodePipeline; const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { pre: [ - new ConfirmPermissionsBroadening('Check', { stage }), + new pipelines.ConfirmPermissionsBroadening('Check', { stage }), ], }); ``` @@ -1047,17 +1151,14 @@ create an SNS Topic, subscribe your own email address, and pass it in as as the `notificationTopic` property: ```ts -import * as sns from '@aws-cdk/aws-sns'; -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; -import * as pipelines from '@aws-cdk/pipelines'; - +declare const pipeline: pipelines.CodePipeline; const topic = new sns.Topic(this, 'SecurityChangesTopic'); topic.addSubscription(new subscriptions.EmailSubscription('test@email.com')); const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { pre: [ - new ConfirmPermissionsBroadening('Check', { + new pipelines.ConfirmPermissionsBroadening('Check', { stage, notificationTopic: topic, }), @@ -1162,19 +1263,19 @@ that bundles asset using tools run via Docker, like `aws-lambda-nodejs`, `aws-la Make sure you set the `privileged` environment variable to `true` in the synth definition: -```typescript - const pipeline = new CdkPipeline(this, 'MyPipeline', { - ... - - synthAction: SimpleSynthAction.standardNpmSynth({ - sourceArtifact: ..., - cloudAssemblyArtifact: ..., - - environment: { - privileged: true, - }, - }), - }); +```ts +const sourceArtifact = new codepipeline.Artifact(); +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const pipeline = new pipelines.CdkPipeline(this, 'MyPipeline', { + cloudAssemblyArtifact, + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + environment: { + privileged: true, + }, + }), +}); ``` After turning on `privilegedMode: true`, you will need to do a one-time manual cdk deploy of your @@ -1201,10 +1302,11 @@ This happens because the pipeline is not self-mutating and, as a consequence, th build projects get out-of-sync with the generated templates. To fix this, make sure the `selfMutating` property is set to `true`: -```typescript -const pipeline = new CdkPipeline(this, 'MyPipeline', { +```ts +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const pipeline = new pipelines.CdkPipeline(this, 'MyPipeline', { selfMutating: true, - ... + cloudAssemblyArtifact, }); ``` @@ -1240,9 +1342,9 @@ $ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html for more info. ```ts -new MyStack(this, 'MyStack', { +new Stack(this, 'MyStack', { // Update this qualifier to match the one used above. - synthesizer: new DefaultStackSynthesizer({ + synthesizer: new cdk.DefaultStackSynthesizer({ qualifier: 'randchars1234', }), }); diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts index 75c1883d92419..1f03105c78ee9 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts @@ -65,11 +65,11 @@ export interface ShellStepProps { * following configuration: * * ```ts - * const script = new ShellStep('MainScript', { - * // ... - * input: MyEngineSource.gitHub('org/source1'), + * const script = new pipelines.ShellStep('MainScript', { + * commands: ['npm ci','npm run build','npx cdk synth'], + * input: pipelines.CodePipelineSource.gitHub('org/source1', 'main'), * additionalInputs: { - * '../siblingdir': MyEngineSource.gitHub('org/source2'), + * '../siblingdir': pipelines.CodePipelineSource.gitHub('org/source2', 'main'), * } * }); * ``` diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts b/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts index 488551f4eefb7..b3e88cbfe026e 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts @@ -183,7 +183,7 @@ export class StackDeployment { * This is `undefined` if the stack template is not published. Use the * `DefaultStackSynthesizer` to ensure it is. * - * @example https://bucket.s3.amazonaws.com/object/key + * Example value: `https://bucket.s3.amazonaws.com/object/key` */ public readonly templateUrl?: string; diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts index 59abcbe8e287d..7534aba9cb840 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts @@ -333,8 +333,11 @@ function generateInputArtifactLinkCommands(artifacts: ArtifactMap, inputs: FileS return inputs.map(input => { const fragments = []; - if (!['.', '..'].includes(path.dirname(input.directory))) { - fragments.push(`mkdir -p -- "${input.directory}"`); + fragments.push(`[ ! -d "${input.directory}" ] || { echo 'additionalInputs: "${input.directory}" must not exist yet. If you want to merge multiple artifacts, use a "cp" command.'; exit 1; }`); + + const parentDirectory = path.dirname(input.directory); + if (!['.', '..'].includes(parentDirectory)) { + fragments.push(`mkdir -p -- "${parentDirectory}"`); } const artifact = artifacts.toCodePipeline(input.fileSet); diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts index b6d10b03f2f67..382bed08028ff 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts @@ -26,7 +26,7 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Pass in the owner and repository in a single string, like this: * * ```ts - * CodePipelineSource.gitHub('owner/repo', 'main'); + * pipelines.CodePipelineSource.gitHub('owner/repo', 'main'); * ``` * * Authentication will be done by a secret called `github-token` in AWS @@ -51,8 +51,8 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Example: * * ```ts - * const bucket: IBucket = ... - * CodePipelineSource.s3(bucket, { + * declare const bucket: s3.Bucket; + * pipelines.CodePipelineSource.s3(bucket, { * key: 'path/to/file.zip', * }); * ``` @@ -74,7 +74,7 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Example: * * ```ts - * CodePipelineSource.connection('owner/repo', 'main', { + * pipelines.CodePipelineSource.connection('owner/repo', 'main', { * connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * }); * ``` @@ -131,7 +131,6 @@ export interface GitHubSourceOptions { * * ```ts * const oauth = cdk.SecretValue.secretsManager('my-github-token'); - * new GitHubSource(this, 'GitHubSource', { authentication: oauth, ... }); * ``` * * The GitHub Personal Access Token should have these scopes: diff --git a/packages/@aws-cdk/pipelines/lib/legacy/actions/deploy-cdk-stack-action.ts b/packages/@aws-cdk/pipelines/lib/legacy/actions/deploy-cdk-stack-action.ts index af6b7821a308d..1fc92da472ea1 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/actions/deploy-cdk-stack-action.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/actions/deploy-cdk-stack-action.ts @@ -16,6 +16,8 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Customization options for a DeployCdkStackAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface DeployCdkStackActionOptions { /** @@ -68,6 +70,8 @@ export interface DeployCdkStackActionOptions { /** * Properties for a DeployCdkStackAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface DeployCdkStackActionProps extends DeployCdkStackActionOptions { /** @@ -129,6 +133,8 @@ export interface DeployCdkStackActionProps extends DeployCdkStackActionOptions { /** * Options for the 'fromStackArtifact' operation + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface CdkStackActionFromArtifactOptions extends DeployCdkStackActionOptions { /** @@ -147,6 +153,8 @@ export interface CdkStackActionFromArtifactOptions extends DeployCdkStackActionO * * You do not need to instantiate this action yourself -- it will automatically * be added by the pipeline when you add stack artifacts or entire stages. + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class DeployCdkStackAction implements codepipeline.IAction { /** @@ -317,6 +325,8 @@ function roleFromPlaceholderArn(scope: Construct, region: string | undefined, /** * Options for CdkDeployAction.fromStackArtifact + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface FromStackArtifactOptions { /** diff --git a/packages/@aws-cdk/pipelines/lib/legacy/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/legacy/actions/publish-assets-action.ts index 1c112aec9afbe..055744cb971c2 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/actions/publish-assets-action.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/actions/publish-assets-action.ts @@ -17,6 +17,8 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Props for a PublishAssetsAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface PublishAssetsActionProps { /** @@ -111,6 +113,8 @@ export interface PublishAssetsActionProps { * * You do not need to instantiate this action -- it will automatically * be added by the pipeline when you add stacks that use assets. + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class PublishAssetsAction extends CoreConstruct implements codepipeline.IAction { private readonly action: codepipeline.IAction; diff --git a/packages/@aws-cdk/pipelines/lib/legacy/actions/update-pipeline-action.ts b/packages/@aws-cdk/pipelines/lib/legacy/actions/update-pipeline-action.ts index bc50e80c0aa56..da4278dd6faf2 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/actions/update-pipeline-action.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/actions/update-pipeline-action.ts @@ -14,6 +14,8 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Props for the UpdatePipelineAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface UpdatePipelineActionProps { /** @@ -79,6 +81,8 @@ export interface UpdatePipelineActionProps { * * You do not need to instantiate this action -- it will automatically * be added by the pipeline. + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class UpdatePipelineAction extends CoreConstruct implements codepipeline.IAction { private readonly action: codepipeline.IAction; diff --git a/packages/@aws-cdk/pipelines/lib/legacy/pipeline.ts b/packages/@aws-cdk/pipelines/lib/legacy/pipeline.ts index 60f158f298fce..ef0d1d209619b 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/pipeline.ts @@ -21,6 +21,8 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; const CODE_BUILD_LENGTH_LIMIT = 100; /** * Properties for a CdkPipeline + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface CdkPipelineProps { /** @@ -49,7 +51,7 @@ export interface CdkPipelineProps { * You can choose to not pass this value, in which case a new CodePipeline is * created with default settings. * - * If you pass an existing CodePipeline, it should should have been created + * If you pass an existing CodePipeline, it should have been created * with `restartExecutionOnUpdate: true`. * * [disable-awslint:ref-via-interface] @@ -206,6 +208,8 @@ export interface CdkPipelineProps { * - Asset publishing. * - Keeping the pipeline up-to-date as the CDK apps change. * - Using stack outputs later on in the pipeline. + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class CdkPipeline extends CoreConstruct { private readonly _pipeline: codepipeline.Pipeline; diff --git a/packages/@aws-cdk/pipelines/lib/legacy/stage.ts b/packages/@aws-cdk/pipelines/lib/legacy/stage.ts index bfb997e908196..c054e9a0592fb 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/stage.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/stage.ts @@ -20,6 +20,8 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Construction properties for a CdkStage + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface CdkStageProps { /** @@ -70,6 +72,8 @@ export interface CdkStageProps { * * You don't need to instantiate this class directly. Use * `cdkPipeline.addStage()` instead. + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class CdkStage extends CoreConstruct { private _nextSequentialRunOrder = 1; // Must start at 1 eh @@ -396,6 +400,8 @@ export class CdkStage extends CoreConstruct { /** * Additional options for adding a stack deployment + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface AddStackOptions { /** @@ -415,6 +421,8 @@ export interface AddStackOptions { /** * A single output of a Stack + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class StackOutput { /** @@ -448,6 +456,8 @@ function isAssetManifest(s: cxapi.CloudArtifact): s is cxapi.AssetManifestArtifa /** * Features that the Stage needs from its environment + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface IStageHost { /** @@ -463,6 +473,8 @@ export interface IStageHost { /** * Instructions to publish certain assets + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface AssetPublishingCommand { /** @@ -493,6 +505,8 @@ export interface AssetPublishingCommand { /** * Base options for a pipelines stage + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface BaseStageOptions { /** @@ -522,6 +536,8 @@ export interface BaseStageOptions { /** * Options for adding an application stage to a pipeline + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface AddStageOptions extends BaseStageOptions { /** @@ -546,6 +562,8 @@ export interface AddStageOptions extends BaseStageOptions { /** * Options for addManualApproval + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface AddManualApprovalOptions { /** diff --git a/packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts index 1fd0b96bb2db6..226a75d2ed23c 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts @@ -19,6 +19,8 @@ import { Construct } from '@aws-cdk/core'; /** * Configuration options for a SimpleSynth + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface SimpleSynthOptions { /** @@ -128,6 +130,8 @@ export interface SimpleSynthOptions { /** * Construction props for SimpleSynthAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface SimpleSynthActionProps extends SimpleSynthOptions { /** @@ -192,6 +196,8 @@ export interface SimpleSynthActionProps extends SimpleSynthOptions { /** * Specification of an additional artifact to generate + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface AdditionalArtifact { /** @@ -207,6 +213,8 @@ export interface AdditionalArtifact { /** * A standard synth with a generated buildspec + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { @@ -479,6 +487,8 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { /** * Options for a convention-based synth using NPM + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface StandardNpmSynthOptions extends SimpleSynthOptions { /** @@ -520,6 +530,8 @@ export interface StandardNpmSynthOptions extends SimpleSynthOptions { /** * Options for a convention-based synth using Yarn + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface StandardYarnSynthOptions extends SimpleSynthOptions { /** diff --git a/packages/@aws-cdk/pipelines/lib/legacy/validation/shell-script-action.ts b/packages/@aws-cdk/pipelines/lib/legacy/validation/shell-script-action.ts index 436f099d3c67d..78f223919cafd 100644 --- a/packages/@aws-cdk/pipelines/lib/legacy/validation/shell-script-action.ts +++ b/packages/@aws-cdk/pipelines/lib/legacy/validation/shell-script-action.ts @@ -13,6 +13,8 @@ import { Construct } from '@aws-cdk/core'; /** * Properties for ShellScriptAction + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export interface ShellScriptActionProps { /** @@ -122,6 +124,8 @@ export interface ShellScriptActionProps { /** * Validate a revision using shell commands + * + * @deprecated This class is part of the old API. Use the API based on the `CodePipeline` class instead */ export class ShellScriptAction implements codepipeline.IAction, iam.IGrantable { private _project?: codebuild.IProject; diff --git a/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts b/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts index d69c2a6c89ab1..2f90df9de6f1a 100644 --- a/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts +++ b/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts @@ -95,9 +95,11 @@ export abstract class PipelineBase extends CoreConstruct { * Example: * * ```ts + * declare const pipeline: pipelines.CodePipeline; + * * const wave = pipeline.addWave('MyWave'); - * wave.addStage(new MyStage('Stage1', ...)); - * wave.addStage(new MyStage('Stage2', ...)); + * wave.addStage(new MyApplicationStage(this, 'Stage1')); + * wave.addStage(new MyApplicationStage(this, 'Stage2')); * ``` */ public addWave(id: string, options?: WaveOptions) { diff --git a/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts b/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts index 5d52c1d5a47a9..454ab1b0b44ed 100644 --- a/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts +++ b/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { PolicyStatement } from '@aws-cdk/aws-iam'; -import { ConcreteDependable, Stack } from '@aws-cdk/core'; +import { ArnFormat, ConcreteDependable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; /** @@ -20,7 +20,7 @@ export class AssetSingletonRole extends iam.Role { resources: [Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: '/aws/codebuild/*', })], actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index 432dfba00c721..ec3538c30b4c9 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -41,7 +41,7 @@ "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "aws-sdk": "^2.848.0" }, "peerDependencies": { @@ -126,7 +126,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "awscdkio": { "announce": false diff --git a/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture b/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..61a973840f007 --- /dev/null +++ b/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture @@ -0,0 +1,29 @@ +// Fixture with packages imported, but nothing else +import { Construct, CfnOutput, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; +import cdk = require('@aws-cdk/core'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cpactions = require('@aws-cdk/aws-codepipeline-actions'); +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import dynamodb = require('@aws-cdk/aws-dynamodb'); +import ecr = require('@aws-cdk/aws-ecr'); +import ec2 = require('@aws-cdk/aws-ec2'); +import iam = require('@aws-cdk/aws-iam'); +import pipelines = require('@aws-cdk/pipelines'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import sns = require('@aws-cdk/aws-sns'); +import subscriptions = require('@aws-cdk/aws-sns-subscriptions'); +import s3 = require('@aws-cdk/aws-s3'); + +class MyApplicationStage extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + } +} + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +} diff --git a/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts b/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts index 52d63d00d64b3..358d22454a035 100644 --- a/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts +++ b/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts @@ -1,3 +1,4 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import { mkdict } from '../../lib/private/javascript'; import { PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, MegaAssetsApp, stackTemplate } from '../testhelpers'; @@ -8,73 +9,78 @@ let modernApp: TestApp; let legacyPipelineStack: Stack; let modernPipelineStack: Stack; -beforeEach(() => { - legacyApp = new TestApp({ - context: { - '@aws-cdk/core:newStyleStackSynthesis': '1', - 'aws:cdk:enable-path-metadata': true, - }, +describeDeprecated('logical id stability', () => { + // this test suite verifies logical id between the new and old (deprecated) APIs. + // so it must be in a 'describeDeprecated' block + + beforeEach(() => { + legacyApp = new TestApp({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + 'aws:cdk:enable-path-metadata': true, + }, + }); + modernApp = new TestApp({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + 'aws:cdk:enable-path-metadata': true, + }, + }); + legacyPipelineStack = new Stack(legacyApp, 'PipelineStack', { env: PIPELINE_ENV }); + modernPipelineStack = new Stack(modernApp, 'PipelineStack', { env: PIPELINE_ENV }); }); - modernApp = new TestApp({ - context: { - '@aws-cdk/core:newStyleStackSynthesis': '1', - 'aws:cdk:enable-path-metadata': true, - }, - }); - legacyPipelineStack = new Stack(legacyApp, 'PipelineStack', { env: PIPELINE_ENV }); - modernPipelineStack = new Stack(modernApp, 'PipelineStack', { env: PIPELINE_ENV }); -}); - -afterEach(() => { - legacyApp.cleanup(); - modernApp.cleanup(); -}); - -test('stateful or nameable resources have the same logicalID between old and new API', () => { - const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk'); - legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { - numAssets: 2, - })); - const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { - crossAccountKeys: true, + afterEach(() => { + legacyApp.cleanup(); + modernApp.cleanup(); }); - modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { - numAssets: 2, - })); - const legacyTemplate = stackTemplate(legacyPipelineStack).template; - const modernTemplate = stackTemplate(modernPipelineStack).template; + test('stateful or nameable resources have the same logicalID between old and new API', () => { + const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk'); + legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { + numAssets: 2, + })); - const legacyStateful = filterR(legacyTemplate.Resources, isStateful); - const modernStateful = filterR(modernTemplate.Resources, isStateful); + const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { + crossAccountKeys: true, + }); + modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { + numAssets: 2, + })); - expect(mapR(modernStateful, typeOfRes)).toEqual(mapR(legacyStateful, typeOfRes)); -}); + const legacyTemplate = stackTemplate(legacyPipelineStack).template; + const modernTemplate = stackTemplate(modernPipelineStack).template; -test('nameable resources have the same names between old and new API', () => { - const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk', { - pipelineName: 'asdf', - }); - legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { - numAssets: 2, - })); + const legacyStateful = filterR(legacyTemplate.Resources, isStateful); + const modernStateful = filterR(modernTemplate.Resources, isStateful); - const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { - pipelineName: 'asdf', - crossAccountKeys: true, + expect(mapR(modernStateful, typeOfRes)).toEqual(mapR(legacyStateful, typeOfRes)); }); - modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { - numAssets: 2, - })); - - const legacyTemplate = stackTemplate(legacyPipelineStack).template; - const modernTemplate = stackTemplate(modernPipelineStack).template; - const legacyNamed = filterR(legacyTemplate.Resources, hasName); - const modernNamed = filterR(modernTemplate.Resources, hasName); - - expect(mapR(modernNamed, nameProps)).toEqual(mapR(legacyNamed, nameProps)); + test('nameable resources have the same names between old and new API', () => { + const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk', { + pipelineName: 'asdf', + }); + legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { + numAssets: 2, + })); + + const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { + pipelineName: 'asdf', + crossAccountKeys: true, + }); + modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { + numAssets: 2, + })); + + const legacyTemplate = stackTemplate(legacyPipelineStack).template; + const modernTemplate = stackTemplate(modernPipelineStack).template; + + const legacyNamed = filterR(legacyTemplate.Resources, hasName); + const modernNamed = filterR(modernTemplate.Resources, hasName); + + expect(mapR(modernNamed, nameProps)).toEqual(mapR(legacyNamed, nameProps)); + }); }); diff --git a/packages/@aws-cdk/pipelines/test/blueprint/stack-deployment.test.ts b/packages/@aws-cdk/pipelines/test/blueprint/stack-deployment.test.ts index ee9d5b29240ce..68ca1f20a20b6 100644 --- a/packages/@aws-cdk/pipelines/test/blueprint/stack-deployment.test.ts +++ b/packages/@aws-cdk/pipelines/test/blueprint/stack-deployment.test.ts @@ -31,7 +31,7 @@ describe('templateUrl', () => { const sd = StageDeployment.fromStage(stage); // THEN - expect(sd.stacks[0].templateUrl).toBe('https://cdk-hnb659fds-assets-111-us-east-1.s3.us-east-1.amazonaws.com/4ef627170a212f66f5d1d9240d967ef306f4820ff9cb05b3a7ec703df6af6c3e.json'); + expect(sd.stacks[0].templateUrl).toBe('https://cdk-hnb659fds-assets-111-us-east-1.s3.us-east-1.amazonaws.com/93ae4de94f81d0905c37db64b7304f5d65233ca4d9581d3a32215743c9bb92dd.json'); }); test('without region', () => { @@ -43,7 +43,7 @@ describe('templateUrl', () => { const sd = StageDeployment.fromStage(stage); // THEN - expect(sd.stacks[0].templateUrl).toBe('https://cdk-hnb659fds-assets-111-.s3.amazonaws.com/$%7BAWS::Region%7D/4ef627170a212f66f5d1d9240d967ef306f4820ff9cb05b3a7ec703df6af6c3e.json'); + expect(sd.stacks[0].templateUrl).toBe('https://cdk-hnb659fds-assets-111-.s3.amazonaws.com/$%7BAWS::Region%7D/93ae4de94f81d0905c37db64b7304f5d65233ca4d9581d3a32215743c9bb92dd.json'); }); }); diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts new file mode 100644 index 0000000000000..166e6c9336dd1 --- /dev/null +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts @@ -0,0 +1,44 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import { Stack } from '@aws-cdk/core'; +import * as cdkp from '../../lib'; +import { PIPELINE_ENV, TestApp } from '../testhelpers'; + +let app: TestApp; +let pipelineStack: Stack; + +beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); +}); + +afterEach(() => { + app.cleanup(); +}); + +test('additionalinputs creates the right commands', () => { + // WHEN + new cdkp.CodePipeline(pipelineStack, 'Pipeline', { + synth: new cdkp.CodeBuildStep('Synth', { + commands: ['/bin/true'], + input: cdkp.CodePipelineSource.gitHub('test/test', 'main'), + additionalInputs: { + 'some/deep/directory': cdkp.CodePipelineSource.gitHub('test2/test2', 'main'), + }, + }), + }); + + // THEN + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + Source: { + BuildSpec: Match.serializedJson(Match.objectLike({ + phases: { + install: { + commands: [ + '[ ! -d "some/deep/directory" ] || { echo \'additionalInputs: "some/deep/directory" must not exist yet. If you want to merge multiple artifacts, use a "cp" command.\'; exit 1; } && mkdir -p -- "some/deep" && ln -s -- "$CODEBUILD_SRC_DIR_test2_test2_Source" "some/deep/directory"', + ], + }, + }, + })), + }, + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts index 09de231c8332c..42c951cfaed2d 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts @@ -1,45 +1,49 @@ import * as codePipeline from '@aws-cdk/aws-codepipeline'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cdkp from '../../lib'; -test('Does not allow setting a pipelineName if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); +describeDeprecated('codepipeline existing', () => { - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - pipelineName: 'CustomPipelineName', - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'pipelineName' if an existing CodePipeline is given using 'codePipeline'"); -}); + test('Does not allow setting a pipelineName if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); -test('Does not allow enabling crossAccountKeys if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + pipelineName: 'CustomPipelineName', + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'pipelineName' if an existing CodePipeline is given using 'codePipeline'"); + }); - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - crossAccountKeys: true, - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'crossAccountKeys' if an existing CodePipeline is given using 'codePipeline'"); -}); + test('Does not allow enabling crossAccountKeys if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); -test('Does not allow enabling key rotation if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + crossAccountKeys: true, + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'crossAccountKeys' if an existing CodePipeline is given using 'codePipeline'"); + }); - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - enableKeyRotation: true, - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'enableKeyRotation' if an existing CodePipeline is given using 'codePipeline'"); + test('Does not allow enabling key rotation if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + enableKeyRotation: true, + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'enableKeyRotation' if an existing CodePipeline is given using 'codePipeline'"); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts b/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts index 822a4f06f6164..9c7ec3ec6a41c 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts @@ -1,6 +1,7 @@ import { Match, Template } from '@aws-cdk/assertions'; import * as cp from '@aws-cdk/aws-codepipeline'; import * as cpa from '@aws-cdk/aws-codepipeline-actions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { SecretValue, Stack } from '@aws-cdk/core'; import * as cdkp from '../../lib'; import { CodePipelineFileSet } from '../../lib'; @@ -128,7 +129,7 @@ describe('with custom Source stage in existing Pipeline', () => { }); }); -describe('with Source and Build stages in existing Pipeline', () => { +describeDeprecated('with Source and Build stages in existing Pipeline', () => { beforeEach(() => { codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { stages: [ diff --git a/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts b/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts index f8e39a536309f..03ee0686f1807 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts @@ -748,7 +748,7 @@ behavior('Pipeline action contains a hash that changes as the buildspec changes' behavior('Synth CodeBuild project role can be granted permissions', (suite) => { let bucket: s3.IBucket; beforeEach(() => { - bucket = s3.Bucket.fromBucketArn(pipelineStack, 'Bucket', 'arn:aws:s3:::ThisParticularBucket'); + bucket = s3.Bucket.fromBucketArn(pipelineStack, 'Bucket', 'arn:aws:s3:::this-particular-bucket'); }); @@ -787,7 +787,7 @@ behavior('Synth CodeBuild project role can be granted permissions', (suite) => { PolicyDocument: { Statement: Match.arrayWith([Match.objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], - Resource: ['arn:aws:s3:::ThisParticularBucket', 'arn:aws:s3:::ThisParticularBucket/*'], + Resource: ['arn:aws:s3:::this-particular-bucket', 'arn:aws:s3:::this-particular-bucket/*'], })]), }, }); @@ -947,8 +947,8 @@ behavior('Multiple input sources in side-by-side directories', (suite) => { phases: { install: { commands: [ - 'ln -s -- "$CODEBUILD_SRC_DIR_foo_bar_Source" "../sibling"', - 'ln -s -- "$CODEBUILD_SRC_DIR_Prebuild_Output" "sub"', + '[ ! -d "../sibling" ] || { echo \'additionalInputs: "../sibling" must not exist yet. If you want to merge multiple artifacts, use a "cp" command.\'; exit 1; } && ln -s -- "$CODEBUILD_SRC_DIR_foo_bar_Source" "../sibling"', + '[ ! -d "sub" ] || { echo \'additionalInputs: "sub" must not exist yet. If you want to merge multiple artifacts, use a "cp" command.\'; exit 1; } && ln -s -- "$CODEBUILD_SRC_DIR_Prebuild_Output" "sub"', ], }, build: { diff --git a/packages/@aws-cdk/pipelines/test/compliance/validations.test.ts b/packages/@aws-cdk/pipelines/test/compliance/validations.test.ts index 7a6a562a8707a..c61cd40474388 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/validations.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/validations.test.ts @@ -463,7 +463,7 @@ behavior('can add policy statements to shell script action', (suite) => { behavior('can grant permissions to shell script action', (suite) => { let bucket: s3.IBucket; beforeEach(() => { - bucket = s3.Bucket.fromBucketArn(pipelineStack, 'Bucket', 'arn:aws:s3:::ThisParticularBucket'); + bucket = s3.Bucket.fromBucketArn(pipelineStack, 'Bucket', 'arn:aws:s3:::this-particular-bucket'); }); suite.legacy(() => { @@ -505,7 +505,7 @@ behavior('can grant permissions to shell script action', (suite) => { PolicyDocument: { Statement: Match.arrayWith([Match.objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], - Resource: ['arn:aws:s3:::ThisParticularBucket', 'arn:aws:s3:::ThisParticularBucket/*'], + Resource: ['arn:aws:s3:::this-particular-bucket', 'arn:aws:s3:::this-particular-bucket/*'], })]), }, }); diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json index e0075942e6069..80a17d7243cb1 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json @@ -2368,7 +2368,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json index 07e65b9bd643f..935ad4ce5136a 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json @@ -2303,7 +2303,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-security.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-security.expected.json index b72a78754a1d2..3d808a0f31767 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-security.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-security.expected.json @@ -2366,7 +2366,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json index 8250f113b53e3..85f20ecf8995f 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json @@ -1519,7 +1519,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { 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 c2e4cddc58aef..e236dbdb569ac 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 @@ -1576,7 +1576,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index 32a0a50bd90d5..0f158a1dffdf5 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -1299,7 +1299,7 @@ "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", - "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store." + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" } }, "Rules": { diff --git a/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts b/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts index bf6603d4753cb..9856797c2bd13 100644 --- a/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts +++ b/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts @@ -1,3 +1,5 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; + interface SkippedSuite { legacy(reason?: string): void; @@ -16,8 +18,9 @@ interface Suite { // eslint-disable-next-line jest/no-export export function behavior(name: string, cb: (suite: Suite) => void) { - // 'describe()' adds a nice grouping in Jest - describe(name, () => { + // Since the goal of the compliance test suites is to compare modern and legacy (i.e. deprecated) APIs, + // use `describeDeprecated()` block here since usage of the legacy API is inevitable. + describeDeprecated(name, () => { const unwritten = new Set(['modern', 'legacy']); function scratchOff(flavor: string) { 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 28d61f7fd9387..6d95f24e5a7e8 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -43,7 +43,7 @@ export const AWS_CDK_METADATA = new Set([ /** * The hosted zone Id if using an alias record in Route53. * - * @see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints + * @see https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_region */ export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { 'af-south-1': 'Z11KHD8FBVPUYU', @@ -55,6 +55,7 @@ export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { 'ap-southeast-1': 'Z3O0J2DXBE1FTB', 'ap-southeast-2': 'Z1WCIGYICN2BYD', 'ca-central-1': 'Z1QDHH18159H29', + 'cn-northwest-1': 'Z282HJ1KT0DH03', 'eu-central-1': 'Z21DNDUVLTQW6Q', 'eu-north-1': 'Z3BAZG2TWCNX0D', 'eu-south-1': 'Z3IXVV8C73GIO3', @@ -71,6 +72,37 @@ export const ROUTE_53_BUCKET_WEBSITE_ZONE_IDS: { [region: string]: string } = { 'us-west-2': 'Z3BJ6K6RIION7M', }; +/** + * The hosted zone Id of the Elastic Beanstalk environment. + * + * @see https://docs.aws.amazon.com/general/latest/gr/elasticbeanstalk.html + */ +export const EBS_ENV_ENDPOINT_HOSTED_ZONE_IDS: { [region: string]: string } = { + 'af-south-1': 'Z1EI3BVKMKK4AM', + 'ap-east-1': 'ZPWYUBWRU171A', + 'ap-northeast-1': 'Z1R25G3KIG2GBW', + 'ap-northeast-2': 'Z3JE5OI70TWKCP', + 'ap-northeast-3': 'ZNE5GEY1TIAGY', + 'ap-south-1': 'Z18NTBI3Y7N9TZ', + 'ap-southeast-1': 'Z16FZ9L249IFLT', + 'ap-southeast-2': 'Z2PCDNR3VC2G1N', + 'ca-central-1': 'ZJFCZL7SSZB5I', + 'eu-central-1': 'Z1FRNW7UH4DEZJ', + 'eu-north-1': 'Z23GO28BZ5AETM', + 'eu-south-1': 'Z10VDYYOA2JFKM', + 'eu-west-1': 'Z2NYPWQ7DFZAZH', + 'eu-west-2': 'Z1GKAAAUGATPF1', + 'eu-west-3': 'Z5WN6GAYWG5OB', + 'me-south-1': 'Z2BBTEKR2I36N2', + 'sa-east-1': 'Z10X7K2B4QSOFV', + 'us-east-1': 'Z117KPS5GTRQ2G', + 'us-east-2': 'Z14LCN19Q5QHIC', + 'us-gov-east-1': 'Z35TSARG0EJ4VU', + 'us-gov-west-1': 'Z4KAURWC4UUUG', + 'us-west-1': 'Z1LQECGX5PH1X', + 'us-west-2': 'Z38NKT9BP95V3O', +}; + interface Region { partition: string, domainSuffix: string } export const PARTITION_MAP: { [region: string]: Region } = { @@ -135,6 +167,7 @@ export const DLC_REPOSITORY_ACCOUNTS: { [region: string]: string } = { }; // https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html +// https://docs.amazonaws.cn/app-mesh/latest/userguide/envoy.html export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { 'af-south-1': '924023996002', 'ap-east-1': '856666278305', @@ -145,6 +178,8 @@ export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { 'ap-southeast-1': '840364872350', 'ap-southeast-2': '840364872350', 'ca-central-1': '840364872350', + 'cn-north-1': '919366029133', + 'cn-northwest-1': '919830735681', 'eu-central-1': '840364872350', 'eu-north-1': '840364872350', 'eu-south-1': '422531588944', diff --git a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts index dd42309b78c8a..006de89d3994d 100644 --- a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts +++ b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts @@ -4,7 +4,7 @@ import { Default } from '../lib/default'; import { AWS_REGIONS, AWS_SERVICES } from './aws-entities'; import { APPMESH_ECR_ACCOUNTS, AWS_CDK_METADATA, AWS_OLDER_REGIONS, CLOUDWATCH_LAMBDA_INSIGHTS_ARNS, DLC_REPOSITORY_ACCOUNTS, - ELBV2_ACCOUNTS, FIREHOSE_CIDR_BLOCKS, PARTITION_MAP, ROUTE_53_BUCKET_WEBSITE_ZONE_IDS, + ELBV2_ACCOUNTS, FIREHOSE_CIDR_BLOCKS, PARTITION_MAP, ROUTE_53_BUCKET_WEBSITE_ZONE_IDS, EBS_ENV_ENDPOINT_HOSTED_ZONE_IDS, } from './fact-tables'; async function main(): Promise { @@ -57,6 +57,9 @@ async function main(): Promise { registerFact(region, 'S3_STATIC_WEBSITE_ZONE_53_HOSTED_ZONE_ID', ROUTE_53_BUCKET_WEBSITE_ZONE_IDS[region] || ''); + registerFact(region, 'EBS_ENV_ENDPOINT_HOSTED_ZONE_ID', EBS_ENV_ENDPOINT_HOSTED_ZONE_IDS[region] || ''); + + registerFact(region, 'ELBV2_ACCOUNT', ELBV2_ACCOUNTS[region]); registerFact(region, 'DLC_REPOSITORY_ACCOUNT', DLC_REPOSITORY_ACCOUNTS[region]); diff --git a/packages/@aws-cdk/region-info/lib/default.ts b/packages/@aws-cdk/region-info/lib/default.ts index abd1001678bf2..c0ae0cc2b28d5 100644 --- a/packages/@aws-cdk/region-info/lib/default.ts +++ b/packages/@aws-cdk/region-info/lib/default.ts @@ -82,6 +82,7 @@ export class Default { // Services with a regional principal case 'states': + case 'ssm': return `${service}.${region}.amazonaws.com`; // Services with a partitional principal diff --git a/packages/@aws-cdk/region-info/lib/fact.ts b/packages/@aws-cdk/region-info/lib/fact.ts index c099aced9fd55..8d4e33802be43 100644 --- a/packages/@aws-cdk/region-info/lib/fact.ts +++ b/packages/@aws-cdk/region-info/lib/fact.ts @@ -128,6 +128,11 @@ export class FactName { */ public static readonly S3_STATIC_WEBSITE_ZONE_53_HOSTED_ZONE_ID = 's3-static-website:route-53-hosted-zone-id'; + /** + * The hosted zone ID used by Route 53 to alias a EBS environment endpoint in this region (e.g: Z2O1EMRO9K5GLX) + */ + public static readonly EBS_ENV_ENDPOINT_HOSTED_ZONE_ID = 'ebs-environment:route-53-hosted-zone-id'; + /** * The prefix for VPC Endpoint Service names, * cn.com.amazonaws.vpce for China regions, diff --git a/packages/@aws-cdk/region-info/lib/region-info.ts b/packages/@aws-cdk/region-info/lib/region-info.ts index baa2c43635892..3482acf66b9a1 100644 --- a/packages/@aws-cdk/region-info/lib/region-info.ts +++ b/packages/@aws-cdk/region-info/lib/region-info.ts @@ -77,6 +77,13 @@ export class RegionInfo { return Fact.find(this.name, FactName.S3_STATIC_WEBSITE_ZONE_53_HOSTED_ZONE_ID); } + /** + * The hosted zone ID used by Route 53 to alias a EBS environment endpoint in this region (e.g: Z2O1EMRO9K5GLX) + */ + public get ebsEnvEndpointHostedZoneId(): string | undefined { + return Fact.find(this.name, FactName.EBS_ENV_ENDPOINT_HOSTED_ZONE_ID); + } + /** * The prefix for VPC Endpoint Service names, * cn.com.amazonaws.vpce for China regions, diff --git a/packages/@aws-cdk/region-info/package.json b/packages/@aws-cdk/region-info/package.json index a9e67db6c3be7..d78a75e84d00d 100644 --- a/packages/@aws-cdk/region-info/package.json +++ b/packages/@aws-cdk/region-info/package.json @@ -56,7 +56,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "fs-extra": "^9.1.0" }, "repository": { diff --git a/packages/@aws-cdk/region-info/test/default.test.ts b/packages/@aws-cdk/region-info/test/default.test.ts index b39952842d75a..1e7c1b166c9b6 100644 --- a/packages/@aws-cdk/region-info/test/default.test.ts +++ b/packages/@aws-cdk/region-info/test/default.test.ts @@ -5,7 +5,7 @@ const urlSuffix = '.nowhere.null'; describe('servicePrincipal', () => { for (const suffix of ['', '.amazonaws.com', '.amazonaws.com.cn']) { - for (const service of ['states']) { + for (const service of ['states', 'ssm']) { test(`${service}${suffix}`, () => { expect(Default.servicePrincipal(`${service}${suffix}`, region, urlSuffix)).toBe(`${service}.${region}.amazonaws.com`); }); diff --git a/packages/@aws-cdk/yaml-cfn/package.json b/packages/@aws-cdk/yaml-cfn/package.json index 3a2a674586d84..2b125cf83c224 100644 --- a/packages/@aws-cdk/yaml-cfn/package.json +++ b/packages/@aws-cdk/yaml-cfn/package.json @@ -72,9 +72,9 @@ "@aws-cdk/assert-internal": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yaml": "^1.9.7", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "bundledDependencies": [ "yaml" diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 4c6bff0534016..4c973ee798190 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -34,21 +34,21 @@ "license": "Apache-2.0", "devDependencies": { "@monocdk-experiment/rewrite-imports": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/node": "^10.17.60", "@aws-cdk/cdk-build-tools": "0.0.0", "constructs": "^3.3.69", - "jest": "^26.6.3", + "jest": "^27.3.1", "monocdk": "0.0.0", "@aws-cdk/pkglint": "0.0.0", - "ts-jest": "^26.5.6" + "ts-jest": "^27.0.7" }, "dependencies": { "@aws-cdk/cloudformation-diff": "0.0.0" }, "peerDependencies": { "constructs": "^3.3.69", - "jest": "^26.6.3", + "jest": "^27.3.1", "monocdk": "^0.0.0" }, "repository": { diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index 7f602376d551a..a9d9e2dd0d77d 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -35,8 +35,8 @@ "typescript": "~3.9.10" }, "devDependencies": { - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", "@types/node": "^10.17.60", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0" diff --git a/packages/aws-cdk-lib/.gitignore b/packages/aws-cdk-lib/.gitignore index 2d239b69afc3a..4802b31b55523 100644 --- a/packages/aws-cdk-lib/.gitignore +++ b/packages/aws-cdk-lib/.gitignore @@ -1,20 +1,13 @@ -*.js -*.d.ts -!deps.js -!gen.js -lib/ -tsconfig.json -.jsii -*.tsbuildinfo +# Ignore everything (because we're going to be generating into this +# directory) except certain source files. + +* +!LICENSE +!NOTICE +!README.md +!scripts/* -dist -.LAST_PACKAGE .LAST_BUILD *.snk -!.eslintrc.js - -# Ignore barrel import entry points -/*.ts - junit.xml -rosetta +!.eslintrc.js \ No newline at end of file diff --git a/packages/aws-cdk-lib/NOTICE b/packages/aws-cdk-lib/NOTICE index df1053512a80b..cae673dc859a5 100644 --- a/packages/aws-cdk-lib/NOTICE +++ b/packages/aws-cdk-lib/NOTICE @@ -373,441 +373,6 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires - -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** lodash.clonedeep - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - ** kubectl - https://github.com/kubernetes/kubectl Copyright 2017 The Kubernetes Authors. diff --git a/packages/aws-cdk-lib/README.md b/packages/aws-cdk-lib/README.md index 48d4cd65f93a5..1de8bb1a9fb41 100644 --- a/packages/aws-cdk-lib/README.md +++ b/packages/aws-cdk-lib/README.md @@ -81,7 +81,7 @@ logical application. You can then treat that new unit the same way you used to be able to treat a single stack: by instantiating it multiple times for different instances of your application. -You can define a custom subclass of `Construct`, holding one or more +You can define a custom subclass of `Stage`, holding one or more `Stack`s, to represent a single logical instance of your application. As a final note: `Stack`s are not a unit of reuse. They describe physical @@ -205,6 +205,13 @@ Duration.days(7) // 7 days Duration.parse('PT5M') // 5 minutes ``` +Durations can be added or subtracted together: + +```ts +Duration.minutes(1).plus(Duration.seconds(60)); // 2 minutes +Duration.minutes(5).minus(Duration.seconds(10)); // 290 secondes +``` + ## Size (Digital Information Quantity) To make specification of digital storage quantities unambiguous, a class called @@ -263,6 +270,8 @@ this purpose. use the region and account of the stack you're calling it on: ```ts +declare const stack: Stack; + // Builds "arn::lambda:::function:MyFunction" stack.formatArn({ service: 'lambda', @@ -278,6 +287,8 @@ but in case of a deploy-time value be aware that the result will be another deploy-time value which cannot be inspected in the CDK application. ```ts +declare const stack: Stack; + // Extracts the function name out of an AWS Lambda Function ARN const arnComponents = stack.parseArn(arn, ':'); const functionName = arnComponents.resourceName; @@ -409,7 +420,11 @@ examples ensures that only a single SNS topic is defined: function getOrCreate(scope: Construct): sns.Topic { const stack = Stack.of(scope); const uniqueid = 'GloballyUniqueIdForSingleton'; // For example, a UUID from `uuidgen` - return stack.node.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); + const existing = stack.node.tryFindChild(uniqueid); + if (existing) { + return existing as sns.Topic; + } + return new sns.Topic(stack, uniqueid); } ``` @@ -786,16 +801,19 @@ CloudFormation [mappings][cfn-mappings] are created and queried using the ```ts const regionTable = new CfnMapping(this, 'RegionTable', { mapping: { - regionName: { - 'us-east-1': 'US East (N. Virginia)', - 'us-east-2': 'US East (Ohio)', + 'us-east-1': { + regionName: 'US East (N. Virginia)', + // ... + }, + 'us-east-2': { + regionName: 'US East (Ohio)', // ... }, // ... } }); -regionTable.findInMap('regionName', Aws.REGION); +regionTable.findInMap(Aws.REGION, 'regionName') ``` This will yield the following template: @@ -803,9 +821,10 @@ This will yield the following template: ```yaml Mappings: RegionTable: - regionName: - us-east-1: US East (N. Virginia) - us-east-2: US East (Ohio) + us-east-1: + regionName: US East (N. Virginia) + us-east-2: + regionName: US East (Ohio) ``` Mappings can also be synthesized "lazily"; lazy mappings will only render a "Mappings" @@ -820,24 +839,27 @@ call to `findInMap` will be able to resolve the value during synthesis and simpl ```ts const regionTable = new CfnMapping(this, 'RegionTable', { mapping: { - regionName: { - 'us-east-1': 'US East (N. Virginia)', - 'us-east-2': 'US East (Ohio)', + 'us-east-1': { + regionName: 'US East (N. Virginia)', + }, + 'us-east-2': { + regionName: 'US East (Ohio)', }, }, lazy: true, }); -regionTable.findInMap('regionName', 'us-east-2'); +regionTable.findInMap('us-east-2', 'regionName'); ``` On the other hand, the following code will produce the "Mappings" section shown above, -since the second-level key is an unresolved token. The call to `findInMap` will return a -token that resolves to `{ Fn::FindInMap: [ 'RegionTable', 'regionName', { Ref: AWS::Region -} ] }`. +since the top-level key is an unresolved token. The call to `findInMap` will return a token that resolves to +`{ "Fn::FindInMap": [ "RegionTable", { "Ref": "AWS::Region" }, "regionName" ] }`. ```ts -regionTable.findInMap('regionName', Aws.REGION); +declare const regionTable: CfnMapping; + +regionTable.findInMap(Aws.REGION, 'regionName'); ``` [cfn-mappings]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index ad2aeeace6cd1..1449ca3bd050e 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -3,8 +3,8 @@ "private": "true", "version": "0.0.0", "description": "The AWS Cloud Development Kit library", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git", @@ -38,7 +38,8 @@ }, "stripDeprecated": true, "post": [ - "node ./scripts/verify-readme-import-rewrites.js" + "node ./scripts/verify-readme-import-rewrites.js", + "node ./scripts/verify-import-resolve-same.js" ] }, "cdk-package": { @@ -94,33 +95,23 @@ "bundledDependencies": [ "@balena/dockerignore", "case", - "colors", - "diff", - "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", - "string-width", - "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "colors": "^1.4.0", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", - "ignore": "^5.1.8", + "ignore": "^5.1.9", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", - "string-width": "^4.2.3", - "table": "^6.7.2", "yaml": "1.10.2" }, "devDependencies": { @@ -231,6 +222,7 @@ "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-iot-actions": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", "@aws-cdk/aws-iotanalytics": "0.0.0", "@aws-cdk/aws-iotcoredeviceadvisor": "0.0.0", @@ -279,6 +271,7 @@ "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-opsworks": "0.0.0", "@aws-cdk/aws-opsworkscm": "0.0.0", + "@aws-cdk/aws-panorama": "0.0.0", "@aws-cdk/aws-pinpoint": "0.0.0", "@aws-cdk/aws-pinpointemail": "0.0.0", "@aws-cdk/aws-qldb": "0.0.0", @@ -286,6 +279,7 @@ "@aws-cdk/aws-ram": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-redshift": "0.0.0", + "@aws-cdk/aws-rekognition": "0.0.0", "@aws-cdk/aws-resourcegroups": "0.0.0", "@aws-cdk/aws-robomaker": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", @@ -326,6 +320,7 @@ "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0", + "@aws-cdk/aws-wisdom": "0.0.0", "@aws-cdk/aws-workspaces": "0.0.0", "@aws-cdk/aws-xray": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", diff --git a/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts b/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts new file mode 100644 index 0000000000000..9d30bf5c291ac --- /dev/null +++ b/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts @@ -0,0 +1,142 @@ +/* eslint-disable no-console */ +/** + * Verify that the two styles of imports we support: + * + * import { aws_ec2 } from 'aws-cdk-lib'; + * import * as aws_ec2 from 'aws-cdk-lib/aws-ec2'; + * + * Resolve to the same source file when analyzed using the TypeScript compiler. + * + * This is necessary for Rosetta's analysis and translation of examples: we need + * to know what submodule we're importing here, and we need to be able to deal + * with both styles since both are used interchangeably. + */ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ts from 'typescript'; + +async function main() { + // First make a tempdir and symlink `aws-cdk-lib` into it so we can refer to it + // as if it was an installed module. + await withTemporaryDirectory(async (tmpDir) => { + await fs.mkdirp(path.join(tmpDir, 'node_modules')); + await fs.symlink(path.resolve(__dirname, '..'), path.join(tmpDir, 'node_modules', 'aws-cdk-lib')); + + const import1 = 'import { aws_ec2 } from "aws-cdk-lib";'; + const import2 = 'import * as aws_ec2 from "aws-cdk-lib/aws-ec2";'; + + const src1 = await compileAndResolve(path.join(tmpDir, 'program1.ts'), import1, 'aws_ec2'); + const src2 = await compileAndResolve(path.join(tmpDir, 'program2.ts'), import2, 'aws_ec2'); + + if (src1 !== src2) { + console.error('Import mismatch!'); + console.error('\n ', import1, '\n'); + console.error('resolves to', src1); + console.error('\n ', import2, '\n'); + console.error('resolves to', src2); + process.exitCode = 1; + } + }); +} + +async function compileAndResolve(fileName: string, contents: string, symbolName: string) { + await fs.writeFile(fileName, contents + `\n\nconsole.log(${symbolName});`, { encoding: 'utf-8' }); + const program = ts.createProgram({ rootNames: [fileName], options: STANDARD_COMPILER_OPTIONS }); + + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + throw new Error(`Could not find sourcefile back: ${fileName}`); + } + + const diags = [ + ...program.getGlobalDiagnostics(), + ...program.getDeclarationDiagnostics(sourceFile), + ...program.getSyntacticDiagnostics(sourceFile), + ...program.getSemanticDiagnostics(sourceFile), + ]; + if (diags.length > 0) { + console.error(ts.formatDiagnostics(diags, { + getNewLine: () => '\n', + getCurrentDirectory: () => path.dirname(fileName), + getCanonicalFileName: (f) => path.resolve(f), + })); + throw new Error('Compilation failed'); + } + + // Find the 'console.log()' back and resolve the symbol inside + const logStmt = assertNode(sourceFile.statements[1], ts.isExpressionStatement); + const logCall = assertNode(logStmt.expression, ts.isCallExpression); + const ident = assertNode(logCall.arguments[0], ts.isIdentifier); + + let sym = program.getTypeChecker().getSymbolAtLocation(ident); + + // Resolve alias if applicable + // eslint-disable-next-line no-bitwise + while (sym && ((sym.flags & ts.SymbolFlags.Alias) !== 0)) { + sym = program.getTypeChecker().getAliasedSymbol(sym); + } + + if (!sym) { + throw new Error(`Could not resolve: ${symbolName} in '${contents}'`); + } + + // Return the filename + const srcFile = sym.declarations?.[0].getSourceFile().fileName; + if (!srcFile) { + console.log(sym); + throw new Error(`Symbol ${symbolName} in '${contents}' does not resolve to a source location`); + } + return srcFile; +} + +export async function withTemporaryDirectory(callback: (dir: string) => Promise): Promise { + const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), path.basename(__filename))); + try { + return await callback(tmpdir); + } finally { + await fs.remove(tmpdir); + } +} + +function assertNode(x: ts.Node, assert: (x: ts.Node) => x is A): A { + if (!assert(x)) { + throw new Error(`Not the right type of node, expecting ${assert.name}, got ${ts.SyntaxKind[x.kind]}`); + } + return x; +} + +export const STANDARD_COMPILER_OPTIONS: ts.CompilerOptions = { + alwaysStrict: true, + charset: 'utf8', + declaration: true, + experimentalDecorators: true, + inlineSourceMap: true, + inlineSources: true, + lib: ['lib.es2016.d.ts', 'lib.es2017.object.d.ts', 'lib.es2017.string.d.ts'], + module: ts.ModuleKind.CommonJS, + noEmitOnError: true, + noFallthroughCasesInSwitch: true, + noImplicitAny: true, + noImplicitReturns: true, + noImplicitThis: true, + noUnusedLocals: false, // Important, becomes super annoying without this + noUnusedParameters: false, // Important, becomes super annoying without this + resolveJsonModule: true, + strict: true, + strictNullChecks: true, + strictPropertyInitialization: true, + stripInternal: true, + target: ts.ScriptTarget.ES2019, + // Incremental builds + incremental: true, + tsBuildInfoFile: '.tsbuildinfo', +}; + +main().catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); diff --git a/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts b/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts index a31f721c05dfc..a2e3569573ef4 100644 --- a/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts +++ b/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts @@ -23,7 +23,7 @@ const jsiiManifest = JSON.parse(fs.readFileSync(jsiiManifestPath, { encoding: 'u // If this test fails because one of the below import statements is invalid, // please update to have a new, comparable example. // This is admittedly a bit fragile; if this test breaks a lot, we should reconsider validation methodology. -// Count of times this test has been broken by README updates so far (please increment as necessary! :D): 0 +// Count of times this test has been broken by README updates so far (please increment as necessary! :D): 1 const EXPECTED_SUBMODULE_IMPORTS = { // import * as origins from '@aws-cdk/aws-cloudfront-origins'; 'aws-cdk-lib.aws_cloudfront_origins': "import { aws_cloudfront_origins as origins } from 'aws-cdk-lib';", @@ -34,7 +34,7 @@ const EXPECTED_SUBMODULE_IMPORTS = { // import { Rule, Schedule } from '@aws-cdk/aws-events'; 'aws-cdk-lib.aws_events': "import { Rule, Schedule } from 'aws-cdk-lib/aws-events';", // import * as cdk from '@aws-cdk/core'; - 'aws-cdk-lib.aws_stepfunctions': "import * as cdk from 'aws-cdk-lib';", + 'aws-cdk-lib.aws_rds': "import * as cdk from 'aws-cdk-lib';", }; Object.entries(EXPECTED_SUBMODULE_IMPORTS).forEach(([submodule, importStatement]) => { diff --git a/packages/aws-cdk-migration/package.json b/packages/aws-cdk-migration/package.json index 1ce16c5670458..4b1fa845c3229 100644 --- a/packages/aws-cdk-migration/package.json +++ b/packages/aws-cdk-migration/package.json @@ -38,8 +38,8 @@ "typescript": "~3.9.10" }, "devDependencies": { - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", "@types/node": "^10.17.60", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0" diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 3a1b2882b2506..1bd9e491e34e4 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -362,6 +362,8 @@ Hotswapping is currently supported for the following changes (additional changes will be supported in the future): - Code asset changes of AWS Lambda functions. +- Definition changes of AWS Step Functions State Machines. +- Container asset changes of AWS ECS Services. **⚠ Note #1**: This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments. For this reason, only use it for development purposes. @@ -370,6 +372,69 @@ For this reason, only use it for development purposes. **⚠ Note #2**: This command is considered experimental, and might have breaking changes in the future. +### `cdk watch` + +The `watch` command is similar to `deploy`, +but instead of being a one-shot operation, +the command continuously monitors the files of the project, +and triggers a deployment whenever it detects any changes: + +```console +$ cdk watch DevelopmentStack +Detected change to 'lambda-code/index.js' (type: change). Triggering 'cdk deploy' +DevelopmentStack: deploying... + + ✅ DevelopmentStack + +^C +``` + +To end a `cdk watch` session, interrupt the process by pressing Ctrl+C. + +What files are observed is determined by the `"watch"` setting in your `cdk.json` file. +It has two sub-keys, `"include"` and `"exclude"`, each of which can be either a single string, or an array of strings. +Each entry is interpreted as a path relative to the location of the `cdk.json` file. +Globs, both `*` and `**`, are allowed to be used. +Example: + +```json +{ + "app": "mvn -e -q compile exec:java", + "watch": { + "include": "src/main/**", + "exclude": "target/*" + } +} +``` + +The default for `"include"` is `"**/*"` +(which means all files and directories in the root of the project), +and `"exclude"` is optional +(note that we always ignore files and directories starting with `.`, +the CDK output directory, and the `node_modules` directory), +so the minimal settings to enable `watch` are `"watch": {}`. + +If either your CDK code, or application code, needs a build step before being deployed, +`watch` works with the `"build"` key in the `cdk.json` file, +for example: + +```json +{ + "app": "mvn -e -q exec:java", + "build": "mvn package", + "watch": { + "include": "src/main/**", + "exclude": "target/*" + } +} +``` + +Note that `watch` by default uses hotswap deployments (see above for details) -- +to turn them off, pass the `--no-hotswap` option when invoking it. + +**Note**: This command is considered experimental, +and might have breaking changes in the future. + ### `cdk destroy` Deletes a stack from it's environment. This will cause the resources in the stack to be destroyed (unless they were @@ -462,6 +527,7 @@ Some of the interesting keys that can be used in the JSON configuration files: ```json5 { "app": "node bin/main.js", // Command to start the CDK app (--app='node bin/main.js') + "build": "mvn package", // Specify pre-synth build (no command line option) "context": { // Context entries (--context=key=value) "key": "value" }, @@ -471,6 +537,12 @@ Some of the interesting keys that can be used in the JSON configuration files: } ``` +If specified, the command in the `build` key will be executed immediately before synthesis. +This can be used to build Lambda Functions, CDK Application code, or other assets. +`build` cannot be specified on the command line or in the User configuration, +and must be specified in the Project configuration. The command specified +in `build` will be executed by the "watch" process before deployment. + ### Environment The following environment variables affect aws-cdk: diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index aaaef0deb3182..3f90dfc8ff3e2 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -77,7 +77,7 @@ async function parseCommandLineArguments() { .option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined }) .option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined, conflicts: 'bootstrap-customer-key' }) .option('bootstrap-customer-key', { type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', default: undefined, conflicts: 'bootstrap-kms-key-id' }) - .option('qualifier', { type: 'string', desc: 'Unique string to distinguish multiple bootstrap stacks', default: undefined }) + .option('qualifier', { type: 'string', desc: 'String which must be unique for each bootstrap stack. You must configure it on your CDK app if you change this from the default.', default: undefined }) .option('public-access-block-configuration', { type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', default: undefined }) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true }) @@ -105,16 +105,58 @@ async function parseCommandLineArguments() { .option('outputs-file', { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true }) .option('previous-parameters', { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' }) .option('progress', { type: 'string', choices: [StackActivityProgress.BAR, StackActivityProgress.EVENTS], desc: 'Display mode for stack activity events' }) - .option('rollback', { type: 'boolean', desc: 'Rollback stack to stable state on failure (iterate more rapidly with --no-rollback or -R)' }) + .option('rollback', { + type: 'boolean', + desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. " + + 'Note: do **not** disable this flag for deployments with resource replacements, as that will always fail', + }) // Hack to get '-R' as an alias for '--no-rollback', suggested by: https://github.com/yargs/yargs/issues/1729 - .option('R', { type: 'boolean', hidden: true }) - .middleware(yargsNegativeAlias('R', 'rollback'), true) + .option('R', { type: 'boolean', hidden: true }).middleware(yargsNegativeAlias('R', 'rollback'), true) .option('hotswap', { type: 'boolean', desc: "Attempts to perform a 'hotswap' deployment, " + 'which skips CloudFormation and updates the resources directly, ' + 'and falls back to a full deployment if that is not possible. ' + 'Do not use this in production environments', + }) + .option('watch', { + type: 'boolean', + desc: 'Continuously observe the project files, ' + + 'and deploy the given stack(s) automatically when changes are detected. ' + + 'Implies --hotswap by default', + }), + ) + .command('watch [STACKS..]', "Shortcut for 'deploy --watch'", yargs => yargs + // I'm fairly certain none of these options, present for 'deploy', make sense for 'watch': + // .option('all', { type: 'boolean', default: false, desc: 'Deploy all available stacks' }) + // .option('ci', { type: 'boolean', desc: 'Force CI detection', default: process.env.CI !== undefined }) + // @deprecated(v2) -- tags are part of the Cloud Assembly and tags specified here will be overwritten on the next deployment + // .option('tags', { type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)', nargs: 1, requiresArg: true }) + // .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true }) + // These options, however, are more subtle - I could be convinced some of these should also be available for 'watch': + // .option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'What security-sensitive changes need manual approval' }) + // .option('parameters', { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', nargs: 1, requiresArg: true, default: {} }) + // .option('previous-parameters', { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' }) + // .option('outputs-file', { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true }) + // .option('notification-arns', { type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events', nargs: 1, requiresArg: true }) + .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] }) + .option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only deploy requested stacks, don\'t include dependencies' }) + .option('change-set-name', { type: 'string', desc: 'Name of the CloudFormation change set to create' }) + .option('force', { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false }) + .option('progress', { type: 'string', choices: [StackActivityProgress.BAR, StackActivityProgress.EVENTS], desc: 'Display mode for stack activity events' }) + .option('rollback', { + type: 'boolean', + desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. " + + 'Note: do **not** disable this flag for deployments with resource replacements, as that will always fail', + }) + // same hack for -R as above in 'deploy' + .option('R', { type: 'boolean', hidden: true }).middleware(yargsNegativeAlias('R', 'rollback'), true) + .option('hotswap', { + type: 'boolean', + desc: "Attempts to perform a 'hotswap' deployment, " + + 'which skips CloudFormation and updates the resources directly, ' + + 'and falls back to a full deployment if that is not possible. ' + + "'true' by default, use --no-hotswap to turn off", }), ) .command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs @@ -332,6 +374,26 @@ async function initCommandLine() { ci: args.ci, rollback: configuration.settings.get(['rollback']), hotswap: args.hotswap, + watch: args.watch, + }); + + case 'watch': + return cli.watch({ + selector, + // parameters: parameterMap, + // usePreviousParameters: args['previous-parameters'], + // outputsFile: configuration.settings.get(['outputsFile']), + // requireApproval: configuration.settings.get(['requireApproval']), + // notificationArns: args.notificationArns, + exclusively: args.exclusively, + toolkitStackName, + roleArn: args.roleArn, + reuseAssets: args['build-exclude'], + changeSetName: args.changeSetName, + force: args.force, + progress: configuration.settings.get(['progress']), + rollback: configuration.settings.get(['rollback']), + hotswap: args.hotswap, }); case 'destroy': diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index b409274efa59c..eb141768b9644 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -177,12 +177,18 @@ async function isEc2Instance() { let instance = false; if (process.platform === 'win32') { // https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/identify_ec2_instances.html - const result = await util.promisify(child_process.exec)('wmic path win32_computersystemproduct get uuid', { encoding: 'utf-8' }); - // output looks like - // UUID - // EC2AE145-D1DC-13B2-94ED-01234ABCDEF - const lines = result.stdout.toString().split('\n'); - instance = lines.some(x => matchesRegex(/^ec2/i, x)); + try { + const result = await util.promisify(child_process.exec)('wmic path win32_computersystemproduct get uuid', { encoding: 'utf-8' }); + // output looks like + // UUID + // EC2AE145-D1DC-13B2-94ED-01234ABCDEF + const lines = result.stdout.toString().split('\n'); + instance = lines.some(x => matchesRegex(/^ec2/i, x)); + } catch (e) { + // Modern machines may not have wmic.exe installed. No reason to fail, just assume it's not an EC2 instance. + debug(`Checking using WMIC failed, assuming NOT an EC2 instance: ${e.message} (pass --ec2creds to force)`); + instance = false; + } } else { // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html const files: Array<[string, RegExp]> = [ diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index 0f1468b4d08b8..bfd30f4324a31 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -5,6 +5,20 @@ import { cached } from '../../util/functions'; import { AccountAccessKeyCache } from './account-cache'; import { Account } from './sdk-provider'; +// We need to map regions to domain suffixes, and the SDK already has a function to do this. +// It's not part of the public API, but it's also unlikely to go away. +// +// Reuse that function, and add a safety check so we don't accidentally break if they ever +// refactor that away. + +/* eslint-disable @typescript-eslint/no-require-imports */ +const regionUtil = require('aws-sdk/lib/region_config'); +/* eslint-enable @typescript-eslint/no-require-imports */ + +if (!regionUtil.getEndpointSuffix) { + throw new Error('This version of AWS SDK for JS does not have the \'getEndpointSuffix\' function!'); +} + export interface ISDK { /** * The region this SDK has been instantiated for @@ -22,6 +36,19 @@ export interface ISDK { */ currentAccount(): Promise; + getEndpointSuffix(region: string): string; + + /** + * Appends the given string as the extra information to put into the User-Agent header for any requests invoked by this SDK. + * If the string is 'undefined', this method has no effect. + */ + appendCustomUserAgent(userAgentData?: string): void; + + /** + * Removes the given string from the extra User-Agent header data used for requests invoked by this SDK. + */ + removeCustomUserAgent(userAgentData: string): void; + lambda(): AWS.Lambda; cloudFormation(): AWS.CloudFormation; ec2(): AWS.EC2; @@ -29,9 +56,11 @@ export interface ISDK { s3(): AWS.S3; route53(): AWS.Route53; ecr(): AWS.ECR; + ecs(): AWS.ECS; elbv2(): AWS.ELBv2; secretsManager(): AWS.SecretsManager; kms(): AWS.KMS; + stepFunctions(): AWS.StepFunctions; } /** @@ -85,6 +114,21 @@ export class SDK implements ISDK { this.currentRegion = region; } + public appendCustomUserAgent(userAgentData?: string): void { + if (!userAgentData) { + return; + } + + const currentCustomUserAgent = this.config.customUserAgent; + this.config.customUserAgent = currentCustomUserAgent + ? `${currentCustomUserAgent} ${userAgentData}` + : userAgentData; + } + + public removeCustomUserAgent(userAgentData: string): void { + this.config.customUserAgent = this.config.customUserAgent?.replace(userAgentData, ''); + } + public lambda(): AWS.Lambda { return this.wrapServiceErrorHandling(new AWS.Lambda(this.config)); } @@ -116,6 +160,10 @@ export class SDK implements ISDK { return this.wrapServiceErrorHandling(new AWS.ECR(this.config)); } + public ecs(): AWS.ECS { + return this.wrapServiceErrorHandling(new AWS.ECS(this.config)); + } + public elbv2(): AWS.ELBv2 { return this.wrapServiceErrorHandling(new AWS.ELBv2(this.config)); } @@ -128,6 +176,10 @@ export class SDK implements ISDK { return this.wrapServiceErrorHandling(new AWS.KMS(this.config)); } + public stepFunctions(): AWS.StepFunctions { + return this.wrapServiceErrorHandling(new AWS.StepFunctions(this.config)); + } + public async currentAccount(): Promise { // Get/refresh if necessary before we can access `accessKeyId` await this.forceCredentialRetrieval(); @@ -180,6 +232,10 @@ export class SDK implements ISDK { } } + public getEndpointSuffix(region: string): string { + return regionUtil.getEndpointSuffix(region); + } + /** * Return a wrapping object for the underlying service object * diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 4d66e59268dcc..49f97e71332c3 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -65,7 +65,7 @@ export class BootstrapStack { const newVersion = bootstrapVersionFromTemplate(template); if (this.currentToolkitInfo.found && newVersion < this.currentToolkitInfo.version && !options.force) { - throw new Error(`Not downgrading existing bootstrap stack from version '${this.currentToolkitInfo.version}' to version '${newVersion}'. Use --force to force.`); + throw new Error(`Not downgrading existing bootstrap stack from version '${this.currentToolkitInfo.version}' to version '${newVersion}'. Use --force to force or set the '@aws-cdk/core:newStyleStackSynthesis' feature flag in cdk.json to use the latest bootstrap version.`); } const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap')); @@ -114,4 +114,4 @@ export function bootstrapVersionFromTemplate(template: any): number { } } return 0; -} \ No newline at end of file +} diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index 914efd51cd574..fb7c5410faf3d 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -142,9 +142,16 @@ export interface DeployStackOptions { * A 'hotswap' deployment will attempt to short-circuit CloudFormation * and update the affected resources like Lambda functions directly. * - * @default - false (do not perform a 'hotswap' deployment) + * @default - false for regular deployments, true for 'watch' deployments */ readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; } export interface DestroyStackOptions { @@ -222,6 +229,7 @@ export class CloudFormationDeployments { ci: options.ci, rollback: options.rollback, hotswap: options.hotswap, + extraUserAgent: options.extraUserAgent, }); } diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts index eeebec8e90f44..9bbb607de44fb 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts @@ -46,10 +46,14 @@ export class CloudExecutable { } /** - * Synthesize a set of stacks + * Synthesize a set of stacks. + * + * @param cacheCloudAssembly whether to cache the Cloud Assembly after it has been first synthesized. + * This is 'true' by default, and only set to 'false' for 'cdk watch', + * which needs to re-synthesize the Assembly each time it detects a change to the project files */ - public async synthesize(): Promise { - if (!this._cloudAssembly) { + public async synthesize(cacheCloudAssembly: boolean = true): Promise { + if (!this._cloudAssembly || !cacheCloudAssembly) { this._cloudAssembly = await this.doSynthesize(); } return this._cloudAssembly; diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index facaf24a3bfe0..bcc298deaeea2 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -46,6 +46,11 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom debug('context:', context); env[cxapi.CONTEXT_ENV] = JSON.stringify(context); + const build = config.settings.get(['build']); + if (build) { + await exec(build); + } + const app = config.settings.get(['app']); if (!app) { throw new Error(`--app is required either in command-line, in ${PROJECT_CONFIG} or in ${USER_DEFAULTS}`); @@ -74,7 +79,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom debug('env:', env); - await exec(); + await exec(commandLine.join(' ')); return createAssembly(outdir); @@ -91,7 +96,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom } } - async function exec() { + async function exec(commandAndArgs: string) { return new Promise((ok, fail) => { // We use a slightly lower-level interface to: // @@ -103,7 +108,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom // anyway, and if the subprocess is printing to it for debugging purposes the // user gets to see it sooner. Plus, capturing doesn't interact nicely with some // processes like Maven. - const proc = childProcess.spawn(commandLine[0], commandLine.slice(1), { + const proc = childProcess.spawn(commandAndArgs, { stdio: ['ignore', 'inherit', 'inherit'], detached: false, shell: true, diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 4888d639ff394..40719507d6e20 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -14,24 +14,10 @@ import { CfnEvaluationException } from './hotswap/evaluate-cloudformation-templa import { ToolkitInfo } from './toolkit-info'; import { changeSetHasNoChanges, CloudFormationStack, TemplateParameters, waitForChangeSet, - waitForStackDeploy, waitForStackDelete, ParameterValues, + waitForStackDeploy, waitForStackDelete, ParameterValues, ParameterChanges, } from './util/cloudformation'; import { StackActivityMonitor, StackActivityProgress } from './util/cloudformation/stack-activity-monitor'; -// We need to map regions to domain suffixes, and the SDK already has a function to do this. -// It's not part of the public API, but it's also unlikely to go away. -// -// Reuse that function, and add a safety check so we don't accidentally break if they ever -// refactor that away. - -/* eslint-disable @typescript-eslint/no-require-imports */ -const regionUtil = require('aws-sdk/lib/region_config'); -/* eslint-enable @typescript-eslint/no-require-imports */ - -if (!regionUtil.getEndpointSuffix) { - throw new Error('This version of AWS SDK for JS does not have the \'getEndpointSuffix\' function!'); -} - type TemplateBodyParameter = { TemplateBody?: string TemplateURL?: string @@ -193,9 +179,16 @@ export interface DeployStackOptions { * A 'hotswap' deployment will attempt to short-circuit CloudFormation * and update the affected resources like Lambda functions directly. * - * @default - false (do not perform a 'hotswap' deployment) + * @default - false for regular deployments, true for 'watch' deployments */ readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; } const LARGE_TEMPLATE_SIZE_KB = 50; @@ -205,6 +198,7 @@ export async function deployStack(options: DeployStackOptions): Promise { + // if we got here, and hotswap is enabled, that means changes couldn't be hotswapped, + // and we had to fall back on a full deployment. Note that fact in our User-Agent + if (options.hotswap) { + options.sdk.appendCustomUserAgent('cdk-hotswap/fallback'); + } + const cfn = options.sdk.cloudFormation(); const deployName = options.deployName ?? stackArtifact.stackName; @@ -332,7 +331,7 @@ async function prepareAndExecuteChangeSet( if (execute) { debug('Initiating execution of changeset %s on stack %s', changeSet.Id, deployName); - const shouldDisableRollback = (options.rollback === undefined && options.hotswap === true) || options.rollback === false; + const shouldDisableRollback = options.rollback === false; // Do a bit of contortions to only pass the `DisableRollback` flag if it's true. That way, // CloudFormation won't balk at the unrecognized option in regions where the feature is not available yet. const disableRollback = shouldDisableRollback ? { DisableRollback: true } : undefined; @@ -382,11 +381,12 @@ async function makeBodyParameter( stack: cxapi.CloudFormationStackArtifact, resolvedEnvironment: cxapi.Environment, assetManifest: AssetManifestBuilder, - toolkitInfo: ToolkitInfo): Promise { + toolkitInfo: ToolkitInfo, + sdk: ISDK): Promise { // If the template has already been uploaded to S3, just use it from there. if (stack.stackTemplateAssetObjectUrl) { - return { TemplateURL: restUrlFromManifest(stack.stackTemplateAssetObjectUrl, resolvedEnvironment) }; + return { TemplateURL: restUrlFromManifest(stack.stackTemplateAssetObjectUrl, resolvedEnvironment, sdk) }; } // Otherwise, pass via API call (if small) or upload here (if large) @@ -466,7 +466,7 @@ export async function destroyStack(options: DestroyStackOptions) { async function canSkipDeploy( deployStackOptions: DeployStackOptions, cloudFormationStack: CloudFormationStack, - parameterChanges: boolean): Promise { + parameterChanges: ParameterChanges): Promise { const deployName = deployStackOptions.deployName || deployStackOptions.stack.stackName; debug(`${deployName}: checking if we can skip deploy`); @@ -509,7 +509,11 @@ async function canSkipDeploy( // Parameters have changed if (parameterChanges) { - debug(`${deployName}: parameters have changed`); + if (parameterChanges === 'ssm') { + debug(`${deployName}: some parameters come from SSM so we have to assume they may have changed`); + } else { + debug(`${deployName}: parameters have changed`); + } return false; } @@ -549,7 +553,7 @@ function compareTags(a: Tag[], b: Tag[]): boolean { * and reformats s3://.../... urls into S3 REST URLs (which CloudFormation * expects) */ -function restUrlFromManifest(url: string, environment: cxapi.Environment): string { +function restUrlFromManifest(url: string, environment: cxapi.Environment, sdk: ISDK): string { const doNotUseMarker = '**DONOTUSE**'; // This URL may contain placeholders, so still substitute those. url = cxapi.EnvironmentPlaceholders.replace(url, { @@ -572,6 +576,6 @@ function restUrlFromManifest(url: string, environment: cxapi.Environment): strin const bucketName = s3Url[1]; const objectKey = s3Url[2]; - const urlSuffix: string = regionUtil.getEndpointSuffix(environment.region); + const urlSuffix: string = sdk.getEndpointSuffix(environment.region); return `https://s3.${environment.region}.${urlSuffix}/${bucketName}/${objectKey}`; } diff --git a/packages/aws-cdk/lib/api/hotswap-deployments.ts b/packages/aws-cdk/lib/api/hotswap-deployments.ts index 5812e50a605ef..0d8a4165f9401 100644 --- a/packages/aws-cdk/lib/api/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/hotswap-deployments.ts @@ -3,9 +3,11 @@ import * as cxapi from '@aws-cdk/cx-api'; import { CloudFormation } from 'aws-sdk'; import { ISDK, Mode, SdkProvider } from './aws-auth'; import { DeployStackResult } from './deploy-stack'; -import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, ListStackResources } from './hotswap/common'; +import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, ListStackResources } from './hotswap/common'; +import { isHotswappableEcsServiceChange } from './hotswap/ecs-services'; import { EvaluateCloudFormationTemplate } from './hotswap/evaluate-cloudformation-template'; import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions'; +import { isHotswappableStateMachineChange } from './hotswap/stepfunctions-state-machines'; import { CloudFormationStack } from './util/cloudformation'; /** @@ -33,10 +35,8 @@ export async function tryHotswapDeployment( parameters: assetParams, account: resolvedEnv.account, region: resolvedEnv.region, - // ToDo make this better: - partition: 'aws', - // ToDo make this better: - urlSuffix: 'amazonaws.com', + partition: (await sdk.currentAccount()).partition, + urlSuffix: sdk.getEndpointSuffix, listStackResources, }); @@ -57,34 +57,103 @@ export async function tryHotswapDeployment( async function findAllHotswappableChanges( stackChanges: cfn_diff.TemplateDiff, evaluateCfnTemplate: EvaluateCloudFormationTemplate, ): Promise { - const promises = new Array>(); - stackChanges.resources.forEachDifference(async (logicalId: string, change: cfn_diff.ResourceDifference) => { - promises.push(isHotswappableLambdaFunctionChange(logicalId, change, evaluateCfnTemplate)); + let foundNonHotswappableChange = false; + const promises: Array>> = []; + + // gather the results of the detector functions + stackChanges.resources.forEachDifference((logicalId: string, change: cfn_diff.ResourceDifference) => { + const resourceHotswapEvaluation = isCandidateForHotswapping(change); + + if (resourceHotswapEvaluation === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) { + foundNonHotswappableChange = true; + } else if (resourceHotswapEvaluation === ChangeHotswapImpact.IRRELEVANT) { + // empty 'if' just for flow-aware typing to kick in... + } else { + promises.push([ + isHotswappableLambdaFunctionChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), + isHotswappableStateMachineChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), + isHotswappableEcsServiceChange(logicalId, resourceHotswapEvaluation, evaluateCfnTemplate), + ]); + } }); - return Promise.all(promises).then(hotswapDetectionResults => { - const hotswappableResources = new Array(); - let foundNonHotswappableChange = false; - for (const lambdaFunctionShortCircuitChange of hotswapDetectionResults) { - if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) { + + const changesDetectionResults: Array> = []; + for (const detectorResultPromises of promises) { + const hotswapDetectionResults = await Promise.all(detectorResultPromises); + changesDetectionResults.push(hotswapDetectionResults); + } + + const hotswappableResources = new Array(); + + // resolve all detector results + for (const hotswapDetectionResults of changesDetectionResults) { + const perChangeHotswappableResources = new Array(); + + for (const result of hotswapDetectionResults) { + if (typeof result !== 'string') { + perChangeHotswappableResources.push(result); + } + } + + // if we found any hotswappable changes, return now + if (perChangeHotswappableResources.length > 0) { + hotswappableResources.push(...perChangeHotswappableResources); + continue; + } + + // no hotswappable changes found, so any REQUIRES_FULL_DEPLOYMENTs imply a non-hotswappable change + for (const result of hotswapDetectionResults) { + if (result === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) { foundNonHotswappableChange = true; - } else if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.IRRELEVANT) { - // empty 'if' just for flow-aware typing to kick in... - } else { - hotswappableResources.push(lambdaFunctionShortCircuitChange); } } - return foundNonHotswappableChange ? undefined : hotswappableResources; - }); + // no REQUIRES_FULL_DEPLOYMENT implies that all results are IRRELEVANT + } + + return foundNonHotswappableChange ? undefined : hotswappableResources; +} + +/** + * returns `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if a resource was deleted, or a change that we cannot short-circuit occured. + * Returns `ChangeHotswapImpact.IRRELEVANT` if a change that does not impact shortcircuiting occured, such as a metadata change. + */ +export function isCandidateForHotswapping(change: cfn_diff.ResourceDifference): HotswappableChangeCandidate | ChangeHotswapImpact { + // a resource has been removed OR a resource has been added; we can't short-circuit that change + if (!change.newValue || !change.oldValue) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + // Ignore Metadata changes + if (change.newValue.Type === 'AWS::CDK::Metadata') { + return ChangeHotswapImpact.IRRELEVANT; + } + + return { + newValue: change.newValue, + propertyUpdates: change.propertyUpdates, + }; } async function applyAllHotswappableChanges( sdk: ISDK, hotswappableChanges: HotswapOperation[], ): Promise { return Promise.all(hotswappableChanges.map(hotswapOperation => { - return hotswapOperation.apply(sdk); + return applyHotswappableChange(sdk, hotswapOperation); })); } +async function applyHotswappableChange(sdk: ISDK, hotswapOperation: HotswapOperation): Promise { + // note the type of service that was successfully hotswapped in the User-Agent + const customUserAgent = `cdk-hotswap/success-${hotswapOperation.service}`; + sdk.appendCustomUserAgent(customUserAgent); + + try { + return await hotswapOperation.apply(sdk); + } finally { + sdk.removeCustomUserAgent(customUserAgent); + } +} + class LazyListStackResources implements ListStackResources { private stackResources: CloudFormation.StackResourceSummary[] | undefined; diff --git a/packages/aws-cdk/lib/api/hotswap/common.ts b/packages/aws-cdk/lib/api/hotswap/common.ts index c11b29d1d7daa..5ac336e692624 100644 --- a/packages/aws-cdk/lib/api/hotswap/common.ts +++ b/packages/aws-cdk/lib/api/hotswap/common.ts @@ -1,6 +1,7 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff'; import { CloudFormation } from 'aws-sdk'; import { ISDK } from '../aws-auth'; +import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; export interface ListStackResources { listStackResources(): Promise; @@ -10,6 +11,12 @@ export interface ListStackResources { * An interface that represents a change that can be deployed in a short-circuit manner. */ export interface HotswapOperation { + /** + * The name of the service being hotswapped. + * Used to set a custom User-Agent for SDK calls. + */ + readonly service: string; + apply(sdk: ISDK): Promise; } @@ -33,6 +40,43 @@ export enum ChangeHotswapImpact { export type ChangeHotswapResult = HotswapOperation | ChangeHotswapImpact; -export function assetMetadataChanged(change: cfn_diff.ResourceDifference): boolean { +/** + * Represents a change that can be hotswapped. + */ +export class HotswappableChangeCandidate { + /** + * The value the resource is being updated to. + */ + public readonly newValue: cfn_diff.Resource; + + /** + * The changes made to the resource properties. + */ + public readonly propertyUpdates: { [key: string]: cfn_diff.PropertyDifference }; + + public constructor(newValue: cfn_diff.Resource, propertyUpdates: { [key: string]: cfn_diff.PropertyDifference }) { + this.newValue = newValue; + this.propertyUpdates = propertyUpdates; + } +} + +export async function establishResourcePhysicalName( + logicalId: string, physicalNameInCfnTemplate: any, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + if (physicalNameInCfnTemplate != null) { + try { + return await evaluateCfnTemplate.evaluateCfnExpression(physicalNameInCfnTemplate); + } catch (e) { + // If we can't evaluate the resource's name CloudFormation expression, + // just look it up in the currently deployed Stack + if (!(e instanceof CfnEvaluationException)) { + throw e; + } + } + } + return evaluateCfnTemplate.findPhysicalNameFor(logicalId); +} + +export function assetMetadataChanged(change: HotswappableChangeCandidate): boolean { return !!change.newValue?.Metadata['aws:asset:path']; } diff --git a/packages/aws-cdk/lib/api/hotswap/ecs-services.ts b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts new file mode 100644 index 0000000000000..90cee85782989 --- /dev/null +++ b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts @@ -0,0 +1,189 @@ +import * as AWS from 'aws-sdk'; +import { ISDK } from '../aws-auth'; +import { ChangeHotswapImpact, ChangeHotswapResult, establishResourcePhysicalName, HotswapOperation, HotswappableChangeCandidate } from './common'; +import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; + +export async function isHotswappableEcsServiceChange( + logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + // the only resource change we should allow is an ECS TaskDefinition + if (change.newValue.Type !== 'AWS::ECS::TaskDefinition') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + for (const updatedPropName in change.propertyUpdates) { + // We only allow a change in the ContainerDefinitions of the TaskDefinition for now - + // it contains the image and environment variables, so seems like a safe bet for now. + // We might revisit this decision in the future though! + if (updatedPropName !== 'ContainerDefinitions') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + const containerDefinitionsDifference = (change.propertyUpdates)[updatedPropName]; + if (containerDefinitionsDifference.newValue === undefined) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + } + // at this point, we know the TaskDefinition can be hotswapped + + // find all ECS Services that reference the TaskDefinition that changed + const resourcesReferencingTaskDef = evaluateCfnTemplate.findReferencesTo(logicalId); + const ecsServiceResourcesReferencingTaskDef = resourcesReferencingTaskDef.filter(r => r.Type === 'AWS::ECS::Service'); + const ecsServicesReferencingTaskDef = new Array(); + for (const ecsServiceResource of ecsServiceResourcesReferencingTaskDef) { + const serviceArn = await evaluateCfnTemplate.findPhysicalNameFor(ecsServiceResource.LogicalId); + if (serviceArn) { + ecsServicesReferencingTaskDef.push({ serviceArn }); + } + } + if (ecsServicesReferencingTaskDef.length === 0 || + resourcesReferencingTaskDef.length > ecsServicesReferencingTaskDef.length) { + // if there are either no resources referencing the TaskDefinition, + // or something besides an ECS Service is referencing it, + // hotswap is not possible + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + const taskDefinitionResource = change.newValue.Properties; + // first, let's get the name of the family + const familyNameOrArn = await establishResourcePhysicalName(logicalId, taskDefinitionResource?.Family, evaluateCfnTemplate); + if (!familyNameOrArn) { + // if the Family property has not bee provided, and we can't find it in the current Stack, + // this means hotswapping is not possible + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + // the physical name of the Task Definition in CloudFormation includes its current revision number at the end, + // remove it if needed + const familyNameOrArnParts = familyNameOrArn.split(':'); + const family = familyNameOrArnParts.length > 1 + // familyNameOrArn is actually an ARN, of the format 'arn:aws:ecs:region:account:task-definition/:' + // so, take the 6th element, at index 5, and split it on '/' + ? familyNameOrArnParts[5].split('/')[1] + // otherwise, familyNameOrArn is just the simple name evaluated from the CloudFormation template + : familyNameOrArn; + // then, let's evaluate the body of the remainder of the TaskDef (without the Family property) + const evaluatedTaskDef = { + ...await evaluateCfnTemplate.evaluateCfnExpression({ + ...(taskDefinitionResource ?? {}), + Family: undefined, + }), + Family: family, + }; + return new EcsServiceHotswapOperation(evaluatedTaskDef, ecsServicesReferencingTaskDef); +} + +interface EcsService { + readonly serviceArn: string; +} + +class EcsServiceHotswapOperation implements HotswapOperation { + public readonly service = 'ecs-service'; + + constructor( + private readonly taskDefinitionResource: any, + private readonly servicesReferencingTaskDef: EcsService[], + ) {} + + public async apply(sdk: ISDK): Promise { + // Step 1 - update the changed TaskDefinition, creating a new TaskDefinition Revision + // we need to lowercase the evaluated TaskDef from CloudFormation, + // as the AWS SDK uses lowercase property names for these + const lowercasedTaskDef = lowerCaseFirstCharacterOfObjectKeys(this.taskDefinitionResource); + const registerTaskDefResponse = await sdk.ecs().registerTaskDefinition(lowercasedTaskDef).promise(); + const taskDefRevArn = registerTaskDefResponse.taskDefinition?.taskDefinitionArn; + + // Step 2 - update the services using that TaskDefinition to point to the new TaskDefinition Revision + const servicePerClusterUpdates: { [cluster: string]: Array<{ promise: Promise, ecsService: EcsService }> } = {}; + for (const ecsService of this.servicesReferencingTaskDef) { + const clusterName = ecsService.serviceArn.split('/')[1]; + + const existingClusterPromises = servicePerClusterUpdates[clusterName]; + let clusterPromises: Array<{ promise: Promise, ecsService: EcsService }>; + if (existingClusterPromises) { + clusterPromises = existingClusterPromises; + } else { + clusterPromises = []; + servicePerClusterUpdates[clusterName] = clusterPromises; + } + + clusterPromises.push({ + promise: sdk.ecs().updateService({ + service: ecsService.serviceArn, + taskDefinition: taskDefRevArn, + cluster: clusterName, + forceNewDeployment: true, + deploymentConfiguration: { + minimumHealthyPercent: 0, + }, + }).promise(), + ecsService: ecsService, + }); + } + await Promise.all(Object.values(servicePerClusterUpdates) + .map(clusterUpdates => { + return Promise.all(clusterUpdates.map(serviceUpdate => serviceUpdate.promise)); + }), + ); + + // Step 3 - wait for the service deployments triggered in Step 2 to finish + // configure a custom Waiter + (sdk.ecs() as any).api.waiters.deploymentToFinish = { + name: 'DeploymentToFinish', + operation: 'describeServices', + delay: 10, + maxAttempts: 60, + acceptors: [ + { + matcher: 'pathAny', + argument: 'failures[].reason', + expected: 'MISSING', + state: 'failure', + }, + { + matcher: 'pathAny', + argument: 'services[].status', + expected: 'DRAINING', + state: 'failure', + }, + { + matcher: 'pathAny', + argument: 'services[].status', + expected: 'INACTIVE', + state: 'failure', + }, + { + matcher: 'path', + argument: "length(services[].deployments[? status == 'PRIMARY' && runningCount < desiredCount][]) == `0`", + expected: true, + state: 'success', + }, + ], + }; + // create a custom Waiter that uses the deploymentToFinish configuration added above + const deploymentWaiter = new (AWS as any).ResourceWaiter(sdk.ecs(), 'deploymentToFinish'); + // wait for all of the waiters to finish + return Promise.all(Object.entries(servicePerClusterUpdates).map(([clusterName, serviceUpdates]) => { + return deploymentWaiter.wait({ + cluster: clusterName, + services: serviceUpdates.map(serviceUpdate => serviceUpdate.ecsService.serviceArn), + }).promise(); + })); + } +} + +function lowerCaseFirstCharacterOfObjectKeys(val: any): any { + if (val == null || typeof val !== 'object') { + return val; + } + if (Array.isArray(val)) { + return val.map(lowerCaseFirstCharacterOfObjectKeys); + } + const ret: { [k: string]: any; } = {}; + for (const [k, v] of Object.entries(val)) { + ret[lowerCaseFirstCharacter(k)] = lowerCaseFirstCharacterOfObjectKeys(v); + } + return ret; +} + +function lowerCaseFirstCharacter(str: string): string { + return str.length > 0 ? `${str[0].toLowerCase()}${str.substr(1)}` : str; +} diff --git a/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts index dc1541ed74771..5dc9f948a4250 100644 --- a/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts +++ b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts @@ -4,36 +4,45 @@ import { ListStackResources } from './common'; export class CfnEvaluationException extends Error {} +export interface ResourceDefinition { + readonly LogicalId: string; + readonly Type: string; + readonly Properties: { [p: string]: any }; +} + export interface EvaluateCloudFormationTemplateProps { readonly stackArtifact: cxapi.CloudFormationStackArtifact; readonly parameters: { [parameterName: string]: string }; readonly account: string; readonly region: string; readonly partition: string; - readonly urlSuffix: string; - + readonly urlSuffix: (region: string) => string; readonly listStackResources: ListStackResources; } export class EvaluateCloudFormationTemplate { private readonly stackResources: ListStackResources; + private readonly template: { [section: string]: { [headings: string]: any } }; private readonly context: { [k: string]: string }; private readonly account: string; private readonly region: string; private readonly partition: string; + private readonly urlSuffix: (region: string) => string; + private cachedUrlSuffix: string | undefined; constructor(props: EvaluateCloudFormationTemplateProps) { this.stackResources = props.listStackResources; + this.template = props.stackArtifact.template; this.context = { 'AWS::AccountId': props.account, 'AWS::Region': props.region, 'AWS::Partition': props.partition, - 'AWS::URLSuffix': props.urlSuffix, ...props.parameters, }; this.account = props.account; this.region = props.region; this.partition = props.partition; + this.urlSuffix = props.urlSuffix; } public async findPhysicalNameFor(logicalId: string): Promise { @@ -41,6 +50,19 @@ export class EvaluateCloudFormationTemplate { return stackResources.find(sr => sr.LogicalResourceId === logicalId)?.PhysicalResourceId; } + public findReferencesTo(logicalId: string): Array { + const ret = new Array(); + for (const [resourceLogicalId, resourceDef] of Object.entries(this.template?.Resources ?? {})) { + if (logicalId !== resourceLogicalId && this.references(logicalId, resourceDef)) { + ret.push({ + ...(resourceDef as any), + LogicalId: resourceLogicalId, + }); + } + } + return ret; + } + public async evaluateCfnExpression(cfnExpression: any): Promise { const self = this; class CfnIntrinsics { @@ -131,6 +153,26 @@ export class EvaluateCloudFormationTemplate { return cfnExpression; } + private references(logicalId: string, templateElement: any): boolean { + if (typeof templateElement === 'string') { + return logicalId === templateElement; + } + + if (templateElement == null) { + return false; + } + + if (Array.isArray(templateElement)) { + return templateElement.some(el => this.references(logicalId, el)); + } + + if (typeof templateElement === 'object') { + return Object.values(templateElement).some(el => this.references(logicalId, el)); + } + + return false; + } + private parseIntrinsic(x: any): Intrinsic | undefined { const keys = Object.keys(x); if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { @@ -144,6 +186,14 @@ export class EvaluateCloudFormationTemplate { private async findRefTarget(logicalId: string): Promise { // first, check to see if the Ref is a Parameter who's value we have + if (logicalId === 'AWS::URLSuffix') { + if (!this.cachedUrlSuffix) { + this.cachedUrlSuffix = this.urlSuffix(this.region); + } + + return this.cachedUrlSuffix; + } + const parameterTarget = this.context[logicalId]; if (parameterTarget) { return parameterTarget; diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index c110a66b6c9db..c45bb432ac65d 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,7 +1,6 @@ -import * as cfn_diff from '@aws-cdk/cloudformation-diff'; import { ISDK } from '../aws-auth'; -import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation } from './common'; -import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; +import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, establishResourcePhysicalName } from './common'; +import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; /** * Returns `false` if the change cannot be short-circuited, @@ -10,7 +9,7 @@ import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evalua * or a LambdaFunctionResource if the change can be short-circuited. */ export async function isHotswappableLambdaFunctionChange( - logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, + logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, ): Promise { const lambdaCodeChange = await isLambdaFunctionCodeOnlyChange(change, evaluateCfnTemplate); if (typeof lambdaCodeChange === 'string') { @@ -24,7 +23,7 @@ export async function isHotswappableLambdaFunctionChange( return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } - const functionName = await establishFunctionPhysicalName(logicalId, change, evaluateCfnTemplate); + const functionName = await establishResourcePhysicalName(logicalId, change.newValue.Properties?.FunctionName, evaluateCfnTemplate); if (!functionName) { return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } @@ -37,34 +36,21 @@ export async function isHotswappableLambdaFunctionChange( } /** - * Returns `true` if the change is not for a AWS::Lambda::Function, + * Returns `ChangeHotswapImpact.IRRELEVANT` if the change is not for a AWS::Lambda::Function, * but doesn't prevent short-circuiting * (like a change to CDKMetadata resource), - * `false` if the change is to a AWS::Lambda::Function, + * `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if the change is to a AWS::Lambda::Function, * but not only to its Code property, * or a LambdaFunctionCode if the change is to a AWS::Lambda::Function, * and only affects its Code property. */ async function isLambdaFunctionCodeOnlyChange( - change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, + change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, ): Promise { - if (!change.newValue) { - return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; - } const newResourceType = change.newValue.Type; - // Ignore Metadata changes - if (newResourceType === 'AWS::CDK::Metadata') { - return ChangeHotswapImpact.IRRELEVANT; - } - // The only other resource change we should see is a Lambda function if (newResourceType !== 'AWS::Lambda::Function') { return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } - if (change.oldValue?.Type == null) { - // this means this is a brand-new Lambda function - - // obviously, we can't short-circuit that! - return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; - } /* * On first glance, we would want to initialize these using the "previous" values (change.oldValue), @@ -83,9 +69,6 @@ async function isLambdaFunctionCodeOnlyChange( const propertyUpdates = change.propertyUpdates; for (const updatedPropName in propertyUpdates) { const updatedProp = propertyUpdates[updatedPropName]; - if (updatedProp.newValue === undefined) { - return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; - } for (const newPropName in updatedProp.newValue) { switch (newPropName) { case 'S3Bucket': @@ -121,6 +104,8 @@ interface LambdaFunctionResource { } class LambdaFunctionHotswapOperation implements HotswapOperation { + public readonly service = 'lambda-function'; + constructor(private readonly lambdaFunctionResource: LambdaFunctionResource) { } @@ -132,21 +117,3 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { }).promise(); } } - -async function establishFunctionPhysicalName( - logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, -): Promise { - const functionNameInCfnTemplate = change.newValue?.Properties?.FunctionName; - if (functionNameInCfnTemplate != null) { - try { - return await evaluateCfnTemplate.evaluateCfnExpression(functionNameInCfnTemplate); - } catch (e) { - // If we can't evaluate the function's name CloudFormation expression, - // just look it up in the currently deployed Stack - if (!(e instanceof CfnEvaluationException)) { - throw e; - } - } - } - return evaluateCfnTemplate.findPhysicalNameFor(logicalId); -} diff --git a/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts new file mode 100644 index 0000000000000..dbaff58ab608b --- /dev/null +++ b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts @@ -0,0 +1,64 @@ +import { ISDK } from '../aws-auth'; +import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, establishResourcePhysicalName } from './common'; +import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; + +export async function isHotswappableStateMachineChange( + logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const stateMachineDefinitionChange = await isStateMachineDefinitionOnlyChange(change, evaluateCfnTemplate); + if (stateMachineDefinitionChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT || + stateMachineDefinitionChange === ChangeHotswapImpact.IRRELEVANT) { + return stateMachineDefinitionChange; + } + + const machineNameInCfnTemplate = change.newValue?.Properties?.StateMachineName; + const machineName = await establishResourcePhysicalName(logicalId, machineNameInCfnTemplate, evaluateCfnTemplate); + if (!machineName) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + return new StateMachineHotswapOperation({ + definition: stateMachineDefinitionChange, + stateMachineName: machineName, + }); +} + +async function isStateMachineDefinitionOnlyChange( + change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const newResourceType = change.newValue.Type; + if (newResourceType !== 'AWS::StepFunctions::StateMachine') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + + const propertyUpdates = change.propertyUpdates; + for (const updatedPropName in propertyUpdates) { + // ensure that only changes to the definition string result in a hotswap + if (updatedPropName !== 'DefinitionString') { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; + } + } + + return evaluateCfnTemplate.evaluateCfnExpression(propertyUpdates.DefinitionString.newValue); +} + +interface StateMachineResource { + readonly stateMachineName: string; + readonly definition: string; +} + +class StateMachineHotswapOperation implements HotswapOperation { + public readonly service = 'stepfunctions-state-machine'; + + constructor(private readonly stepFunctionResource: StateMachineResource) { + } + + public async apply(sdk: ISDK): Promise { + // not passing the optional properties leaves them unchanged + return sdk.stepFunctions().updateStateMachine({ + // even though the name of the property is stateMachineArn, passing the name of the state machine is allowed here + stateMachineArn: this.stepFunctionResource.stateMachineName, + definition: this.stepFunctionResource.definition, + }).promise(); + } +} diff --git a/packages/aws-cdk/lib/api/util/cloudformation.ts b/packages/aws-cdk/lib/api/util/cloudformation.ts index 19db988fdc15e..b3b5660d727c4 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation.ts @@ -1,3 +1,4 @@ +import { SSMPARAM_NO_INVALIDATE } from '@aws-cdk/cx-api'; import { CloudFormation } from 'aws-sdk'; import { debug } from '../../logging'; import { deserializeStructure } from '../../serialize'; @@ -11,6 +12,7 @@ export type Template = { interface TemplateParameter { Type: string; Default?: any; + Description?: string; [key: string]: any; } @@ -424,11 +426,12 @@ export class ParameterValues { /** * Whether this set of parameter updates will change the actual stack values */ - public hasChanges(currentValues: Record): boolean { + public hasChanges(currentValues: Record): ParameterChanges { // If any of the parameters are SSM parameters, deploying must always happen - // because we can't predict what the values will be. - if (Object.values(this.formalParams).some(p => p.Type.startsWith('AWS::SSM::Parameter::'))) { - return true; + // because we can't predict what the values will be. We will allow some + // parameters to opt out of this check by having a magic string in their description. + if (Object.values(this.formalParams).some(p => p.Type.startsWith('AWS::SSM::Parameter::') && !p.Description?.includes(SSMPARAM_NO_INVALIDATE))) { + return 'ssm'; } // Otherwise we're dirty if: @@ -445,3 +448,5 @@ export class ParameterValues { return false; } } + +export type ParameterChanges = boolean | 'ssm'; \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts index 73075a41fee27..e956e63e3704c 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts @@ -523,8 +523,9 @@ export class HistoryActivityPrinter extends ActivityPrinterBase { this.stream.write( util.format( - ' %s%s | %s | %s | %s %s%s%s\n', - (progress !== false ? ` ${this.progress()} | ` : ''), + '%s | %s%s | %s | %s | %s %s%s%s\n', + e.StackName, + (progress !== false ? `${this.progress()} | ` : ''), new Date(e.Timestamp).toLocaleTimeString(), color(padRight(STATUS_WIDTH, (e.ResourceStatus || '').substr(0, STATUS_WIDTH))), // pad left and trim padRight(this.props.resourceTypeColumnWidth, e.ResourceType || ''), diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 6bc109dd37822..d6e27a8c8e54b 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { format } from 'util'; import * as cxapi from '@aws-cdk/cx-api'; +import * as chokidar from 'chokidar'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; import * as promptly from 'promptly'; @@ -12,9 +13,9 @@ import { CloudAssembly, DefaultSelection, ExtendedStackSelection, StackCollectio import { CloudExecutable } from './api/cxapp/cloud-executable'; import { StackActivityProgress } from './api/util/cloudformation/stack-activity-monitor'; import { printSecurityDiff, printStackDiff, RequireApproval } from './diff'; -import { data, error, highlight, print, success, warning } from './logging'; +import { data, debug, error, highlight, print, success, warning } from './logging'; import { deserializeStructure } from './serialize'; -import { Configuration } from './settings'; +import { Configuration, PROJECT_CONFIG } from './settings'; import { numberFromBool, partition } from './util'; export interface CdkToolkitProps { @@ -112,7 +113,11 @@ export class CdkToolkit { } public async deploy(options: DeployOptions) { - const stacks = await this.selectStacksForDeploy(options.selector, options.exclusively); + if (options.watch) { + return this.watch(options); + } + + const stacks = await this.selectStacksForDeploy(options.selector, options.exclusively, options.cacheCloudAssembly); const requireApproval = options.requireApproval ?? RequireApproval.Broadening; @@ -203,6 +208,7 @@ export class CdkToolkit { ci: options.ci, rollback: options.rollback, hotswap: options.hotswap, + extraUserAgent: options.extraUserAgent, }); const message = result.noOp @@ -243,6 +249,85 @@ export class CdkToolkit { } } + public async watch(options: WatchOptions) { + const rootDir = path.dirname(path.resolve(PROJECT_CONFIG)); + debug("root directory used for 'watch' is: %s", rootDir); + + const watchSettings: { include?: string | string[], exclude: string | string [] } | undefined = + this.props.configuration.settings.get(['watch']); + if (!watchSettings) { + throw new Error("Cannot use the 'watch' command without specifying at least one directory to monitor. " + + 'Make sure to add a "watch" key to your cdk.json'); + } + + // For the "include" subkey under the "watch" key, the behavior is: + // 1. No "watch" setting? We error out. + // 2. "watch" setting without an "include" key? We default to observing "./**". + // 3. "watch" setting with an empty "include" key? We default to observing "./**". + // 4. Non-empty "include" key? Just use the "include" key. + const watchIncludes = this.patternsArrayForWatch(watchSettings.include, { rootDir, returnRootDirIfEmpty: true }); + debug("'include' patterns for 'watch': %s", watchIncludes); + + // For the "exclude" subkey under the "watch" key, + // the behavior is to add some default excludes in addition to the ones specified by the user: + // 1. The CDK output directory. + // 2. Any file whose name starts with a dot. + // 3. Any directory's content whose name starts with a dot. + // 4. Any node_modules and its content (even if it's not a JS/TS project, you might be using a local aws-cli package) + const outputDir = this.props.configuration.settings.get(['output']); + const watchExcludes = this.patternsArrayForWatch(watchSettings.exclude, { rootDir, returnRootDirIfEmpty: false }).concat( + `${outputDir}/**`, + '**/.*', + '**/.*/**', + '**/node_modules/**', + ); + debug("'exclude' patterns for 'watch': %s", watchExcludes); + + // Since 'cdk deploy' is a relatively slow operation for a 'watch' process, + // introduce a concurrency latch that tracks the state. + // This way, if file change events arrive when a 'cdk deploy' is still executing, + // we will batch them, and trigger another 'cdk deploy' after the current one finishes, + // making sure 'cdk deploy's always execute one at a time. + // Here's a diagram showing the state transitions: + // -------------- -------- file changed -------------- file changed -------------- file changed + // | | ready event | | ------------------> | | ------------------> | | --------------| + // | pre-ready | -------------> | open | | deploying | | queued | | + // | | | | <------------------ | | <------------------ | | <-------------| + // -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done -------------- + let latch: 'pre-ready' | 'open' | 'deploying' | 'queued' = 'pre-ready'; + chokidar.watch(watchIncludes, { + ignored: watchExcludes, + cwd: rootDir, + // ignoreInitial: true, + }).on('ready', () => { + latch = 'open'; + debug("'watch' received the 'ready' event. From now on, all file changes will trigger a deployment"); + }).on('all', async (event, filePath) => { + if (latch === 'pre-ready') { + print(`'watch' is observing ${event === 'addDir' ? 'directory' : 'the file'} '%s' for changes`, filePath); + } else if (latch === 'open') { + latch = 'deploying'; + print("Detected change to '%s' (type: %s). Triggering 'cdk deploy'", filePath, event); + await this.invokeDeployFromWatch(options); + + // If latch is still 'deploying' after the 'await', that's fine, + // but if it's 'queued', that means we need to deploy again + while ((latch as 'deploying' | 'queued') === 'queued') { + // TypeScript doesn't realize latch can change between 'awaits', + // and thinks the above 'while' condition is always 'false' without the cast + latch = 'deploying'; + print("Detected file changes during deployment. Invoking 'cdk deploy' again"); + await this.invokeDeployFromWatch(options); + } + latch = 'open'; + } else { // this means latch is either 'deploying' or 'queued' + latch = 'queued'; + print("Detected change to '%s' (type: %s) while 'cdk deploy' is still running. " + + 'Will queue for another deployment after this one finishes', filePath, event); + } + }); + } + public async destroy(options: DestroyOptions) { let stacks = await this.selectStacksForDestroy(options.selector, options.exclusively); @@ -282,7 +367,7 @@ export class CdkToolkit { const long = []; for (const stack of stacks.stackArtifacts) { long.push({ - id: stack.id, + id: stack.hierarchicalId, name: stack.stackName, environment: stack.environment, }); @@ -397,8 +482,8 @@ export class CdkToolkit { return stacks; } - private async selectStacksForDeploy(selector: StackSelector, exclusively?: boolean): Promise { - const assembly = await this.assembly(); + private async selectStacksForDeploy(selector: StackSelector, exclusively?: boolean, cacheCloudAssembly?: boolean): Promise { + const assembly = await this.assembly(cacheCloudAssembly); const stacks = await assembly.selectStacks(selector, { extend: exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Upstream, defaultBehavior: DefaultSelection.OnlySingle, @@ -480,10 +565,40 @@ export class CdkToolkit { return assembly.stackById(stacks.firstStack.id); } - private assembly(): Promise { - return this.props.cloudExecutable.synthesize(); + private assembly(cacheCloudAssembly?: boolean): Promise { + return this.props.cloudExecutable.synthesize(cacheCloudAssembly); + } + + private patternsArrayForWatch(patterns: string | string[] | undefined, options: { rootDir: string, returnRootDirIfEmpty: boolean }): string[] { + const patternsArray: string[] = patterns !== undefined + ? (Array.isArray(patterns) ? patterns : [patterns]) + : []; + return patternsArray.length > 0 + ? patternsArray + : (options.returnRootDirIfEmpty ? [options.rootDir] : []); } + private async invokeDeployFromWatch(options: WatchOptions): Promise { + // 'watch' has different defaults than regular 'deploy' + const hotswap = options.hotswap === undefined ? true : options.hotswap; + const deployOptions: DeployOptions = { + ...options, + requireApproval: RequireApproval.Never, + // if 'watch' is called by invoking 'cdk deploy --watch', + // we need to make sure to not call 'deploy' with 'watch' again, + // as that would lead to a cycle + watch: false, + cacheCloudAssembly: false, + hotswap: hotswap, + extraUserAgent: `cdk-watch/hotswap-${hotswap ? 'on' : 'off'}`, + }; + + try { + await this.deploy(deployOptions); + } catch (e) { + // just continue - deploy will show the error + } + } } export interface DiffOptions { @@ -542,7 +657,7 @@ export interface DiffOptions { securityOnly?: boolean; } -export interface DeployOptions { +interface WatchOptions { /** * Criteria for selecting stacks to deploy */ @@ -567,6 +682,56 @@ export interface DeployOptions { */ roleArn?: string; + /** + * Reuse the assets with the given asset IDs + */ + reuseAssets?: string[]; + + /** + * Optional name to use for the CloudFormation change set. + * If not provided, a name will be generated automatically. + */ + changeSetName?: string; + + /** + * Always deploy, even if templates are identical. + * @default false + */ + force?: boolean; + + /** + * Display mode for stack deployment progress. + * + * @default - StackActivityProgress.Bar - stack events will be displayed for + * the resource currently being deployed. + */ + progress?: StackActivityProgress; + + /** + * Rollback failed deployments + * + * @default true + */ + readonly rollback?: boolean; + + /** + * Whether to perform a 'hotswap' deployment. + * A 'hotswap' deployment will attempt to short-circuit CloudFormation + * and update the affected resources like Lambda functions directly. + * + * @default - false for regular deployments, true for 'watch' deployments + */ + readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; +} + +export interface DeployOptions extends WatchOptions { /** * ARNs of SNS topics that CloudFormation will notify with stack related events */ @@ -579,11 +744,6 @@ export interface DeployOptions { */ requireApproval?: RequireApproval; - /** - * Reuse the assets with the given asset IDs - */ - reuseAssets?: string[]; - /** * Tags to pass to CloudFormation for deployment */ @@ -596,18 +756,6 @@ export interface DeployOptions { */ execute?: boolean; - /** - * Optional name to use for the CloudFormation change set. - * If not provided, a name will be generated automatically. - */ - changeSetName?: string; - - /** - * Always deploy, even if templates are identical. - * @default false - */ - force?: boolean; - /** * Additional parameters for CloudFormation at deploy time * @default {} @@ -623,14 +771,6 @@ export interface DeployOptions { */ usePreviousParameters?: boolean; - /** - * Display mode for stack deployment progress. - * - * @default - StackActivityProgress.Bar - stack events will be displayed for - * the resource currently being deployed. - */ - progress?: StackActivityProgress; - /** * Path to file where stack outputs will be written after a successful deploy as JSON * @default - Outputs are not written to any file @@ -645,19 +785,20 @@ export interface DeployOptions { readonly ci?: boolean; /** - * Rollback failed deployments + * Whether this 'deploy' command should actually delegate to the 'watch' command. * - * @default true + * @default false */ - readonly rollback?: boolean; - /* - * Whether to perform a 'hotswap' deployment. - * A 'hotswap' deployment will attempt to short-circuit CloudFormation - * and update the affected resources like Lambda functions directly. + readonly watch?: boolean; + + /** + * Whether we should cache the Cloud Assembly after the first time it has been synthesized. + * The default is 'true', we only don't want to do it in case the deployment is triggered by + * 'cdk watch'. * - * @default - false (do not perform a 'hotswap' deployment) + * @default true */ - readonly hotswap?: boolean; + readonly cacheCloudAssembly?: boolean; } export interface DestroyOptions { diff --git a/packages/aws-cdk/lib/context-providers/security-groups.ts b/packages/aws-cdk/lib/context-providers/security-groups.ts index 7edde696fba45..f4c85d07c7ba3 100644 --- a/packages/aws-cdk/lib/context-providers/security-groups.ts +++ b/packages/aws-cdk/lib/context-providers/security-groups.ts @@ -12,11 +12,34 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin const account: string = args.account!; const region: string = args.region!; + if (args.securityGroupId && args.securityGroupName) { + throw new Error('\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group'); + } + + if (!args.securityGroupId && !args.securityGroupName) { + throw new Error('\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group'); + } + const options = { assumeRoleArn: args.lookupRoleArn }; const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); + const filters: AWS.EC2.FilterList = []; + if (args.vpcId) { + filters.push({ + Name: 'vpc-id', + Values: [args.vpcId], + }); + } + if (args.securityGroupName) { + filters.push({ + Name: 'group-name', + Values: [args.securityGroupName], + }); + } + const response = await ec2.describeSecurityGroups({ - GroupIds: [args.securityGroupId], + GroupIds: args.securityGroupId ? [args.securityGroupId] : undefined, + Filters: filters.length > 0 ? filters : undefined, }).promise(); const securityGroups = response.SecurityGroups ?? []; @@ -24,6 +47,10 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin throw new Error(`No security groups found matching ${JSON.stringify(args)}`); } + if (securityGroups.length > 1) { + throw new Error(`More than one security groups found matching ${JSON.stringify(args)}`); + } + const [securityGroup] = securityGroups; return { diff --git a/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go index 24a2b0e90b616..742ed4c64ce7b 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go @@ -2,9 +2,9 @@ package main import ( "github.com/aws/aws-cdk-go/awscdk" - "github.com/aws/aws-cdk-go/awscdk/awssns" + // "github.com/aws/aws-cdk-go/awscdk/awssqs" "github.com/aws/constructs-go/constructs/v3" - "github.com/aws/jsii-runtime-go" + // "github.com/aws/jsii-runtime-go" ) type %name.PascalCased%StackProps struct { @@ -20,10 +20,10 @@ func New%name.PascalCased%Stack(scope constructs.Construct, id string, props *%n // The code that defines your stack goes here - // as an example, here's how you would define an AWS SNS topic: - awssns.NewTopic(stack, jsii.String("MyTopic"), &awssns.TopicProps{ - DisplayName: jsii.String("MyCoolTopic"), - }) + // example resource + // queue := awssqs.NewQueue(stack, jsii.String("%name.PascalCased%Queue"), &awssqs.QueueProps{ + // VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)), + // }) return stack } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go index c0534249b6282..78742540bc2f7 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go @@ -1,28 +1,26 @@ package main -import ( - "encoding/json" - "testing" +// import ( +// "testing" - "github.com/aws/aws-cdk-go/awscdk" - "github.com/stretchr/testify/assert" - "github.com/tidwall/gjson" -) +// "github.com/aws/aws-cdk-go/awscdk" +// "github.com/aws/aws-cdk-go/awscdk/assertions" +// "github.com/aws/jsii-runtime-go" +// ) -func Test%name.PascalCased%Stack(t *testing.T) { - // GIVEN - app := awscdk.NewApp(nil) +// example tests. To run these tests, uncomment this file along with the +// example resource in %name%_test.go +// func Test%name.PascalCased%Stack(t *testing.T) { +// // GIVEN +// app := awscdk.NewApp(nil) - // WHEN - stack := New%name.PascalCased%Stack(app, "MyStack", nil) +// // WHEN +// stack := New%name.PascalCased%Stack(app, "MyStack", nil) - // THEN - bytes, err := json.Marshal(app.Synth(nil).GetStackArtifact(stack.ArtifactId()).Template()) - if err != nil { - t.Error(err) - } +// // THEN +// template := assertions.Template_FromStack(stack) - template := gjson.ParseBytes(bytes) - displayName := template.Get("Resources.MyTopic86869434.Properties.DisplayName").String() - assert.Equal(t, "MyCoolTopic", displayName) -} +// template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{ +// "VisibilityTimeout": 300, +// }) +// } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod b/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod index a1dcb391c1614..5dbef39984826 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod @@ -6,8 +6,4 @@ require ( github.com/aws/aws-cdk-go/awscdk v%cdk-version%-devpreview github.com/aws/constructs-go/constructs/v3 v3.3.71 github.com/aws/jsii-runtime-go v1.26.0 - - // for testing - github.com/tidwall/gjson v1.7.4 - github.com/stretchr/testify v1.7.0 ) diff --git a/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v1/app/java/pom.template.xml index fb0b6cf828aaf..b8b00e798209c 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v1/app/java/pom.template.xml @@ -43,24 +43,18 @@ core ${cdk.version} - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test + software.amazon.awscdk + assertions + ${cdk.version} + test + org.junit.jupiter - junit-jupiter-engine + junit-jupiter ${junit.version} test - - org.assertj - assertj-core - 3.18.1 - test - diff --git a/packages/aws-cdk/lib/init-templates/v1/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java b/packages/aws-cdk/lib/init-templates/v1/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java index e0d4b5a0be8b1..a4dbab1dbd8b9 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java +++ b/packages/aws-cdk/lib/init-templates/v1/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java @@ -3,6 +3,8 @@ import software.amazon.awscdk.core.Construct; import software.amazon.awscdk.core.Stack; import software.amazon.awscdk.core.StackProps; +// import software.amazon.awscdk.services.sqs.Queue; +// import software.amazon.awscdk.core.Duration; public class %name.PascalCased%Stack extends Stack { public %name.PascalCased%Stack(final Construct scope, final String id) { @@ -13,5 +15,10 @@ public class %name.PascalCased%Stack extends Stack { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // final Queue queue = Queue.Builder.create(this, "%name.PascalCased%Queue") + // .visibilityTimeout(Duration.seconds(300)) + // .build(); } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java b/packages/aws-cdk/lib/init-templates/v1/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java index 9494caf63d628..e095565be7920 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java +++ b/packages/aws-cdk/lib/init-templates/v1/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java @@ -1,28 +1,26 @@ -package com.myorg; +// package com.myorg; -import software.amazon.awscdk.core.App; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +// import software.amazon.awscdk.core.App; +// import software.amazon.awscdk.assertions.Template; +// import java.io.IOException; -import java.io.IOException; +// import java.util.HashMap; -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; +// import org.junit.jupiter.api.Test; -public class %name.PascalCased%Test { - private final static ObjectMapper JSON = - new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); +// example test. To run these tests, uncomment this file, along with the +// example resource in java/src/main/java/com/myorg/%name.PascalCased%Stack.java +// public class %name.PascalCased%Test { - @Test - public void testStack() throws IOException { - App app = new App(); - %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); +// @Test +// public void testStack() throws IOException { +// App app = new App(); +// %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); - // synthesize the stack to a CloudFormation template - JsonNode actual = JSON.valueToTree(app.synth().getStackArtifact(stack.getArtifactId()).getTemplate()); +// Template template = Template.fromStack(stack); - // Update once resources have been added to the stack - assertThat(actual.get("Resources")).isNull(); - } -} +// template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{ +// put("VisibilityTimeout", 300); +// }}); +// } +// } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/lib/%name%-stack.template.js b/packages/aws-cdk/lib/init-templates/v1/app/javascript/lib/%name%-stack.template.js index 2c52c5f65244c..8004c54a568aa 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/javascript/lib/%name%-stack.template.js +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/lib/%name%-stack.template.js @@ -1,4 +1,5 @@ const cdk = require('@aws-cdk/core'); +// const sqs = require('@aws-cdk/aws-sqs'); class %name.PascalCased%Stack extends cdk.Stack { /** @@ -11,6 +12,11 @@ class %name.PascalCased%Stack extends cdk.Stack { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: cdk.Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/package.template.json b/packages/aws-cdk/lib/init-templates/v1/app/javascript/package.template.json index 5547dacf3a673..abe1218b79d96 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/javascript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/package.template.json @@ -10,7 +10,7 @@ "test": "jest" }, "devDependencies": { - "@aws-cdk/assert": "%cdk-version%", + "@aws-cdk/assertions": "%cdk-version%", "aws-cdk": "%cdk-version%", "jest": "^26.4.2" }, diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/test/%name%.test.template.js b/packages/aws-cdk/lib/init-templates/v1/app/javascript/test/%name%.test.template.js index 54e6bb172e0a9..514cd34e9faf1 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/javascript/test/%name%.test.template.js +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/test/%name%.test.template.js @@ -1,13 +1,18 @@ -const { expect, matchTemplate, MatchStyle } = require('@aws-cdk/assert'); -const cdk = require('@aws-cdk/core'); -const %name.PascalCased% = require('../lib/%name%-stack'); +// const { Template } = require('@aws-cdk/assertions'); +// const cdk = require('@aws-cdk/core'); +// const %name.PascalCased% = require('../lib/%name%-stack'); -test('Empty Stack', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - expect(stack).to(matchTemplate({ - "Resources": {} - }, MatchStyle.EXACT)) +// example test. To run these tests, uncomment this file along with the +// example resource in lib/%name%-stack.js +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); + diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v1/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py index 6b4ed6e8ea6ed..9c5de032e839d 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -1,4 +1,7 @@ -from aws_cdk import core as cdk +from aws_cdk import ( + core as cdk + # aws_sqs as sqs, +) # For consistency with other languages, `cdk` is the preferred import name for # the CDK's core module. The following line also imports it as `core` for use @@ -13,3 +16,9 @@ def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # The code that defines your stack goes here + + # example resource + # queue = sqs.Queue( + # self, "%name.PascalCased%Queue", + # visibility_timeout=cdk.Duration.seconds(300), + # ) diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/requirements-dev.txt b/packages/aws-cdk/lib/init-templates/v1/app/python/requirements-dev.txt new file mode 100644 index 0000000000000..927094516e657 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/requirements-dev.txt @@ -0,0 +1 @@ +pytest==6.2.5 diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.template.txt b/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.template.txt new file mode 100644 index 0000000000000..87cbb00cfac55 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.template.txt @@ -0,0 +1,2 @@ +aws-cdk.core>=%cdk-version% +aws-cdk.assertions>=%cdk-version% diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.txt deleted file mode 100644 index d6e1198b1ab1f..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e . diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py deleted file mode 100644 index 113bf29e54755..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py +++ /dev/null @@ -1,43 +0,0 @@ -import setuptools - - -with open("README.md") as fp: - long_description = fp.read() - - -setuptools.setup( - name="%name.PythonModule%", - version="0.0.1", - - description="An empty CDK Python app", - long_description=long_description, - long_description_content_type="text/markdown", - - author="author", - - package_dir={"": "%name.PythonModule%"}, - packages=setuptools.find_packages(where="%name.PythonModule%"), - - install_requires=[ - "aws-cdk.core==%cdk-version%", - ], - - python_requires=">=3.6", - - classifiers=[ - "Development Status :: 4 - Beta", - - "Intended Audience :: Developers", - - "Programming Language :: JavaScript", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - - "Topic :: Software Development :: Code Generators", - "Topic :: Utilities", - - "Typing :: Typed", - ], -) diff --git a/packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.no-packagejson-validator b/packages/aws-cdk/lib/init-templates/v1/app/python/tests/__init__.py similarity index 100% rename from packages/@aws-cdk/lambda-layer-node-proxy-agent/layer/.no-packagejson-validator rename to packages/aws-cdk/lib/init-templates/v1/app/python/tests/__init__.py diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/tests/unit/__init__.py b/packages/aws-cdk/lib/init-templates/v1/app/python/tests/unit/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/tests/unit/test_%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v1/app/python/tests/unit/test_%name.PythonModule%_stack.template.py new file mode 100644 index 0000000000000..1b0e4b66e9aaa --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/tests/unit/test_%name.PythonModule%_stack.template.py @@ -0,0 +1,19 @@ +# from aws_cdk import ( +# core, +# assertions +# ) + +# from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack + + +# example tests. To run these tests, uncomment this file along with the example +# resource in %name.PythonModule%/%name.PythonModule%_stack.py +def test_sqs_queue_created(): +# app = core.App() +# stack = %name.PascalCased%Stack(app, "%name.StackName%") +# template = assertions.Template.from_stack(stack) + +# template.has_resource_properties("AWS::SQS::Queue", { +# "VisibilityTimeout": 300 +# }) + pass diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/lib/%name%-stack.template.ts b/packages/aws-cdk/lib/init-templates/v1/app/typescript/lib/%name%-stack.template.ts index 80fa43a59ea24..82fd9a48ac0dd 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/lib/%name%-stack.template.ts +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/lib/%name%-stack.template.ts @@ -1,9 +1,15 @@ import * as cdk from '@aws-cdk/core'; +// import * as sqs from '@aws-cdk/aws-sqs'; export class %name.PascalCased%Stack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: cdk.Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/v1/app/typescript/package.template.json index df89ae6a206bb..94bbd07dae3ac 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/package.template.json @@ -11,7 +11,7 @@ "cdk": "cdk" }, "devDependencies": { - "@aws-cdk/assert": "%cdk-version%", + "@aws-cdk/assertions": "%cdk-version%", "@types/jest": "^26.0.10", "@types/node": "10.17.27", "jest": "^26.4.2", diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v1/app/typescript/test/%name%.test.template.ts index b98e4f0abee50..d41f3aaf74d04 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/test/%name%.test.template.ts @@ -1,13 +1,17 @@ -import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; -import * as cdk from '@aws-cdk/core'; -import * as %name.PascalCased% from '../lib/%name%-stack'; +// import { Template } from '@aws-cdk/assertions'; +// import * as cdk from '@aws-cdk/core'; +// import * as %name.PascalCased% from '../lib/%name%-stack'; -test('Empty Stack', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - expectCDK(stack).to(matchTemplate({ - "Resources": {} - }, MatchStyle.EXACT)) +// example test. To run these tests, uncomment this file along with the +// example resource in lib/%name%-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); diff --git a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/lib/index.template.ts b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/lib/index.template.ts index d05d444fc8f09..49c002d8e8271 100644 --- a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/lib/index.template.ts +++ b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/lib/index.template.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +// import * as sqs from '@aws-cdk/aws-sqs'; export interface %name.PascalCased%Props { // Define construct properties here @@ -10,5 +11,10 @@ export class %name.PascalCased% extends cdk.Construct { super(scope, id); // Define construct contents here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: cdk.Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/package.template.json index 8d9ef219c74ca..4e99fa16cbc8f 100644 --- a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/package.template.json @@ -9,7 +9,7 @@ "test": "jest" }, "devDependencies": { - "@aws-cdk/assert": "%cdk-version%", + "@aws-cdk/assertions": "%cdk-version%", "@types/jest": "^26.0.10", "@types/node": "10.17.27", "jest": "^26.4.2", diff --git a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/test/%name%.test.template.ts index 0b49cb3cc99d1..641b974082aa7 100644 --- a/packages/aws-cdk/lib/init-templates/v1/lib/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v1/lib/typescript/test/%name%.test.template.ts @@ -1,15 +1,18 @@ -import { expect as expectCDK, countResources } from '@aws-cdk/assert'; -import * as cdk from '@aws-cdk/core'; -import * as %name.PascalCased% from '../lib/index'; +// import { Template } from '@aws-cdk/assertions'; +// import * as cdk from '@aws-cdk/core'; +// import * as %name.PascalCased% from '../lib/index'; -/* - * Example test - */ -test('SNS Topic Created', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, "TestStack"); - // WHEN - new %name.PascalCased%.%name.PascalCased%(stack, 'MyTestConstruct'); - // THEN - expectCDK(stack).to(countResources("AWS::SNS::Topic",0)); +// example test. To run these tests, uncomment this file along with the +// example resource in lib/index.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// const stack = new cdk.Stack(app, 'TestStack'); +// // WHEN +// new %name.PascalCased%.%name.PascalCased%(stack, 'MyTestConstruct'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%.template.go b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%.template.go new file mode 100644 index 0000000000000..5ac97c495740f --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%.template.go @@ -0,0 +1,71 @@ +package main + +import ( + "github.com/aws/aws-cdk-go/awscdk" + "github.com/aws/aws-cdk-go/awscdk/awssns" + "github.com/aws/aws-cdk-go/awscdk/awssnssubscriptions" + "github.com/aws/aws-cdk-go/awscdk/awssqs" + "github.com/aws/constructs-go/constructs/v3" + "github.com/aws/jsii-runtime-go" +) + +type %name.PascalCased%StackProps struct { + awscdk.StackProps +} + +func New%name.PascalCased%Stack(scope constructs.Construct, id string, props *%name.PascalCased%StackProps) awscdk.Stack { + var sprops awscdk.StackProps + if props != nil { + sprops = props.StackProps + } + stack := awscdk.NewStack(scope, &id, &sprops) + + + queue := awssqs.NewQueue(stack, jsii.String("%name.PascalCased%Queue"), &awssqs.QueueProps{ + VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)), + }) + + topic := awssns.NewTopic(stack, jsii.String("%name.PascalCased%Topic"), &awssns.TopicProps{}) + topic.AddSubscription(awssnssubscriptions.NewSqsSubscription(queue, &awssnssubscriptions.SqsSubscriptionProps{})) + + return stack +} + +func main() { + app := awscdk.NewApp(nil) + + New%name.PascalCased%Stack(app, "%name.PascalCased%Stack", &%name.PascalCased%StackProps{ + awscdk.StackProps{ + Env: env(), + }, + }) + + app.Synth(nil) +} + +// env determines the AWS environment (account+region) in which our stack is to +// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html +func env() *awscdk.Environment { + // If unspecified, this stack will be "environment-agnostic". + // Account/Region-dependent features and context lookups will not work, but a + // single synthesized template can be deployed anywhere. + //--------------------------------------------------------------------------- + return nil + + // Uncomment if you know exactly what account and region you want to deploy + // the stack to. This is the recommendation for production stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String("123456789012"), + // Region: jsii.String("us-east-1"), + // } + + // Uncomment to specialize this stack for the AWS Account and Region that are + // implied by the current CLI configuration. This is recommended for dev + // stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")), + // Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")), + // } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%_test.template.go b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%_test.template.go new file mode 100644 index 0000000000000..834f4830cf23e --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/%name%_test.template.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" + + "github.com/aws/aws-cdk-go/awscdk" + "github.com/aws/aws-cdk-go/awscdk/assertions" + "github.com/aws/jsii-runtime-go" +) + +func Test%name.PascalCased%Stack(t *testing.T) { + // GIVEN + app := awscdk.NewApp(nil) + + // WHEN + stack := New%name.PascalCased%Stack(app, "MyStack", nil) + + // THEN + template := assertions.Template_FromStack(stack) + + template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{ + "VisibilityTimeout": 300, + }) + template.ResourceCountIs(jsii.String("AWS::SNS::Topic"), jsii.Number(1)) +} diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/.template.gitignore b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/.template.gitignore new file mode 100644 index 0000000000000..92fe1ec34b4b6 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/.template.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# go.sum should be committed +!go.sum + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/README.md b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/README.md new file mode 100644 index 0000000000000..9b8d4b29f26e3 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK Go project! + +This is a blank project for Go development with CDK. + +**NOTICE**: Go support is still in Developer Preview. This implies that APIs may +change while we address early feedback from the community. We would love to hear +about your experience through GitHub issues. + +## Useful commands + + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk synth` emits the synthesized CloudFormation template + * `go test` run unit tests diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json new file mode 100644 index 0000000000000..a25485ed0951b --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json @@ -0,0 +1,13 @@ +{ + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/go.template.mod b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/go.template.mod new file mode 100644 index 0000000000000..5dbef39984826 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/go.template.mod @@ -0,0 +1,9 @@ +module %name% + +go 1.16 + +require ( + github.com/aws/aws-cdk-go/awscdk v%cdk-version%-devpreview + github.com/aws/constructs-go/constructs/v3 v3.3.71 + github.com/aws/jsii-runtime-go v1.26.0 +) diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/pom.template.xml index 5d679d2570040..e25c5e7c23a9b 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/pom.template.xml @@ -55,24 +55,18 @@ sqs ${cdk.version} - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test + software.amazon.awscdk + assertions + ${cdk.version} + test + org.junit.jupiter - junit-jupiter-engine + junit-jupiter ${junit.version} test - - org.assertj - assertj-core - 3.18.1 - test - diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java index 93b00f5d770c2..5cbbe8a127e4f 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java @@ -1,27 +1,26 @@ package com.myorg; import software.amazon.awscdk.core.App; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import software.amazon.awscdk.assertions.Template; +import software.amazon.awscdk.assertions.Match; import java.io.IOException; +import java.util.HashMap; + import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; public class %name.PascalCased%StackTest { - private final static ObjectMapper JSON = - new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); @Test public void testStack() throws IOException { App app = new App(); %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); - JsonNode actual = JSON.valueToTree(app.synth().getStackArtifact(stack.getArtifactId()).getTemplate()); + Template template = Template.fromStack(stack); - assertThat(actual.toString()) - .contains("AWS::SQS::Queue") - .contains("AWS::SNS::Topic"); + template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{ + put("VisibilityTimeout", 300); + }}); + template.resourceCountIs("AWS::SNS::Topic", 1); } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/package.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/package.template.json index 7594be2ffb151..43b6f94c6f314 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/package.template.json @@ -10,7 +10,7 @@ "test": "jest" }, "devDependencies": { - "@aws-cdk/assert": "%cdk-version%", + "@aws-cdk/assertions": "%cdk-version%", "aws-cdk": "%cdk-version%", "jest": "^26.4.2" }, diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/test/%name%.test.template.js b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/test/%name%.test.template.js index 7e786e6ee3753..4e53341387c69 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/test/%name%.test.template.js +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/test/%name%.test.template.js @@ -1,15 +1,17 @@ -const { expect, haveResource } = require('@aws-cdk/assert'); +const { Template, Match } = require('@aws-cdk/assertions'); const cdk = require('@aws-cdk/core'); const %name.PascalCased% = require('../lib/%name%-stack'); test('SQS Queue Created', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - expect(stack).to(haveResource("AWS::SQS::Queue",{ - VisibilityTimeout: 300 - })); + const app = new cdk.App(); + // WHEN + const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); + // THEN + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::SQS::Queue', { + VisibilityTimeout: 300 + }); }); test('SNS Topic Created', () => { @@ -17,5 +19,7 @@ test('SNS Topic Created', () => { // WHEN const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); // THEN - expect(stack).to(haveResource("AWS::SNS::Topic")); + const template = Template.fromStack(stack); + + template.resourceCountIs('AWS::SNS::Topic', 1); }); diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements-dev.txt b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements-dev.txt new file mode 100644 index 0000000000000..927094516e657 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements-dev.txt @@ -0,0 +1 @@ +pytest==6.2.5 diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.template.txt b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.template.txt new file mode 100644 index 0000000000000..6c20aced0687a --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.template.txt @@ -0,0 +1,7 @@ +aws-cdk.core==%cdk-version% +aws-cdk.aws_iam==%cdk-version% +aws-cdk.aws_sqs==%cdk-version% +aws-cdk.aws_sns==%cdk-version% +aws-cdk.aws_sns_subscriptions==%cdk-version% +aws-cdk.aws_s3==%cdk-version% +aws-cdk.assertions==%cdk-version% diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.txt deleted file mode 100644 index ae60ed5f14ca8..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ --e . -pytest diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py deleted file mode 100644 index d79611021c860..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py +++ /dev/null @@ -1,48 +0,0 @@ -import setuptools - - -with open("README.md") as fp: - long_description = fp.read() - - -setuptools.setup( - name="%name.PythonModule%", - version="0.0.1", - - description="A sample CDK Python app", - long_description=long_description, - long_description_content_type="text/markdown", - - author="author", - - package_dir={"": "%name.PythonModule%"}, - packages=setuptools.find_packages(where="%name.PythonModule%"), - - install_requires=[ - "aws-cdk.core==%cdk-version%", - "aws-cdk.aws_iam==%cdk-version%", - "aws-cdk.aws_sqs==%cdk-version%", - "aws-cdk.aws_sns==%cdk-version%", - "aws-cdk.aws_sns_subscriptions==%cdk-version%", - "aws-cdk.aws_s3==%cdk-version%", - ], - - python_requires=">=3.6", - - classifiers=[ - "Development Status :: 4 - Beta", - - "Intended Audience :: Developers", - - "Programming Language :: JavaScript", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - - "Topic :: Software Development :: Code Generators", - "Topic :: Utilities", - - "Typing :: Typed", - ], -) diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py index 182d07fe09855..99a6a26b4a15e 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py @@ -1,19 +1,24 @@ -import json -import pytest +from aws_cdk import ( + core, + assertions + ) -from aws_cdk import core from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack -def get_template(): +def test_sqs_queue_created(): app = core.App() - %name.PascalCased%Stack(app, "%name.StackName%") - return json.dumps(app.synth().get_stack("%name.StackName%").template) - + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) -def test_sqs_queue_created(): - assert("AWS::SQS::Queue" in get_template()) + template.has_resource_properties("AWS::SQS::Queue", { + "VisibilityTimeout": 300 + }) def test_sns_topic_created(): - assert("AWS::SNS::Topic" in get_template()) + app = core.App() + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) + + template.resource_count_is("AWS::SNS::Topic", 1) diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/package.template.json index 98942aa31c167..550691f4eb2c5 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/package.template.json @@ -12,7 +12,7 @@ }, "devDependencies": { "aws-cdk": "%cdk-version%", - "@aws-cdk/assert": "%cdk-version%", + "@aws-cdk/assertions": "%cdk-version%", "@types/jest": "^26.0.10", "@types/node": "10.17.27", "jest": "^26.4.2", diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/test/%name%.test.template.ts index 3a602380d6f6d..45cd6cca71ba4 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/test/%name%.test.template.ts @@ -1,15 +1,17 @@ -import { expect as expectCDK, haveResource } from '@aws-cdk/assert'; +import { Template, Match } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; import * as %name.PascalCased% from '../lib/%name%-stack'; test('SQS Queue Created', () => { - const app = new cdk.App(); + const app = new cdk.App(); // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); + const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); // THEN - expectCDK(stack).to(haveResource("AWS::SQS::Queue",{ - VisibilityTimeout: 300 - })); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::SQS::Queue', { + VisibilityTimeout: 300 + }); }); test('SNS Topic Created', () => { @@ -17,5 +19,7 @@ test('SNS Topic Created', () => { // WHEN const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); // THEN - expectCDK(stack).to(haveResource("AWS::SNS::Topic")); + const template = Template.fromStack(stack); + + template.resourceCountIs('AWS::SNS::Topic', 1); }); diff --git a/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/go/%name%.template.go b/packages/aws-cdk/lib/init-templates/v2/app/go/%name%.template.go index 46577faef90f6..a2ab8b5d8f8fd 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/go/%name%.template.go +++ b/packages/aws-cdk/lib/init-templates/v2/app/go/%name%.template.go @@ -2,9 +2,9 @@ package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" - "github.com/aws/aws-cdk-go/awscdk/v2/awssns" + // "github.com/aws/aws-cdk-go/awscdk/v2/awssqs" "github.com/aws/constructs-go/constructs/v10" - "github.com/aws/jsii-runtime-go" + // "github.com/aws/jsii-runtime-go" ) type %name.PascalCased%StackProps struct { @@ -20,10 +20,10 @@ func New%name.PascalCased%Stack(scope constructs.Construct, id string, props *%n // The code that defines your stack goes here - // as an example, here's how you would define an AWS SNS topic: - awssns.NewTopic(stack, jsii.String("MyTopic"), &awssns.TopicProps{ - DisplayName: jsii.String("MyCoolTopic"), - }) + // example resource + // queue := awssqs.NewQueue(stack, jsii.String("%name.PascalCased%Queue"), &awssqs.QueueProps{ + // VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)), + // }) return stack } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/go/%name%_test.template.go b/packages/aws-cdk/lib/init-templates/v2/app/go/%name%_test.template.go index 6c166d681b0ce..251be839ed3f0 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/go/%name%_test.template.go +++ b/packages/aws-cdk/lib/init-templates/v2/app/go/%name%_test.template.go @@ -1,28 +1,26 @@ package main -import ( - "encoding/json" - "testing" +// import ( +// "testing" - "github.com/aws/aws-cdk-go/awscdk/v2" - "github.com/stretchr/testify/assert" - "github.com/tidwall/gjson" -) +// "github.com/aws/aws-cdk-go/awscdk/v2" +// assertions "github.com/aws/aws-cdk-go/awscdk/v2/assertions" +// "github.com/aws/jsii-runtime-go" +// ) -func Test%name.PascalCased%Stack(t *testing.T) { - // GIVEN - app := awscdk.NewApp(nil) +// example tests. To run these tests, uncomment this file along with the +// example resource in %name%_test.go +// func Test%name.PascalCased%Stack(t *testing.T) { +// // GIVEN +// app := awscdk.NewApp(nil) - // WHEN - stack := New%name.PascalCased%Stack(app, "MyStack", nil) +// // WHEN +// stack := New%name.PascalCased%Stack(app, "MyStack", nil) - // THEN - bytes, err := json.Marshal(app.Synth(nil).GetStackArtifact(stack.ArtifactId()).Template()) - if err != nil { - t.Error(err) - } +// // THEN +// template := assertions.Template_FromStack(stack) - template := gjson.ParseBytes(bytes) - displayName := template.Get("Resources.MyTopic86869434.Properties.DisplayName").String() - assert.Equal(t, "MyCoolTopic", displayName) -} +// template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{ +// "VisibilityTimeout": 300, +// }) +// } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v2/app/go/go.template.mod b/packages/aws-cdk/lib/init-templates/v2/app/go/go.template.mod index 50f21389478f4..3b61360cb8e15 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/go/go.template.mod +++ b/packages/aws-cdk/lib/init-templates/v2/app/go/go.template.mod @@ -6,8 +6,4 @@ require ( github.com/aws/aws-cdk-go/awscdk/v2 v%cdk-version% github.com/aws/constructs-go/constructs/v10 v10.0.5 github.com/aws/jsii-runtime-go v1.29.0 - - // for testing - github.com/tidwall/gjson v1.7.4 - github.com/stretchr/testify v1.7.0 ) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml index 1d0eb742d4f8e..e362c878770f6 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml @@ -53,21 +53,9 @@ org.junit.jupiter - junit-jupiter-api + junit-jupiter ${junit.version} test - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - org.assertj - assertj-core - 3.18.1 - test - diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java b/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java index 0d8cbf10bf988..e944bde388603 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%Stack.template.java @@ -3,6 +3,8 @@ import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; +// import software.amazon.awscdk.Duration; +// import software.amazon.awscdk.services.sqs.Queue; public class %name.PascalCased%Stack extends Stack { public %name.PascalCased%Stack(final Construct scope, final String id) { @@ -13,5 +15,10 @@ public class %name.PascalCased%Stack extends Stack { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // final Queue queue = Queue.Builder.create(this, "%name.PascalCased%Queue") + // .visibilityTimeout(Duration.seconds(300)) + // .build(); } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java b/packages/aws-cdk/lib/init-templates/v2/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java index 715840abd0de1..a87913e1ab722 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/src/test/java/com/myorg/%name.PascalCased%Test.template.java @@ -1,29 +1,26 @@ -package com.myorg; +// package com.myorg; -import software.amazon.awscdk.App; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +// import software.amazon.awscdk.App; +// import software.amazon.awscdk.assertions.Template; +// import java.io.IOException; -import java.io.IOException; +// import java.util.HashMap; -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; +// import org.junit.jupiter.api.Test; -public class %name.PascalCased%Test { - private final static ObjectMapper JSON = - new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); +// example test. To run these tests, uncomment this file, along with the +// example resource in java/src/main/java/com/myorg/%name.PascalCased%Stack.java +// public class %name.PascalCased%Test { - @Test - public void testStack() throws IOException { - App app = new App(); - %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); +// @Test +// public void testStack() throws IOException { +// App app = new App(); +// %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); - // synthesize the stack to a CloudFormation template and compare against - // a checked-in JSON file. - JsonNode actual = JSON.valueToTree(app.synth().getStackArtifact(stack.getArtifactId()).getTemplate()); +// Template template = Template.fromStack(stack); - // Update once resources have been added to the stack - assertThat(actual.get("Resources")).isNull(); - } -} +// template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{ +// put("VisibilityTimeout", 300); +// }}); +// } +// } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/javascript/lib/%name%-stack.template.js b/packages/aws-cdk/lib/init-templates/v2/app/javascript/lib/%name%-stack.template.js index d5d66d9bd229e..cb7c095969484 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/javascript/lib/%name%-stack.template.js +++ b/packages/aws-cdk/lib/init-templates/v2/app/javascript/lib/%name%-stack.template.js @@ -1,4 +1,5 @@ -const { Stack } = require('aws-cdk-lib'); +const { Stack, Duration } = require('aws-cdk-lib'); +// const sqs = require('aws-cdk-lib/aws-sqs'); class %name.PascalCased%Stack extends Stack { /** @@ -11,6 +12,11 @@ class %name.PascalCased%Stack extends Stack { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/javascript/test/%name%.test.template.js b/packages/aws-cdk/lib/init-templates/v2/app/javascript/test/%name%.test.template.js index 79db241da8e0e..2c13f3bade1cd 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/javascript/test/%name%.test.template.js +++ b/packages/aws-cdk/lib/init-templates/v2/app/javascript/test/%name%.test.template.js @@ -1,11 +1,17 @@ -const cdk = require('aws-cdk-lib'); -const %name.PascalCased% = require('../lib/%name%-stack'); +// const cdk = require('aws-cdk-lib'); +// const { Template } = require('aws-cdk-lib/assertions'); +// const %name.PascalCased% = require('../lib/%name%-stack'); -test('Empty Stack', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - const actual = app.synth().getStackArtifact(stack.artifactId).template; - expect(actual.Resources || {}).toEqual({}); +// example test. To run these tests, uncomment this file along with the +// example resource in lib/%name%-stack.js +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py index 4599c4f6e19f6..b93133ce944cf 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -1,4 +1,8 @@ -from aws_cdk import Stack +from aws_cdk import ( + # Duration, + Stack, + # aws_sqs as sqs, +) from constructs import Construct class %name.PascalCased%Stack(Stack): @@ -7,3 +11,9 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # The code that defines your stack goes here + + # example resource + # queue = sqs.Queue( + # self, "%name.PascalCased%Queue", + # visibility_timeout=Duration.seconds(300), + # ) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/requirements-dev.txt b/packages/aws-cdk/lib/init-templates/v2/app/python/requirements-dev.txt new file mode 100644 index 0000000000000..927094516e657 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/requirements-dev.txt @@ -0,0 +1 @@ +pytest==6.2.5 diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.template.txt b/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.template.txt new file mode 100644 index 0000000000000..bea43683e66a1 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.template.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==%cdk-version% +constructs%constructs-version% diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.txt deleted file mode 100644 index d6e1198b1ab1f..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e . diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py deleted file mode 100644 index d819a7bcadb77..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py +++ /dev/null @@ -1,44 +0,0 @@ -import setuptools - - -with open("README.md") as fp: - long_description = fp.read() - - -setuptools.setup( - name="%name.PythonModule%", - version="0.0.1", - - description="An empty CDK Python app", - long_description=long_description, - long_description_content_type="text/markdown", - - author="author", - - package_dir={"": "%name.PythonModule%"}, - packages=setuptools.find_packages(where="%name.PythonModule%"), - - install_requires=[ - "aws-cdk-lib==%cdk-version%", - "constructs%constructs-version%", - ], - - python_requires=">=3.6", - - classifiers=[ - "Development Status :: 4 - Beta", - - "Intended Audience :: Developers", - - "Programming Language :: JavaScript", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - - "Topic :: Software Development :: Code Generators", - "Topic :: Utilities", - - "Typing :: Typed", - ], -) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/tests/__init__.py b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/__init__.py b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py new file mode 100644 index 0000000000000..2bf2309dca779 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py @@ -0,0 +1,15 @@ +import aws_cdk as core +import aws_cdk.assertions as assertions + +from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack + +# example tests. To run these tests, uncomment this file along with the example +# resource in %name.PythonModule%/%name.PythonModule%_stack.py +def test_sqs_queue_created(): + app = core.App() + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) + +# template.has_resource_properties("AWS::SQS::Queue", { +# "VisibilityTimeout": 300 +# }) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/typescript/lib/%name%-stack.template.ts b/packages/aws-cdk/lib/init-templates/v2/app/typescript/lib/%name%-stack.template.ts index 82d70c083134e..3d6c54bd3dca2 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/typescript/lib/%name%-stack.template.ts +++ b/packages/aws-cdk/lib/init-templates/v2/app/typescript/lib/%name%-stack.template.ts @@ -1,10 +1,16 @@ import { Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; +// import * as sqs from 'aws-cdk-lib/aws-sqs'; export class %name.PascalCased%Stack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // The code that defines your stack goes here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: cdk.Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v2/app/typescript/test/%name%.test.template.ts index a2542902315c8..b3fc1aaa584d1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v2/app/typescript/test/%name%.test.template.ts @@ -1,11 +1,17 @@ -import * as cdk from 'aws-cdk-lib'; -import * as %name.PascalCased% from '../lib/%name%-stack'; +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as %name.PascalCased% from '../lib/%name%-stack'; -test('Empty Stack', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - const actual = app.synth().getStackArtifact(stack.artifactId).template; - expect(actual.Resources ?? {}).toEqual({}); +// example test. To run these tests, uncomment this file along with the +// example resource in lib/%name%-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); diff --git a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/lib/index.template.ts b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/lib/index.template.ts index 3c48194882c3d..bbd6f13d470bd 100644 --- a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/lib/index.template.ts +++ b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/lib/index.template.ts @@ -1,4 +1,5 @@ import { Construct } from 'constructs'; +// import * as sqs from 'aws-cdk-lib/aws-sqs'; export interface %name.PascalCased%Props { // Define construct properties here @@ -10,5 +11,10 @@ export class %name.PascalCased% extends Construct { super(scope, id); // Define construct contents here + + // example resource + // const queue = new sqs.Queue(this, '%name.PascalCased%Queue', { + // visibilityTimeout: cdk.Duration.seconds(300) + // }); } } diff --git a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/test/%name%.test.template.ts index 0aa210d01ae5c..a94f62e6a44ad 100644 --- a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/test/%name%.test.template.ts @@ -1,12 +1,18 @@ -import * as cdk from 'aws-cdk-lib'; -import * as %name.PascalCased% from '../lib/index'; +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as %name.PascalCased% from '../lib/index'; -test('Empty Stack', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, "TestStack"); - // WHEN - new %name.PascalCased%.%name.PascalCased%(stack, 'MyTestConstruct'); - // THEN - const actual = app.synth().getStackArtifact(stack.artifactId).template; - expect(actual.Resources ?? {}).toEqual({}); +// example test. To run these tests, uncomment this file along with the +// example resource in lib/index.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// const stack = new cdk.Stack(app, "TestStack"); +// // WHEN +// new %name.PascalCased%.%name.PascalCased%(stack, 'MyTestConstruct'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); }); diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%.template.go b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%.template.go new file mode 100644 index 0000000000000..1ae8249703373 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%.template.go @@ -0,0 +1,71 @@ +package main + +import ( + "github.com/aws/aws-cdk-go/awscdk/v2" + "github.com/aws/aws-cdk-go/awscdk/v2/awssns" + "github.com/aws/aws-cdk-go/awscdk/v2/awssnssubscriptions" + "github.com/aws/aws-cdk-go/awscdk/v2/awssqs" + "github.com/aws/constructs-go/constructs/v10" + "github.com/aws/jsii-runtime-go" +) + +type %name.PascalCased%StackProps struct { + awscdk.StackProps +} + +func New%name.PascalCased%Stack(scope constructs.Construct, id string, props *%name.PascalCased%StackProps) awscdk.Stack { + var sprops awscdk.StackProps + if props != nil { + sprops = props.StackProps + } + stack := awscdk.NewStack(scope, &id, &sprops) + + + queue := awssqs.NewQueue(stack, jsii.String("%name.PascalCased%Queue"), &awssqs.QueueProps{ + VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)), + }) + + topic := awssns.NewTopic(stack, jsii.String("%name.PascalCased%Topic"), &awssns.TopicProps{}) + topic.AddSubscription(awssnssubscriptions.NewSqsSubscription(queue, &awssnssubscriptions.SqsSubscriptionProps{})) + + return stack +} + +func main() { + app := awscdk.NewApp(nil) + + New%name.PascalCased%Stack(app, "%name.PascalCased%Stack", &%name.PascalCased%StackProps{ + awscdk.StackProps{ + Env: env(), + }, + }) + + app.Synth(nil) +} + +// env determines the AWS environment (account+region) in which our stack is to +// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html +func env() *awscdk.Environment { + // If unspecified, this stack will be "environment-agnostic". + // Account/Region-dependent features and context lookups will not work, but a + // single synthesized template can be deployed anywhere. + //--------------------------------------------------------------------------- + return nil + + // Uncomment if you know exactly what account and region you want to deploy + // the stack to. This is the recommendation for production stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String("123456789012"), + // Region: jsii.String("us-east-1"), + // } + + // Uncomment to specialize this stack for the AWS Account and Region that are + // implied by the current CLI configuration. This is recommended for dev + // stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")), + // Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")), + // } +} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%_test.template.go b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%_test.template.go new file mode 100644 index 0000000000000..606ccc499d9ab --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/%name%_test.template.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" + + "github.com/aws/aws-cdk-go/awscdk/v2" + assertions "github.com/aws/aws-cdk-go/awscdk/v2/assertions" + "github.com/aws/jsii-runtime-go" +) + +func Test%name.PascalCased%Stack(t *testing.T) { + // GIVEN + app := awscdk.NewApp(nil) + + // WHEN + stack := New%name.PascalCased%Stack(app, "MyStack", nil) + + // THEN + template := assertions.Template_FromStack(stack) + + template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{ + "VisibilityTimeout": 300, + }) + template.ResourceCountIs(jsii.String("AWS::SNS::Topic"), jsii.Number(1)) +} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/.template.gitignore b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/.template.gitignore new file mode 100644 index 0000000000000..92fe1ec34b4b6 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/.template.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# go.sum should be committed +!go.sum + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/README.md b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/README.md new file mode 100644 index 0000000000000..9b8d4b29f26e3 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK Go project! + +This is a blank project for Go development with CDK. + +**NOTICE**: Go support is still in Developer Preview. This implies that APIs may +change while we address early feedback from the community. We would love to hear +about your experience through GitHub issues. + +## Useful commands + + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk synth` emits the synthesized CloudFormation template + * `go test` run unit tests diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json new file mode 100644 index 0000000000000..a25485ed0951b --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json @@ -0,0 +1,13 @@ +{ + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/go.template.mod b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/go.template.mod new file mode 100644 index 0000000000000..3b61360cb8e15 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/go.template.mod @@ -0,0 +1,9 @@ +module %name% + +go 1.16 + +require ( + github.com/aws/aws-cdk-go/awscdk/v2 v%cdk-version% + github.com/aws/constructs-go/constructs/v10 v10.0.5 + github.com/aws/jsii-runtime-go v1.29.0 +) diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml index 79c769157ceee..1418d994e172f 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml @@ -47,21 +47,9 @@ org.junit.jupiter - junit-jupiter-api + junit-jupiter ${junit.version} test - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - org.assertj - assertj-core - 3.18.1 - test - diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java index a6f7c4f4590b4..90f54277412b1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/src/test/java/com/myorg/%name.PascalCased%StackTest.template.java @@ -1,27 +1,27 @@ package com.myorg; import software.amazon.awscdk.App; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import software.amazon.awscdk.assertions.Template; +import software.amazon.awscdk.assertions.Match; import java.io.IOException; +import java.util.HashMap; + import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; public class %name.PascalCased%StackTest { - private final static ObjectMapper JSON = - new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); @Test public void testStack() throws IOException { App app = new App(); %name.PascalCased%Stack stack = new %name.PascalCased%Stack(app, "test"); - JsonNode actual = JSON.valueToTree(app.synth().getStackArtifact(stack.getArtifactId()).getTemplate()); + Template template = Template.fromStack(stack); + + template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{ + put("VisibilityTimeout", 300); + }}); - assertThat(actual.toString()) - .contains("AWS::SQS::Queue") - .contains("AWS::SNS::Topic"); + template.resourceCountIs("AWS::SNS::Topic", 1); } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/test/%name%.test.template.js b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/test/%name%.test.template.js index b741d4045225b..15ef44fbde9d5 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/test/%name%.test.template.js +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/test/%name%.test.template.js @@ -1,12 +1,16 @@ const cdk = require('aws-cdk-lib'); +const { Match, Template } = require('aws-cdk-lib/assertions'); const %name.PascalCased% = require('../lib/%name%-stack'); test('SQS Queue and SNS Topic Created', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - const actual = JSON.stringify(app.synth().getStackArtifact(stack.artifactId).template); - expect(actual).toContain('AWS::SQS::Queue'); - expect(actual).toContain('AWS::SNS::Topic'); + const app = new cdk.App(); + // WHEN + const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::SQS::Queue', { + VisibilityTimeout: 300, + }); + + template.resourceCountIs('AWS::SNS::Topic', 1); }); diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements-dev.txt b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements-dev.txt new file mode 100644 index 0000000000000..927094516e657 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements-dev.txt @@ -0,0 +1 @@ +pytest==6.2.5 diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.template.txt b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.template.txt new file mode 100644 index 0000000000000..bea43683e66a1 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.template.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==%cdk-version% +constructs%constructs-version% diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.txt deleted file mode 100644 index ae60ed5f14ca8..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ --e . -pytest diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py deleted file mode 100644 index f9eb461178472..0000000000000 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py +++ /dev/null @@ -1,44 +0,0 @@ -import setuptools - - -with open("README.md") as fp: - long_description = fp.read() - - -setuptools.setup( - name="%name.PythonModule%", - version="0.0.1", - - description="A sample CDK Python app", - long_description=long_description, - long_description_content_type="text/markdown", - - author="author", - - package_dir={"": "%name.PythonModule%"}, - packages=setuptools.find_packages(where="%name.PythonModule%"), - - install_requires=[ - "aws-cdk-lib==%cdk-version%", - "constructs%constructs-version%", - ], - - python_requires=">=3.6", - - classifiers=[ - "Development Status :: 4 - Beta", - - "Intended Audience :: Developers", - - "Programming Language :: JavaScript", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - - "Topic :: Software Development :: Code Generators", - "Topic :: Utilities", - - "Typing :: Typed", - ], -) diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py index b51a164ff8c5e..1f4fd6b6cb9e6 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/tests/unit/test_%name.PythonModule%_stack.template.py @@ -1,19 +1,21 @@ -import json -import pytest - -import aws_cdk_lib as core +import aws_cdk as core +import aws_cdk.assertions as assertions from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack -def get_template(): +def test_sqs_queue_created(): app = core.App() - %name.PascalCased%Stack(app, "%name.StackName%") - return json.dumps(app.synth().get_stack("%name.StackName%").template) - + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) -def test_sqs_queue_created(): - assert("AWS::SQS::Queue" in get_template()) + template.has_resource_properties("AWS::SQS::Queue", { + "VisibilityTimeout": 300 + }) def test_sns_topic_created(): - assert("AWS::SNS::Topic" in get_template()) + app = core.App() + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) + + template.resource_count_is("AWS::SNS::Topic", 1) diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/test/%name%.test.template.ts b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/test/%name%.test.template.ts index a563f50309295..7c8c4d7d03a6f 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/test/%name%.test.template.ts +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/test/%name%.test.template.ts @@ -1,12 +1,17 @@ import * as cdk from 'aws-cdk-lib'; +import { Template, Match } from 'aws-cdk-lib/assertions'; import * as %name.PascalCased% from '../lib/%name%-stack'; test('SQS Queue and SNS Topic Created', () => { - const app = new cdk.App(); - // WHEN - const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); - // THEN - const actual = JSON.stringify(app.synth().getStackArtifact(stack.artifactId).template); - expect(actual).toContain('AWS::SQS::Queue'); - expect(actual).toContain('AWS::SNS::Topic'); + const app = new cdk.App(); + // WHEN + const stack = new %name.PascalCased%.%name.PascalCased%Stack(app, 'MyTestStack'); + // THEN + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::SQS::Queue', { + VisibilityTimeout: 300 + }); + template.resourceCountIs('AWS::SNS::Topic', 1); }); diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 4bfafd447d607..38723bffc0bc3 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -30,6 +30,7 @@ export enum Command { METADATA = 'metadata', INIT = 'init', VERSION = 'version', + WATCH = 'watch', } const BUNDLING_COMMANDS = [ @@ -37,6 +38,7 @@ const BUNDLING_COMMANDS = [ Command.DIFF, Command.SYNTH, Command.SYNTHESIZE, + Command.WATCH, ]; export type Arguments = { @@ -113,6 +115,10 @@ export class Configuration { const readUserContext = this.props.readUserContext ?? true; + if (userConfig.get(['build'])) { + throw new Error('The `build` key cannot be specified in the user config (~/.cdk.json), specify it in the project config (cdk.json) instead'); + } + const contextSources = [ this.commandLineContext, this.projectConfig.subSettings([CONTEXT_KEY]).makeReadOnly(), @@ -247,7 +253,7 @@ export class Settings { // Determine bundling stacks let bundlingStacks: string[]; if (BUNDLING_COMMANDS.includes(argv._[0])) { - // If we deploy, diff or synth a list of stacks exclusively we skip + // If we deploy, diff, synth or watch a list of stacks exclusively we skip // bundling for all other stacks. bundlingStacks = argv.exclusively ? argv.STACKS ?? ['*'] diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 3635d2b639002..670231627ec92 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -27,6 +27,8 @@ export async function publishAssets(manifest: cdk_assets.AssetManifest, sdk: Sdk } class PublishingAws implements cdk_assets.IAws { + private sdkCache: Map = new Map(); + constructor( /** * The base SDK to work with @@ -66,16 +68,30 @@ class PublishingAws implements cdk_assets.IAws { /** * Get an SDK appropriate for the given client options */ - private sdk(options: cdk_assets.ClientOptions): Promise { + private async sdk(options: cdk_assets.ClientOptions): Promise { const env = { ...this.targetEnv, region: options.region ?? this.targetEnv.region, // Default: same region as the stack }; - return this.aws.forEnvironment(env, Mode.ForWriting, { + const cacheKey = JSON.stringify({ + env, // region, name, account + assumeRuleArn: options.assumeRoleArn, + assumeRoleExternalId: options.assumeRoleExternalId, + }); + + const maybeSdk = this.sdkCache.get(cacheKey); + if (maybeSdk) { + return maybeSdk; + } + + const sdk = await this.aws.forEnvironment(env, Mode.ForWriting, { assumeRoleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, }); + this.sdkCache.set(cacheKey, sdk); + + return sdk; } } diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 9df63052c00c5..d870b6d31a8b5 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -38,16 +38,16 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/core": "0.0.0", - "@octokit/rest": "^18.11.4", + "@octokit/rest": "^18.12.0", "@types/archiver": "^5.3.0", "@types/fs-extra": "^8.1.2", - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", "@types/minimatch": "^3.0.5", "@types/mockery": "^1.4.30", "@types/node": "^10.17.60", "@types/promptly": "^3.0.2", - "@types/semver": "^7.3.8", + "@types/semver": "^7.3.9", "@types/sinon": "^9.0.11", "@types/table": "^6.0.0", "@types/uuid": "^8.3.1", @@ -56,14 +56,14 @@ "aws-sdk-mock": "^5.4.0", "@aws-cdk/cdk-build-tools": "0.0.0", "constructs": "^3.3.69", - "jest": "^26.6.3", + "jest": "^27.3.1", "make-runnable": "^1.3.10", "mockery": "^2.1.0", - "nock": "^13.1.3", + "nock": "^13.2.1", "@aws-cdk/pkglint": "0.0.0", "sinon": "^9.2.4", - "ts-jest": "^26.5.6", - "ts-mock-imports": "^1.3.7", + "ts-jest": "^27.0.7", + "ts-mock-imports": "^1.3.8", "xml-js": "^1.6.11" }, "dependencies": { @@ -71,11 +71,12 @@ "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", - "@jsii/check-node": "1.38.0", + "@jsii/check-node": "1.44.2", "archiver": "^5.3.0", "aws-sdk": "^2.979.0", - "camelcase": "^6.2.0", + "camelcase": "^6.2.1", "cdk-assets": "0.0.0", + "chokidar": "^3.5.2", "colors": "^1.4.0", "decamelize": "^5.0.1", "fs-extra": "^9.1.0", @@ -86,7 +87,7 @@ "proxy-agent": "^5.0.0", "semver": "^7.3.5", "source-map-support": "^0.5.20", - "table": "^6.7.2", + "table": "^6.7.3", "uuid": "^8.3.2", "wrap-ansi": "^7.0.0", "yaml": "1.10.2", diff --git a/packages/aws-cdk/test/account-cache.test.ts b/packages/aws-cdk/test/account-cache.test.ts index 89887cc627d71..fd98edfda5573 100644 --- a/packages/aws-cdk/test/account-cache.test.ts +++ b/packages/aws-cdk/test/account-cache.test.ts @@ -83,8 +83,6 @@ test('fetch(k, resolver) can be used to "atomically" get + resolve + put', async }); test(`cache is nuked if it exceeds ${AccountAccessKeyCache.MAX_ENTRIES} entries`, async () => { - // This makes a lot of promises, so it can queue for a while... - jest.setTimeout(30_000); const { cacheDir, cacheFile, cache } = await makeCache(); @@ -110,7 +108,9 @@ test(`cache is nuked if it exceeds ${AccountAccessKeyCache.MAX_ENTRIES} entries` } finally { await nukeCache(cacheDir); } -}); +}, +// This makes a lot of promises, so it can queue for a while... +30_000); test('cache pretends to be empty if cache file does not contain JSON', async() => { const { cacheDir, cacheFile, cache } = await makeCache(); diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index eb551ac528ec4..2b199ea225b87 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -29,6 +29,7 @@ const FAKE_STACK_TERMINATION_PROTECTION = testStack({ let sdk: MockSdk; let sdkProvider: MockSdkProvider; let cfnMocks: MockedObject>; + beforeEach(() => { jest.resetAllMocks(); @@ -62,7 +63,7 @@ beforeEach(() => { updateTerminationProtection: jest.fn((_o) => ({ StackId: 'stack-id' })), }; sdk.stubCloudFormation(cfnMocks as any); - + sdk.stubGetEndpointSuffix(() => 'amazonaws.com'); }); function standardDeployStackArguments(): DeployStackOptions { @@ -80,10 +81,15 @@ test("calls tryHotswapDeployment() if 'hotswap' is true", async () => { await deployStack({ ...standardDeployStackArguments(), hotswap: true, + extraUserAgent: 'extra-user-agent', }); // THEN expect(tryHotswapDeployment).toHaveBeenCalled(); + // check that the extra User-Agent is honored + expect(sdk.appendCustomUserAgent).toHaveBeenCalledWith('extra-user-agent'); + // check that the fallback has been called if hotswapping failed + expect(sdk.appendCustomUserAgent).toHaveBeenCalledWith('cdk-hotswap/fallback'); }); test("does not call tryHotswapDeployment() if 'hotswap' is false", async () => { @@ -97,7 +103,7 @@ test("does not call tryHotswapDeployment() if 'hotswap' is false", async () => { expect(tryHotswapDeployment).not.toHaveBeenCalled(); }); -test("rollback defaults to disabled if 'hotswap' is true", async () => { +test("rollback still defaults to enabled even if 'hotswap' is enabled", async () => { // WHEN await deployStack({ ...standardDeployStackArguments(), @@ -106,7 +112,7 @@ test("rollback defaults to disabled if 'hotswap' is true", async () => { }); // THEN - expect(cfnMocks.executeChangeSet).toHaveBeenCalledWith(expect.objectContaining({ + expect(cfnMocks.executeChangeSet).not.toHaveBeenCalledWith(expect.objectContaining({ DisableRollback: true, })); }); diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index d2e65907527fa..b9c5637acce1a 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -137,7 +137,7 @@ test('the application set in --app is executed', async () => { // GIVEN config.settings.set(['app'], 'cloud-executable'); mockSpawn({ - commandLine: ['cloud-executable'], + commandLine: 'cloud-executable', sideEffect: () => writeOutputAssembly(), }); @@ -149,7 +149,7 @@ test('the application set in --app is executed as-is if it contains a filename t // GIVEN config.settings.set(['app'], 'does-not-exist'); mockSpawn({ - commandLine: ['does-not-exist'], + commandLine: 'does-not-exist', sideEffect: () => writeOutputAssembly(), }); @@ -161,7 +161,7 @@ test('the application set in --app is executed with arguments', async () => { // GIVEN config.settings.set(['app'], 'cloud-executable an-arg'); mockSpawn({ - commandLine: ['cloud-executable', 'an-arg'], + commandLine: 'cloud-executable an-arg', sideEffect: () => writeOutputAssembly(), }); @@ -174,7 +174,7 @@ test('application set in --app as `*.js` always uses handler on windows', async sinon.stub(process, 'platform').value('win32'); config.settings.set(['app'], 'windows.js'); mockSpawn({ - commandLine: [process.execPath, 'windows.js'], + commandLine: process.execPath + ' windows.js', sideEffect: () => writeOutputAssembly(), }); @@ -186,7 +186,7 @@ test('application set in --app is `*.js` and executable', async () => { // GIVEN config.settings.set(['app'], 'executable-app.js'); mockSpawn({ - commandLine: ['executable-app.js'], + commandLine: 'executable-app.js', sideEffect: () => writeOutputAssembly(), }); @@ -194,6 +194,36 @@ test('application set in --app is `*.js` and executable', async () => { await execProgram(sdkProvider, config); }); +test('cli throws when the `build` script fails', async () => { + // GIVEN + config.settings.set(['build'], 'fake-command'); + mockSpawn({ + commandLine: 'fake-command', + exitCode: 127, + }); + + // WHEN + await expect(execProgram(sdkProvider, config)).rejects.toEqual(new Error('Subprocess exited with error 127')); +}, TEN_SECOND_TIMEOUT); + +test('cli does not throw when the `build` script succeeds', async () => { + // GIVEN + config.settings.set(['build'], 'real command'); + config.settings.set(['app'], 'executable-app.js'); + mockSpawn({ + commandLine: 'real command', // `build` key is not split on whitespace + exitCode: 0, + }, + { + commandLine: 'executable-app.js', + sideEffect: () => writeOutputAssembly(), + }); + + // WHEN + await execProgram(sdkProvider, config); +}, TEN_SECOND_TIMEOUT); + + function writeOutputAssembly() { const asm = testAssembly({ stacks: [], diff --git a/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts new file mode 100644 index 0000000000000..717f68ecd5b29 --- /dev/null +++ b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts @@ -0,0 +1,364 @@ +import * as AWS from 'aws-sdk'; +import * as setup from './hotswap-test-setup'; + +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockRegisterTaskDef: jest.Mock; +let mockUpdateService: (params: AWS.ECS.UpdateServiceRequest) => AWS.ECS.UpdateServiceResponse; + +beforeEach(() => { + hotswapMockSdkProvider = setup.setupHotswapTests(); + + mockRegisterTaskDef = jest.fn(); + mockUpdateService = jest.fn(); + hotswapMockSdkProvider.stubEcs({ + registerTaskDefinition: mockRegisterTaskDef, + updateService: mockUpdateService, + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, + }); +}); + +test('should call registerTaskDefinition and updateService for a difference only in the TaskDefinition with a Family property', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image1' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Service', 'AWS::ECS::Service', + 'arn:aws:ecs:region:account:service/my-cluster/my-service'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image2' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockRegisterTaskDef).toBeCalledWith({ + family: 'my-task-def', + containerDefinitions: [ + { image: 'image2' }, + ], + }); + expect(mockUpdateService).toBeCalledWith({ + service: 'arn:aws:ecs:region:account:service/my-cluster/my-service', + cluster: 'my-cluster', + taskDefinition: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + deploymentConfiguration: { + minimumHealthyPercent: 0, + }, + forceNewDeployment: true, + }); +}); + +test('any other TaskDefinition property change besides ContainerDefinition cannot be hotswapped', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image1' }, + ], + Cpu: '256', + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Service', 'AWS::ECS::Service', + 'arn:aws:ecs:region:account:service/my-cluster/my-service'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image2' }, + ], + Cpu: '512', + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); +}); + +test('should call registerTaskDefinition and updateService for a difference only in the TaskDefinition without a Family property', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + ContainerDefinitions: [ + { Image: 'image1' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('TaskDef', 'AWS::ECS::TaskDefinition', + 'arn:aws:ecs:region:account:task-definition/my-task-def:2'), + setup.stackSummaryOf('Service', 'AWS::ECS::Service', + 'arn:aws:ecs:region:account:service/my-cluster/my-service'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + ContainerDefinitions: [ + { Image: 'image2' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockRegisterTaskDef).toBeCalledWith({ + family: 'my-task-def', + containerDefinitions: [ + { image: 'image2' }, + ], + }); + expect(mockUpdateService).toBeCalledWith({ + service: 'arn:aws:ecs:region:account:service/my-cluster/my-service', + cluster: 'my-cluster', + taskDefinition: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + deploymentConfiguration: { + minimumHealthyPercent: 0, + }, + forceNewDeployment: true, + }); +}); + +test('a difference just in a TaskDefinition, without any services using it, is not hotswappable', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + ContainerDefinitions: [ + { Image: 'image1' }, + ], + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('TaskDef', 'AWS::ECS::TaskDefinition', + 'arn:aws:ecs:region:account:task-definition/my-task-def:2'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + ContainerDefinitions: [ + { Image: 'image2' }, + ], + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockRegisterTaskDef).not.toHaveBeenCalled(); +}); + +test('if anything besides an ECS Service references the changed TaskDefinition, hotswapping is not possible', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image1' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + Function: { + Type: 'AWS::Lambda::Function', + Properties: { + Environment: { + Variables: { + TaskDefRevArn: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Service', 'AWS::ECS::Service', + 'arn:aws:ecs:region:account:service/my-cluster/my-service'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image2' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + Function: { + Type: 'AWS::Lambda::Function', + Properties: { + Environment: { + Variables: { + TaskDefRevArn: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockRegisterTaskDef).not.toHaveBeenCalled(); +}); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts new file mode 100644 index 0000000000000..28af0119bcb3d --- /dev/null +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -0,0 +1,317 @@ +import { Lambda, StepFunctions } from 'aws-sdk'; +import * as setup from './hotswap-test-setup'; + +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; +let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; +let mockGetEndpointSuffix: () => string; + +beforeEach(() => { + hotswapMockSdkProvider = setup.setupHotswapTests(); + mockUpdateLambdaCode = jest.fn(); + mockUpdateMachineDefinition = jest.fn(); + mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); + hotswapMockSdkProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); + hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); + hotswapMockSdkProvider.stubGetEndpointSuffix(mockGetEndpointSuffix); +}); + +test('returns a deployStackResult with noOp=true when it receives an empty set of changes', async () => { + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(setup.cdkStackArtifactOf()); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(deployStackResult?.noOp).toBeTruthy(); + expect(deployStackResult?.stackArn).toEqual(setup.STACK_ID); +}); + +test('A change to only a non-hotswappable resource results in a full deployment', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + SomethingElse: { + Type: 'AWS::CloudFormation::SomethingElse', + Properties: { + Prop: 'old-value', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + SomethingElse: { + Type: 'AWS::CloudFormation::SomethingElse', + Properties: { + Prop: 'new-value', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); + +test('A change to both a hotswappable resource and a non-hotswappable resource results in a full deployment', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + SomethingElse: { + Type: 'AWS::CloudFormation::SomethingElse', + Properties: { + Prop: 'old-value', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + SomethingElse: { + Type: 'AWS::CloudFormation::SomethingElse', + Properties: { + Prop: 'new-value', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); + +test('changes only to CDK::Metadata result in a noOp', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + MetaData: { + Type: 'AWS::CDK::MetaData', + Properties: { + Prop: 'old-value', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + MetaData: { + Type: 'AWS::CDK::Metadata', + Properties: { + Prop: 'new-value', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(deployStackResult?.noOp).toEqual(true); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); + +test('resource deletions require full deployments', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf(); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); +}); + +test('can correctly reference AWS::Partition in hotswappable changes', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: { + 'Fn::Join': [ + '', + [ + { Ref: 'AWS::Partition' }, + '-', + 'my-function', + ], + ], + }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: { + 'Fn::Join': [ + '', + [ + { Ref: 'AWS::Partition' }, + '-', + 'my-function', + ], + ], + }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ + FunctionName: 'aws-my-function', + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }); +}); + +test('can correctly reference AWS::URLSuffix in hotswappable changes', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: { + 'Fn::Join': ['', [ + 'my-function-', + { Ref: 'AWS::URLSuffix' }, + '-', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: { + 'Fn::Join': ['', [ + 'my-function-', + { Ref: 'AWS::URLSuffix' }, + '-', + { Ref: 'AWS::URLSuffix' }, + ]], + }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ + FunctionName: 'my-function-amazonaws.com-amazonaws.com', + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }); + expect(mockGetEndpointSuffix).toHaveBeenCalledTimes(1); + + // the User-Agent is set correctly + expect(hotswapMockSdkProvider.mockSdkProvider.sdk.appendCustomUserAgent) + .toHaveBeenCalledWith('cdk-hotswap/success-lambda-function'); + expect(hotswapMockSdkProvider.mockSdkProvider.sdk.removeCustomUserAgent) + .toHaveBeenCalledWith('cdk-hotswap/success-lambda-function'); +}); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts new file mode 100644 index 0000000000000..f96b419b94b20 --- /dev/null +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -0,0 +1,104 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import { CloudFormation } from 'aws-sdk'; +import * as AWS from 'aws-sdk'; +import * as lambda from 'aws-sdk/clients/lambda'; +import * as stepfunctions from 'aws-sdk/clients/stepfunctions'; +import { DeployStackResult } from '../../../lib/api'; +import * as deployments from '../../../lib/api/hotswap-deployments'; +import { Template } from '../../../lib/api/util/cloudformation'; +import { testStack, TestStackArtifact } from '../../util'; +import { MockSdkProvider, SyncHandlerSubsetOf } from '../../util/mock-sdk'; +import { FakeCloudformationStack } from '../fake-cloudformation-stack'; + +const STACK_NAME = 'withouterrors'; +export const STACK_ID = 'stackId'; + +let hotswapMockSdkProvider: HotswapMockSdkProvider; +let currentCfnStack: FakeCloudformationStack; +const currentCfnStackResources: CloudFormation.StackResourceSummary[] = []; + +export function setupHotswapTests() { + jest.resetAllMocks(); + // clear the array + currentCfnStackResources.splice(0); + hotswapMockSdkProvider = new HotswapMockSdkProvider(); + currentCfnStack = new FakeCloudformationStack({ + stackName: STACK_NAME, + stackId: STACK_ID, + }); + + return hotswapMockSdkProvider; +} + +export function cdkStackArtifactOf(testStackArtifact: Partial = {}): cxapi.CloudFormationStackArtifact { + return testStack({ + stackName: STACK_NAME, + ...testStackArtifact, + }); +} + +export function pushStackResourceSummaries(...items: CloudFormation.StackResourceSummary[]) { + currentCfnStackResources.push(...items); +} + +export function setCurrentCfnStackTemplate(template: Template) { + currentCfnStack.setTemplate(template); +} + +export function stackSummaryOf(logicalId: string, resourceType: string, physicalResourceId: string): CloudFormation.StackResourceSummary { + return { + LogicalResourceId: logicalId, + PhysicalResourceId: physicalResourceId, + ResourceType: resourceType, + ResourceStatus: 'CREATE_COMPLETE', + LastUpdatedTimestamp: new Date(), + }; +} + +export class HotswapMockSdkProvider { + public readonly mockSdkProvider: MockSdkProvider; + + constructor() { + this.mockSdkProvider = new MockSdkProvider({ realSdk: false }); + + this.mockSdkProvider.stubCloudFormation({ + listStackResources: ({ StackName: stackName }) => { + if (stackName !== STACK_NAME) { + throw new Error(`Expected Stack name in listStackResources() call to be: '${STACK_NAME}', but received: ${stackName}'`); + } + return { + StackResourceSummaries: currentCfnStackResources, + }; + }, + }); + } + + public setUpdateStateMachineMock( + mockUpdateMachineDefinition: (input: stepfunctions.UpdateStateMachineInput) => stepfunctions.UpdateStateMachineOutput, + ) { + this.mockSdkProvider.stubStepFunctions({ + updateStateMachine: mockUpdateMachineDefinition, + }); + } + + public setUpdateFunctionCodeMock(mockUpdateLambdaCode: (input: lambda.UpdateFunctionCodeRequest) => lambda.FunctionConfiguration) { + this.mockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + }); + } + + public stubEcs(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}): void { + this.mockSdkProvider.stubEcs(stubs, additionalProperties); + } + + public stubGetEndpointSuffix(stub: () => string) { + this.mockSdkProvider.stubGetEndpointSuffix(stub); + } + + public tryHotswapDeployment( + stackArtifact: cxapi.CloudFormationStackArtifact, + assetParams: { [key: string]: string } = {}, + ): Promise { + return deployments.tryHotswapDeployment(this.mockSdkProvider, assetParams, currentCfnStack, stackArtifact); + } +} diff --git a/packages/aws-cdk/test/api/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts similarity index 55% rename from packages/aws-cdk/test/api/hotswap-deployments.test.ts rename to packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts index 5bb6ff8b7b466..8c9d5498967f7 100644 --- a/packages/aws-cdk/test/api/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts @@ -1,56 +1,18 @@ -import * as cxapi from '@aws-cdk/cx-api'; -import { CloudFormation, Lambda } from 'aws-sdk'; -import { tryHotswapDeployment } from '../../lib/api/hotswap-deployments'; -import { testStack, TestStackArtifact } from '../util'; -import { MockSdkProvider } from '../util/mock-sdk'; -import { FakeCloudformationStack } from './fake-cloudformation-stack'; +import { Lambda } from 'aws-sdk'; +import * as setup from './hotswap-test-setup'; -const STACK_NAME = 'withouterrors'; -const STACK_ID = 'stackId'; - -let mockSdkProvider: MockSdkProvider; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; -let currentCfnStack: FakeCloudformationStack; -const currentCfnStackResources: CloudFormation.StackResourceSummary[] = []; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { - jest.resetAllMocks(); - mockSdkProvider = new MockSdkProvider({ realSdk: false }); + hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn(); - mockSdkProvider.stubLambda({ - updateFunctionCode: mockUpdateLambdaCode, - }); - // clear the array - currentCfnStackResources.splice(0); - mockSdkProvider.stubCloudFormation({ - listStackResources: ({ StackName: stackName }) => { - if (stackName !== STACK_NAME) { - throw new Error(`Expected Stack name in listStackResources() call to be: '${STACK_NAME}', but received: ${stackName}'`); - } - return { - StackResourceSummaries: currentCfnStackResources, - }; - }, - }); - currentCfnStack = new FakeCloudformationStack({ - stackName: STACK_NAME, - stackId: STACK_ID, - }); -}); - -test('returns a deployStackResult with noOp=true when it receives an empty set of changes', async () => { - // WHEN - const deployStackResult = await tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifactOf()); - - // THEN - expect(deployStackResult).not.toBeUndefined(); - expect(deployStackResult?.noOp).toBeTruthy(); - expect(deployStackResult?.stackArn).toEqual(STACK_ID); + hotswapMockSdkProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); }); -test('returns undefined when it a new Lambda function is added to the Stack', async () => { +test('returns undefined when a new Lambda function is added to the Stack', async () => { // GIVEN - const cdkStackArtifact = cdkStackArtifactOf({ + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Func: { @@ -61,7 +23,7 @@ test('returns undefined when it a new Lambda function is added to the Stack', as }); // WHEN - const deployStackResult = await tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -69,7 +31,7 @@ test('returns undefined when it a new Lambda function is added to the Stack', as test('calls the updateLambdaCode() API when it receives only a code difference in a Lambda function', async () => { // GIVEN - currentCfnStack.setTemplate({ + setup.setCurrentCfnStackTemplate({ Resources: { Func: { Type: 'AWS::Lambda::Function', @@ -86,7 +48,7 @@ test('calls the updateLambdaCode() API when it receives only a code difference i }, }, }); - const cdkStackArtifact = cdkStackArtifactOf({ + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Func: { @@ -107,7 +69,7 @@ test('calls the updateLambdaCode() API when it receives only a code difference i }); // WHEN - const deployStackResult = await tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -120,7 +82,7 @@ test('calls the updateLambdaCode() API when it receives only a code difference i test("correctly evaluates the function's name when it references a different resource from the template", async () => { // GIVEN - currentCfnStack.setTemplate({ + setup.setCurrentCfnStackTemplate({ Resources: { Bucket: { Type: 'AWS::S3::Bucket', @@ -146,8 +108,8 @@ test("correctly evaluates the function's name when it references a different res }, }, }); - currentCfnStackResources.push(stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'mybucket')); - const cdkStackArtifact = cdkStackArtifactOf({ + setup.pushStackResourceSummaries(setup.stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'mybucket')); + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Bucket: { @@ -177,7 +139,7 @@ test("correctly evaluates the function's name when it references a different res }); // WHEN - const deployStackResult = await tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -190,7 +152,7 @@ test("correctly evaluates the function's name when it references a different res test("correctly falls back to taking the function's name from the current stack if it can't evaluate it in the template", async () => { // GIVEN - currentCfnStack.setTemplate({ + setup.setCurrentCfnStackTemplate({ Parameters: { Param1: { Type: 'String' }, AssetBucketParam: { Type: 'String' }, @@ -211,8 +173,8 @@ test("correctly falls back to taking the function's name from the current stack }, }, }); - currentCfnStackResources.push(stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-function')); - const cdkStackArtifact = cdkStackArtifactOf({ + setup.pushStackResourceSummaries(setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-function')); + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Parameters: { Param1: { Type: 'String' }, @@ -237,9 +199,7 @@ test("correctly falls back to taking the function's name from the current stack }); // WHEN - const deployStackResult = await tryHotswapDeployment(mockSdkProvider, { - AssetBucketParam: 'asset-bucket', - }, currentCfnStack, cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { AssetBucketParam: 'asset-bucket' }); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -252,7 +212,7 @@ test("correctly falls back to taking the function's name from the current stack test("will not perform a hotswap deployment if it cannot find a Ref target (outside the function's name)", async () => { // GIVEN - currentCfnStack.setTemplate({ + setup.setCurrentCfnStackTemplate({ Parameters: { Param1: { Type: 'String' }, }, @@ -271,8 +231,8 @@ test("will not perform a hotswap deployment if it cannot find a Ref target (outs }, }, }); - currentCfnStackResources.push(stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func')); - const cdkStackArtifact = cdkStackArtifactOf({ + setup.pushStackResourceSummaries(setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func')); + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Parameters: { Param1: { Type: 'String' }, @@ -296,13 +256,13 @@ test("will not perform a hotswap deployment if it cannot find a Ref target (outs // THEN await expect(() => - tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow(/Parameter or resource 'Param1' could not be found for evaluation/); }); test("will not perform a hotswap deployment if it doesn't know how to handle a specific attribute (outside the function's name)", async () => { // GIVEN - currentCfnStack.setTemplate({ + setup.setCurrentCfnStackTemplate({ Resources: { Bucket: { Type: 'AWS::S3::Bucket', @@ -321,11 +281,11 @@ test("will not perform a hotswap deployment if it doesn't know how to handle a s }, }, }); - currentCfnStackResources.push( - stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), - stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'my-bucket'), + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), + setup.stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'my-bucket'), ); - const cdkStackArtifact = cdkStackArtifactOf({ + const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Bucket: { @@ -349,23 +309,142 @@ test("will not perform a hotswap deployment if it doesn't know how to handle a s // THEN await expect(() => - tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow("We don't support the 'UnknownAttribute' attribute of the 'AWS::S3::Bucket' resource. This is a CDK limitation. Please report it at https://github.com/aws/aws-cdk/issues/new/choose"); }); -function cdkStackArtifactOf(testStackArtifact: Partial = {}): cxapi.CloudFormationStackArtifact { - return testStack({ - stackName: STACK_NAME, - ...testStackArtifact, +test('calls the updateLambdaCode() API when it receives a code difference in a Lambda function with no name', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + }, + Metadata: { + 'aws:asset:path': 'current-path', + }, + }, + }, }); -} + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + }, + Metadata: { + 'aws:asset:path': 'current-path', + }, + }, + }, + }, + }); + + // WHEN + setup.pushStackResourceSummaries(setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'mock-function-resource-id')); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ + FunctionName: 'mock-function-resource-id', + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }); +}); -function stackSummaryOf(logicalId: string, resourceType: string, physicalResourceId: string): CloudFormation.StackResourceSummary { - return { - LogicalResourceId: logicalId, - PhysicalResourceId: physicalResourceId, - ResourceType: resourceType, - ResourceStatus: 'CREATE_COMPLETE', - LastUpdatedTimestamp: new Date(), - }; -} +test('does not call the updateLambdaCode() API when it receives a change that is not a code difference in a Lambda function', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + PackageType: 'Zip', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + PackageType: 'Image', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); + +test('does not call the updateLambdaCode() API when a resource with type that is not AWS::Lambda::Function but has the same properties is changed', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::NotLambda::NotAFunction', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::NotLambda::NotAFunction', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); +}); diff --git a/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts new file mode 100644 index 0000000000000..289921fb2bd76 --- /dev/null +++ b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts @@ -0,0 +1,483 @@ +import { StepFunctions } from 'aws-sdk'; +import * as setup from './hotswap-test-setup'; + +let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; + +beforeEach(() => { + hotswapMockSdkProvider = setup.setupHotswapTests(); + mockUpdateMachineDefinition = jest.fn(); + hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); +}); + +test('returns undefined when a new StateMachine is added to the Stack', async () => { + // GIVEN + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); +}); + +test('calls the updateStateMachine() API when it receives only a definitionString change without Fn::Join in a state machine', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ Prop: "old-value" }', + StateMachineName: 'my-machine', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ Prop: "new-value" }', + StateMachineName: 'my-machine', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ + definition: '{ Prop: "new-value" }', + stateMachineArn: 'my-machine', + }); +}); + +test('calls the updateStateMachine() API when it receives only a definitionString change with Fn::Join in a state machine', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '\n', + [ + '{', + ' "StartAt" : "SuccessState"', + ' "States" : {', + ' "SuccessState": {', + ' "Type": "Pass"', + ' "Result": "Success"', + ' "End": true', + ' }', + ' }', + '}', + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '\n', + [ + '{', + ' "StartAt": "SuccessState",', + ' "States": {', + ' "SuccessState": {', + ' "Type": "Succeed"', + ' }', + ' }', + '}', + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ + definition: JSON.stringify({ + StartAt: 'SuccessState', + States: { + SuccessState: { + Type: 'Succeed', + }, + }, + }, null, 2), + stateMachineArn: 'my-machine', + }); +}); + +test('calls the updateStateMachine() API when it receives a change to the definitionString in a state machine that has no name', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ "Prop" : "old-value" }', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ "Prop" : "new-value" }', + }, + }, + }, + }, + }); + + // WHEN + setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id')); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ + definition: '{ "Prop" : "new-value" }', + stateMachineArn: 'mock-machine-resource-id', // the sdk will convert the ID to the arn in a production environment + }); +}); + +test('does not call the updateStateMachine() API when it receives a change to a property that is not the definitionString in a state machine', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ "Prop" : "old-value" }', + LoggingConfiguration: { // non-definitionString property + IncludeExecutionData: true, + }, + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: '{ "Prop" : "new-value" }', + LoggingConfiguration: { + IncludeExecutionData: false, + }, + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); +}); + +test('does not call the updateStateMachine() API when a resource has a DefinitionString property but is not an AWS::StepFunctions::StateMachine is changed', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Machine: { + Type: 'AWS::NotStepFunctions::NotStateMachine', + Properties: { + DefinitionString: '{ Prop: "old-value" }', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Machine: { + Type: 'AWS::NotStepFunctions::NotStateMachine', + Properties: { + DefinitionString: '{ Prop: "new-value" }', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).toBeUndefined(); + expect(mockUpdateMachineDefinition).not.toHaveBeenCalled(); +}); + +test('can correctly hotswap old style synth changes', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Parameters: { AssetParam1: { Type: 'String' } }, + Resources: { + SM: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { Ref: 'AssetParam1' }, + StateMachineName: 'machine-name', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Parameters: { AssetParam2: { Type: String } }, + Resources: { + SM: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { Ref: 'AssetParam2' }, + StateMachineName: 'machine-name', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { AssetParam2: 'asset-param-2' }); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ + definition: 'asset-param-2', + stateMachineArn: 'machine-name', + }); +}); + +test('calls the updateStateMachine() API when it receives a change to the definitionString that uses Attributes in a state machine', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + }, + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '\n', + [ + '{', + ' "StartAt" : "SuccessState"', + ' "States" : {', + ' "SuccessState": {', + ' "Type": "Succeed"', + ' }', + ' }', + '}', + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + }, + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '"Resource": ', + { 'Fn::GetAtt': ['Func', 'Arn'] }, + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }, + }); + + // WHEN + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id'), + setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), + ); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateMachineDefinition).toHaveBeenCalledWith({ + definition: '"Resource": arn:aws:lambda:here:123456789012:function:my-func', + stateMachineArn: 'my-machine', + }); +}); + +test("will not perform a hotswap deployment if it cannot find a Ref target (outside the state machine's name)", async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Parameters: { + Param1: { Type: 'String' }, + }, + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '{ Prop: "old-value" }, ', + '{ "Param" : ', + { 'Fn::Sub': '${Param1}' }, + ' }', + ], + ], + }, + }, + }, + }, + }); + setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'my-machine')); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Parameters: { + Param1: { Type: 'String' }, + }, + Resources: { + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '{ Prop: "new-value" }, ', + '{ "Param" : ', + { 'Fn::Sub': '${Param1}' }, + ' }', + ], + ], + }, + }, + }, + }, + }, + }); + + // THEN + await expect(() => + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), + ).rejects.toThrow(/Parameter or resource 'Param1' could not be found for evaluation/); +}); + +test("will not perform a hotswap deployment if it doesn't know how to handle a specific attribute (outside the state machines's name)", async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Bucket: { + Type: 'AWS::S3::Bucket', + }, + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '{ Prop: "old-value" }, ', + '{ "S3Bucket" : ', + { 'Fn::GetAtt': ['Bucket', 'UnknownAttribute'] }, + ' }', + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'my-machine'), + setup.stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'my-bucket'), + ); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Bucket: { + Type: 'AWS::Lambda::Function', + }, + Machine: { + Type: 'AWS::StepFunctions::StateMachine', + Properties: { + DefinitionString: { + 'Fn::Join': [ + '', + [ + '{ Prop: "new-value" }, ', + '{ "S3Bucket" : ', + { 'Fn::GetAtt': ['Bucket', 'UnknownAttribute'] }, + ' }', + ], + ], + }, + StateMachineName: 'my-machine', + }, + }, + }, + }, + }); + + // THEN + await expect(() => + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), + ).rejects.toThrow("We don't support the 'UnknownAttribute' attribute of the 'AWS::S3::Bucket' resource. This is a CDK limitation. Please report it at https://github.com/aws/aws-cdk/issues/new/choose"); +}); diff --git a/packages/aws-cdk/test/api/stack-activity-monitor.test.ts b/packages/aws-cdk/test/api/stack-activity-monitor.test.ts index c76ddda1ceb00..b793acda8b68b 100644 --- a/packages/aws-cdk/test/api/stack-activity-monitor.test.ts +++ b/packages/aws-cdk/test/api/stack-activity-monitor.test.ts @@ -27,12 +27,12 @@ test('prints 0/4 progress report, when addActivity is called with an "IN_PROGRES ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); }); - expect(output[0].trim()).toStrictEqual(`0/4 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 0/4 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); }); test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COMPLETE" ResourceStatus', () => { @@ -51,12 +51,12 @@ test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COM ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); }); - expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); }); test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COMPLETE_CLEAN_IN_PROGRESS" ResourceStatus', () => { @@ -75,12 +75,12 @@ test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COM ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); }); - expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE_CLEA')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE_CLEA')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); }); @@ -100,12 +100,12 @@ test('prints 1/4 progress report, when addActivity is called with an "ROLLBACK_C ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); }); - expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${yellow('ROLLBACK_COMPLETE_CL')} | AWS::CloudFormation::Stack | ${yellow(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 1/4 | ${HUMAN_TIME} | ${yellow('ROLLBACK_COMPLETE_CL')} | AWS::CloudFormation::Stack | ${yellow(bold('stack1'))}`); }); test('prints 0/4 progress report, when addActivity is called with an "UPDATE_FAILED" ResourceStatus', () => { @@ -124,12 +124,12 @@ test('prints 0/4 progress report, when addActivity is called with an "UPDATE_FAI ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); }); - expect(output[0].trim()).toStrictEqual(`0/4 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 0/4 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); }); @@ -149,7 +149,7 @@ test('does not print "Failed Resources:" list, when all deployments are successf ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); historyActivityPrinter.addActivity({ @@ -160,7 +160,7 @@ test('does not print "Failed Resources:" list, when all deployments are successf ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); historyActivityPrinter.addActivity({ @@ -171,16 +171,16 @@ test('does not print "Failed Resources:" list, when all deployments are successf ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); historyActivityPrinter.stop(); }); expect(output.length).toStrictEqual(3); - expect(output[0].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); - expect(output[1].trim()).toStrictEqual(`1/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); - expect(output[2].trim()).toStrictEqual(`2/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack2'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[1].trim()).toStrictEqual(`stack-name | 1/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[2].trim()).toStrictEqual(`stack-name | 2/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack2'))}`); }); test('prints "Failed Resources:" list, when at least one deployment fails', () => { @@ -199,7 +199,7 @@ test('prints "Failed Resources:" list, when at least one deployment fails', () = ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); historyActivityPrinter.addActivity({ @@ -210,15 +210,15 @@ test('prints "Failed Resources:" list, when at least one deployment fails', () = ResourceType: 'AWS::CloudFormation::Stack', StackId: '', EventId: '', - StackName: '', + StackName: 'stack-name', }, }); historyActivityPrinter.stop(); }); expect(output.length).toStrictEqual(4); - expect(output[0].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); - expect(output[1].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`stack-name | 0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[1].trim()).toStrictEqual(`stack-name | 0/2 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); expect(output[2].trim()).toStrictEqual('Failed resources:'); - expect(output[3].trim()).toStrictEqual(`${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); + expect(output[3].trim()).toStrictEqual(`stack-name | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); }); diff --git a/packages/aws-cdk/test/aws-sdk-non-public-apis.test.ts b/packages/aws-cdk/test/aws-sdk-non-public-apis.test.ts new file mode 100644 index 0000000000000..7b7c5b42d8ecb --- /dev/null +++ b/packages/aws-cdk/test/aws-sdk-non-public-apis.test.ts @@ -0,0 +1,25 @@ +// The ECS hotswapping functionality in lib/api/hotswap/ecs-services.ts +// uses some non-public APIs of the JS AWS SDK for waiting on the deployment to finish. +// These unit tests are here to confirm the non-public elements are present and working as expected, +// and do not get changed in a new version of the aws-sdk package + +import * as AWS from 'aws-sdk'; + +let ecsService: AWS.ECS; +beforeEach(() => { + ecsService = new AWS.ECS(); +}); + +test("the 'waiters' API is available in the current AWS SDK", () => { + const waiters = (ecsService as any).api?.waiters; + + expect(waiters).not.toBeUndefined(); + expect(typeof waiters).toBe('object'); +}); + +test("the 'ResourceWaiter' API is available in the current AWS SDK", () => { + const resourceWaiter = new (AWS as any).ResourceWaiter(ecsService, 'servicesStable'); + + // make sure the 'wait' method is available + expect(typeof resourceWaiter.wait).toBe('function'); +}); diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index 19276b15b7b7b..9ed9a21445f4d 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -1,3 +1,52 @@ +// We need to mock the chokidar library, used by 'cdk watch' +const mockChokidarWatcherOn = jest.fn(); +const fakeChokidarWatcher = { + on: mockChokidarWatcherOn, +}; +const fakeChokidarWatcherOn = { + get readyCallback(): () => void { + expect(mockChokidarWatcherOn.mock.calls.length).toBeGreaterThanOrEqual(1); + // The call to the first 'watcher.on()' in the production code is the one we actually want here. + // This is a pretty fragile, but at least with this helper class, + // we would have to change it only in one place if it ever breaks + const firstCall = mockChokidarWatcherOn.mock.calls[0]; + // let's make sure the first argument is the 'ready' event, + // just to be double safe + expect(firstCall[0]).toBe('ready'); + // the second argument is the callback + return firstCall[1]; + }, + + get fileEventCallback(): (event: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', path: string) => Promise { + expect(mockChokidarWatcherOn.mock.calls.length).toBeGreaterThanOrEqual(2); + const secondCall = mockChokidarWatcherOn.mock.calls[1]; + // let's make sure the first argument is not the 'ready' event, + // just to be double safe + expect(secondCall[0]).not.toBe('ready'); + // the second argument is the callback + return secondCall[1]; + }, +}; + +const mockChokidarWatch = jest.fn(); +jest.mock('chokidar', () => ({ + watch: mockChokidarWatch, +})); +const fakeChokidarWatch = { + get includeArgs(): string[] { + expect(mockChokidarWatch.mock.calls.length).toBe(1); + // the include args are the first parameter to the 'watch()' call + return mockChokidarWatch.mock.calls[0][0]; + }, + + get excludeArgs(): string[] { + expect(mockChokidarWatch.mock.calls.length).toBe(1); + // the ignore args are a property of the second parameter to the 'watch()' call + const chokidarWatchOpts = mockChokidarWatch.mock.calls[0][1]; + return chokidarWatchOpts.ignored; + }, +}; + import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { Bootstrapper } from '../lib/api/bootstrap'; @@ -11,6 +60,12 @@ import { instanceMockFrom, MockCloudExecutable, TestStackArtifact } from './util let cloudExecutable: MockCloudExecutable; let bootstrapper: jest.Mocked; beforeEach(() => { + jest.resetAllMocks(); + + mockChokidarWatch.mockReturnValue(fakeChokidarWatcher); + // on() in chokidar's Watcher returns 'this' + mockChokidarWatcherOn.mockReturnValue(fakeChokidarWatcher); + bootstrapper = instanceMockFrom(Bootstrapper); bootstrapper.bootstrapEnvironment.mockResolvedValue({ noOp: false, outputs: {} } as any); @@ -191,6 +246,141 @@ describe('deploy', () => { }); }); +describe('watch', () => { + test("fails when no 'watch' settings are found", async () => { + const toolkit = defaultToolkitSetup(); + + await expect(() => { + return toolkit.watch({ selector: { patterns: [] } }); + }).rejects.toThrow("Cannot use the 'watch' command without specifying at least one directory to monitor. " + + 'Make sure to add a "watch" key to your cdk.json'); + }); + + test('observes only the root directory by default', async () => { + cloudExecutable.configuration.settings.set(['watch'], {}); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + const includeArgs = fakeChokidarWatch.includeArgs; + expect(includeArgs.length).toBe(1); + }); + + test("allows providing a single string in 'watch.include'", async () => { + cloudExecutable.configuration.settings.set(['watch'], { + include: 'my-dir', + }); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + expect(fakeChokidarWatch.includeArgs).toStrictEqual(['my-dir']); + }); + + test("allows providing an array of strings in 'watch.include'", async () => { + cloudExecutable.configuration.settings.set(['watch'], { + include: ['my-dir1', '**/my-dir2/*'], + }); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + expect(fakeChokidarWatch.includeArgs).toStrictEqual(['my-dir1', '**/my-dir2/*']); + }); + + test('ignores the output dir, dot files, dot directories, and node_modules by default', async () => { + cloudExecutable.configuration.settings.set(['watch'], {}); + cloudExecutable.configuration.settings.set(['output'], 'cdk.out'); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + expect(fakeChokidarWatch.excludeArgs).toStrictEqual([ + 'cdk.out/**', + '**/.*', + '**/.*/**', + '**/node_modules/**', + ]); + }); + + test("allows providing a single string in 'watch.exclude'", async () => { + cloudExecutable.configuration.settings.set(['watch'], { + exclude: 'my-dir', + }); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + const excludeArgs = fakeChokidarWatch.excludeArgs; + expect(excludeArgs.length).toBe(5); + expect(excludeArgs[0]).toBe('my-dir'); + }); + + test("allows providing an array of strings in 'watch.exclude'", async () => { + cloudExecutable.configuration.settings.set(['watch'], { + exclude: ['my-dir1', '**/my-dir2'], + }); + const toolkit = defaultToolkitSetup(); + + await toolkit.watch({ selector: { patterns: [] } }); + + const excludeArgs = fakeChokidarWatch.excludeArgs; + expect(excludeArgs.length).toBe(6); + expect(excludeArgs[0]).toBe('my-dir1'); + expect(excludeArgs[1]).toBe('**/my-dir2'); + }); + + describe('with file change events', () => { + let toolkit: CdkToolkit; + let cdkDeployMock: jest.Mock; + + beforeEach(async () => { + cloudExecutable.configuration.settings.set(['watch'], {}); + toolkit = defaultToolkitSetup(); + cdkDeployMock = jest.fn(); + toolkit.deploy = cdkDeployMock; + await toolkit.watch({ selector: { patterns: [] } }); + }); + + test("does not trigger a 'deploy' before the 'ready' event has fired", async () => { + await fakeChokidarWatcherOn.fileEventCallback('add', 'my-file'); + + expect(cdkDeployMock).not.toHaveBeenCalled(); + }); + + describe("when the 'ready' event has already fired", () => { + beforeEach(() => { + fakeChokidarWatcherOn.readyCallback(); + }); + + test("does trigger a 'deploy' for a file change", async () => { + await fakeChokidarWatcherOn.fileEventCallback('add', 'my-file'); + + expect(cdkDeployMock).toHaveBeenCalled(); + }); + + test("triggers a 'deploy' twice for two file changes", async () => { + await Promise.all([ + fakeChokidarWatcherOn.fileEventCallback('add', 'my-file1'), + fakeChokidarWatcherOn.fileEventCallback('change', 'my-file2'), + ]); + + expect(cdkDeployMock).toHaveBeenCalledTimes(2); + }); + + test("batches file changes that happen during 'deploy'", async () => { + await Promise.all([ + fakeChokidarWatcherOn.fileEventCallback('add', 'my-file1'), + fakeChokidarWatcherOn.fileEventCallback('change', 'my-file2'), + fakeChokidarWatcherOn.fileEventCallback('unlink', 'my-file3'), + ]); + + expect(cdkDeployMock).toHaveBeenCalledTimes(2); + }); + }); + }); +}); + describe('synth', () => { test('with no stdout option', async () => { // GIVE @@ -200,20 +390,6 @@ describe('synth', () => { await expect(toolkit.synth(['Test-Stack-A'], false, true)).resolves.toBeUndefined(); }); - describe('post-synth validation', () => { - beforeEach(() => { - cloudExecutable = new MockCloudExecutable({ - stacks: [ - MockStack.MOCK_STACK_A, - MockStack.MOCK_STACK_B, - ], - nestedAssemblies: [{ - stacks: [MockStack.MOCK_STACK_WITH_ERROR], - }], - }); - }); - }); - afterEach(() => { process.env.STACKS_TO_VALIDATE = undefined; }); diff --git a/packages/aws-cdk/test/context-providers/security-groups.test.ts b/packages/aws-cdk/test/context-providers/security-groups.test.ts index 7f24e684819b6..2d64961af7fe4 100644 --- a/packages/aws-cdk/test/context-providers/security-groups.test.ts +++ b/packages/aws-cdk/test/context-providers/security-groups.test.ts @@ -74,6 +74,157 @@ describe('security group context provider plugin', () => { expect(res.allowAllOutbound).toEqual(true); }); + test('looks up by security group id and vpc id', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + GroupIds: ['sg-1234'], + Filters: [ + { + Name: 'vpc-id', + Values: ['vpc-1234567'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + vpcId: 'vpc-1234567', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + + test('looks up by security group name', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + Filters: [ + { + Name: 'group-name', + Values: ['my-security-group'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupName: 'my-security-group', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + + test('looks up by security group name and vpc id', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + Filters: [ + { + Name: 'vpc-id', + Values: ['vpc-1234567'], + }, + { + Name: 'group-name', + Values: ['my-security-group'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupName: 'my-security-group', + vpcId: 'vpc-1234567', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + test('detects non all-outbound egress', async () => { // GIVEN const provider = new SecurityGroupContextProviderPlugin(mockSDK); @@ -109,6 +260,77 @@ describe('security group context provider plugin', () => { expect(res.allowAllOutbound).toEqual(false); }); + test('errors when more than one security group is found', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ GroupIds: ['sg-1234'] }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '10.0.0.0/16' }, + ], + }, + ], + }, + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '10.0.0.0/16' }, + ], + }, + ], + }, + ], + }); + }); + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + }), + ).rejects.toThrow(/\More than one security groups found matching/i); + }); + + test('errors when securityGroupId and securityGroupName are specified both', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + securityGroupName: 'my-security-group', + }), + ).rejects.toThrow(/\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group/i); + }); + + test('errors when neither securityGroupId nor securityGroupName are specified', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + }), + ).rejects.toThrow(/\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group/i); + }); + test('identifies allTrafficEgress from SecurityGroup permissions', () => { expect( hasAllTrafficEgress({ diff --git a/packages/aws-cdk/test/context.test.ts b/packages/aws-cdk/test/context.test.ts index c364285044f5e..ac84fdb49a309 100644 --- a/packages/aws-cdk/test/context.test.ts +++ b/packages/aws-cdk/test/context.test.ts @@ -8,22 +8,19 @@ const state: { tempDir?: string; } = {}; -beforeAll(async done => { +beforeAll(async () => { state.previousWorkingDir = process.cwd(); state.tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aws-cdk-test')); // eslint-disable-next-line no-console console.log('Temporary working directory:', state.tempDir); process.chdir(state.tempDir); - done(); }); -afterAll(async done => { +afterAll(async () => { // eslint-disable-next-line no-console console.log('Switching back to', state.previousWorkingDir, 'cleaning up', state.tempDir); process.chdir(state.previousWorkingDir!); await fs.remove(state.tempDir!); - - done(); }); test('load context from both files if available', async () => { diff --git a/packages/aws-cdk/test/init.test.ts b/packages/aws-cdk/test/init.test.ts index e659161fbb38a..fe27f114c12c3 100644 --- a/packages/aws-cdk/test/init.test.ts +++ b/packages/aws-cdk/test/init.test.ts @@ -65,8 +65,6 @@ describe.each(['1', '2'])('v%s tests', (majorVersion) => { }); test('verify "future flags" are added to cdk.json', async () => { - // This is a lot to test, and it can be slow-ish, especially when ran with other tests. - jest.setTimeout(30_000); for (const templ of await availableInitTemplates()) { for (const lang of templ.languages) { @@ -95,7 +93,9 @@ describe.each(['1', '2'])('v%s tests', (majorVersion) => { }); } } - }); + }, + // This is a lot to test, and it can be slow-ish, especially when ran with other tests. + 30_000); }); test('when no version number is present (e.g., local development), the v1 templates are chosen by default', async () => { diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/NOTES.md b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/NOTES.md new file mode 100644 index 0000000000000..5e17983dd23b4 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/NOTES.md @@ -0,0 +1,12 @@ +--------------------------------------- + +On november 2nd 2021, lambda started deprecating the nodejs10.x runtime. This meant we can no longer create functions with this runtime. +Our integration tests use this runtime for one of its stacks. + +This patch brings https://github.com/aws/aws-cdk/pull/17282 into the regression suite. + +---------------------------------------- + +Needs to disable an existing test due to change in bootstrap of integration tests. + +This patch brings https://github.com/aws/aws-cdk/pull/17337 into the regression suite. \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/app/app.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/app/app.js new file mode 100644 index 0000000000000..f0e332eb351fa --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/app/app.js @@ -0,0 +1,378 @@ +const path = require('path'); + +var constructs = require('constructs'); +if (process.env.PACKAGE_LAYOUT_VERSION === '1') { + var cdk = require('@aws-cdk/core'); + var ec2 = require('@aws-cdk/aws-ec2'); + var ssm = require('@aws-cdk/aws-ssm'); + var iam = require('@aws-cdk/aws-iam'); + var sns = require('@aws-cdk/aws-sns'); + var lambda = require('@aws-cdk/aws-lambda'); + var docker = require('@aws-cdk/aws-ecr-assets'); +} else { + var cdk = require('aws-cdk-lib'); + var { + aws_ec2: ec2, + aws_ssm: ssm, + aws_iam: iam, + aws_sns: sns, + aws_lambda: lambda, + aws_ecr_assets: docker + } = require('aws-cdk-lib'); +} + +const { Annotations } = cdk; +const { StackWithNestedStack, StackWithNestedStackUsingParameters } = require('./nested-stack'); + +const stackPrefix = process.env.STACK_NAME_PREFIX; +if (!stackPrefix) { + throw new Error(`the STACK_NAME_PREFIX environment variable is required`); +} + +class MyStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + new sns.Topic(this, 'topic'); + + if (cdk.AvailabilityZoneProvider) { // <= 0.34.0 + new cdk.AvailabilityZoneProvider(this).availabilityZones; + } else if (cdk.Context) { // <= 0.35.0 + cdk.Context.getAvailabilityZones(this); + } else { + this.availabilityZones; + } + + const parameterName = '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'; + getSsmParameterValue(this, parameterName); + } +} + +function getSsmParameterValue(scope, parameterName) { + return ssm.StringParameter.valueFromLookup(scope, parameterName); +} + +class YourStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + new sns.Topic(this, 'topic1'); + new sns.Topic(this, 'topic2'); + } +} + +class StackUsingContext extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + new cdk.CfnResource(this, 'Handle', { + type: 'AWS::CloudFormation::WaitConditionHandle' + }); + + new cdk.CfnOutput(this, 'Output', { + value: this.availabilityZones, + }); + } +} + +class ParameterStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new sns.Topic(this, 'TopicParameter', { + topicName: new cdk.CfnParameter(this, 'TopicNameParam').valueAsString + }); + } +} + +class OtherParameterStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new sns.Topic(this, 'TopicParameter', { + topicName: new cdk.CfnParameter(this, 'OtherTopicNameParam').valueAsString + }); + } +} + +class MultiParameterStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new sns.Topic(this, 'TopicParameter', { + displayName: new cdk.CfnParameter(this, 'DisplayNameParam').valueAsString + }); + new sns.Topic(this, 'OtherTopicParameter', { + displayName: new cdk.CfnParameter(this, 'OtherDisplayNameParam').valueAsString + }); + } +} + +class OutputsStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + const topic = new sns.Topic(this, 'MyOutput', { + topicName: `${cdk.Stack.of(this).stackName}MyTopic` + }); + + new cdk.CfnOutput(this, 'TopicName', { + value: topic.topicName + }) + } +} + +class AnotherOutputsStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + const topic = new sns.Topic(this, 'MyOtherOutput', { + topicName: `${cdk.Stack.of(this).stackName}MyOtherTopic` + }); + + new cdk.CfnOutput(this, 'TopicName', { + value: topic.topicName + }); + } +} + +class IamStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new iam.Role(this, 'SomeRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') + }); + } +} + +class ProvidingStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + this.topic = new sns.Topic(this, 'BogusTopic'); // Some filler + } +} + +class StackWithError extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + this.topic = new sns.Topic(this, 'BogusTopic'); // Some filler + Annotations.of(this).addError('This is an error'); + } +} + +class StageWithError extends cdk.Stage { + constructor(parent, id, props) { + super(parent, id, props); + + new StackWithError(this, 'Stack'); + } +} + +class ConsumingStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new sns.Topic(this, 'BogusTopic'); // Some filler + new cdk.CfnOutput(this, 'IConsumedSomething', { value: props.providingStack.topic.topicArn }); + } +} + +class MissingSSMParameterStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + const parameterName = constructs.Node.of(this).tryGetContext('test:ssm-parameter-name'); + if (parameterName) { + const param = getSsmParameterValue(this, parameterName); + new iam.Role(this, 'PhonyRole', { assumedBy: new iam.AccountPrincipal(param) }); + } + } +} + +class LambdaStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + const fn = new lambda.Function(this, 'my-function', { + code: lambda.Code.asset(path.join(__dirname, 'lambda')), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }); + + new cdk.CfnOutput(this, 'FunctionArn', { value: fn.functionArn }); + } +} + +class DockerStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new docker.DockerImageAsset(this, 'image', { + directory: path.join(__dirname, 'docker') + }); + + // Add at least a single resource (WaitConditionHandle), otherwise this stack will never + // be deployed (and its assets never built) + new cdk.CfnResource(this, 'Handle', { + type: 'AWS::CloudFormation::WaitConditionHandle' + }); + } +} + +class DockerStackWithCustomFile extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + new docker.DockerImageAsset(this, 'image', { + directory: path.join(__dirname, 'docker'), + file: 'Dockerfile.Custom' + }); + + // Add at least a single resource (WaitConditionHandle), otherwise this stack will never + // be deployed (and its assets never built) + new cdk.CfnResource(this, 'Handle', { + type: 'AWS::CloudFormation::WaitConditionHandle' + }); + } +} + +class FailedStack extends cdk.Stack { + + constructor(parent, id, props) { + super(parent, id, props); + + // fails on 'Property PolicyDocument cannot be empty'. + new cdk.CfnResource(this, 'EmptyPolicy', { + type: 'AWS::IAM::Policy' + }) + + } + +} + +const VPC_TAG_NAME = 'custom-tag'; +const VPC_TAG_VALUE = `${stackPrefix}-bazinga!`; + +class DefineVpcStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + const vpc = new ec2.Vpc(this, 'VPC', { + maxAzs: 1, + }) + cdk.Aspects.of(vpc).add(new cdk.Tag(VPC_TAG_NAME, VPC_TAG_VALUE)); + } +} + +class ImportVpcStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + ec2.Vpc.fromLookup(this, 'DefaultVPC', { isDefault: true }); + ec2.Vpc.fromLookup(this, 'ByTag', { tags: { [VPC_TAG_NAME]: VPC_TAG_VALUE } }); + } +} + +class ConditionalResourceStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, props); + + if (!process.env.NO_RESOURCE) { + new iam.User(this, 'User'); + } + } +} + +class SomeStage extends cdk.Stage { + constructor(parent, id, props) { + super(parent, id, props); + + new YourStack(this, 'StackInStage'); + } +} + +class StageUsingContext extends cdk.Stage { + constructor(parent, id, props) { + super(parent, id, props); + + new StackUsingContext(this, 'StackInStage'); + } +} + +const app = new cdk.App(); + +const defaultEnv = { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION +}; + +// Sometimes we don't want to synthesize all stacks because it will impact the results +const stackSet = process.env.INTEG_STACK_SET || 'default'; + +switch (stackSet) { + case 'default': + // Deploy all does a wildcard ${stackPrefix}-test-* + new MyStack(app, `${stackPrefix}-test-1`, { env: defaultEnv }); + new YourStack(app, `${stackPrefix}-test-2`); + // Deploy wildcard with parameters does ${stackPrefix}-param-test-* + new ParameterStack(app, `${stackPrefix}-param-test-1`); + new OtherParameterStack(app, `${stackPrefix}-param-test-2`); + // Deploy stack with multiple parameters + new MultiParameterStack(app, `${stackPrefix}-param-test-3`); + // Deploy stack with outputs does ${stackPrefix}-outputs-test-* + new OutputsStack(app, `${stackPrefix}-outputs-test-1`); + new AnotherOutputsStack(app, `${stackPrefix}-outputs-test-2`); + // Not included in wildcard + new IamStack(app, `${stackPrefix}-iam-test`, { env: defaultEnv }); + const providing = new ProvidingStack(app, `${stackPrefix}-order-providing`); + new ConsumingStack(app, `${stackPrefix}-order-consuming`, { providingStack: providing }); + + new MissingSSMParameterStack(app, `${stackPrefix}-missing-ssm-parameter`, { env: defaultEnv }); + + new LambdaStack(app, `${stackPrefix}-lambda`); + new DockerStack(app, `${stackPrefix}-docker`); + new DockerStackWithCustomFile(app, `${stackPrefix}-docker-with-custom-file`); + new FailedStack(app, `${stackPrefix}-failed`) + + if (process.env.ENABLE_VPC_TESTING) { // Gating so we don't do context fetching unless that's what we are here for + const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }; + if (process.env.ENABLE_VPC_TESTING === 'DEFINE') + new DefineVpcStack(app, `${stackPrefix}-define-vpc`, { env }); + if (process.env.ENABLE_VPC_TESTING === 'IMPORT') + new ImportVpcStack(app, `${stackPrefix}-import-vpc`, { env }); + } + + new ConditionalResourceStack(app, `${stackPrefix}-conditional-resource`) + + new StackWithNestedStack(app, `${stackPrefix}-with-nested-stack`); + new StackWithNestedStackUsingParameters(app, `${stackPrefix}-with-nested-stack-using-parameters`); + + new YourStack(app, `${stackPrefix}-termination-protection`, { + terminationProtection: process.env.TERMINATION_PROTECTION !== 'FALSE' ? true : false, + }); + + new SomeStage(app, `${stackPrefix}-stage`); + break; + + case 'stage-using-context': + // Cannot be combined with other test stacks, because we use this to test + // that stage context is propagated up and causes synth to fail when combined + // with '--no-lookups'. + + // Needs a dummy stack at the top level because the CLI will fail otherwise + new YourStack(app, `${stackPrefix}-toplevel`, { env: defaultEnv }); + new StageUsingContext(app, `${stackPrefix}-stage-using-context`, { + env: defaultEnv, + }); + break; + + case 'stage-with-errors': + const stage = new StageWithError(app, `${stackPrefix}-stage-with-errors`); + stage.synth({ validateOnSynthesis: true }); + break; + + default: + throw new Error(`Unrecognized INTEG_STACK_SET: '${stackSet}'`); +} + +app.synth(); diff --git a/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/bootstrapping.integtest.js b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/bootstrapping.integtest.js new file mode 100644 index 0000000000000..5895d49ff1ad9 --- /dev/null +++ b/packages/aws-cdk/test/integ/cli-regression-patches/v1.130.0/bootstrapping.integtest.js @@ -0,0 +1,220 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +const cdk_1 = require("../helpers/cdk"); +const test_helpers_1 = require("../helpers/test-helpers"); +const timeout = process.env.CODEBUILD_BUILD_ID ? // if the process is running in CodeBuild + 3600000 : // 1 hour + 600000; // 10 minutes +jest.setTimeout(timeout); +process.stdout.write(`bootstrapping.integtest.ts: Setting jest time out to ${timeout} ms`); +test_helpers_1.integTest('can bootstrap without execution', cdk_1.withDefaultFixture(async (fixture) => { + var _a; + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + noExecute: true, + }); + const resp = await fixture.aws.cloudFormation('describeStacks', { + StackName: bootstrapStackName, + }); + expect((_a = resp.Stacks) === null || _a === void 0 ? void 0 : _a[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); +})); +test_helpers_1.integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + const legacyBootstrapBucketName = `aws-cdk-bootstrap-integ-test-legacy-bckt-${cdk_1.randomString()}`; + const newBootstrapBucketName = `aws-cdk-bootstrap-integ-test-v2-bckt-${cdk_1.randomString()}`; + fixture.rememberToDeleteBucket(legacyBootstrapBucketName); // This one will leak + fixture.rememberToDeleteBucket(newBootstrapBucketName); // This one shouldn't leak if the test succeeds, but let's be safe in case it doesn't + // Legacy bootstrap + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + bootstrapBucketName: legacyBootstrapBucketName, + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('lambda', { + options: ['--toolkit-stack-name', bootstrapStackName], + }); + // Upgrade bootstrap stack to "new" style + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + bootstrapBucketName: newBootstrapBucketName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + // (Force) deploy stack again + // --force to bypass the check which says that the template hasn't changed. + await fixture.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--force', + ], + }); +})); +test_helpers_1.integTest('can and deploy if omitting execution policies', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +})); +test_helpers_1.integTest('deploy new style synthesis to new style bootstrap', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +})); +test_helpers_1.integTest('deploy new style synthesis to new style bootstrap (with docker image)', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('docker', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +})); +test_helpers_1.integTest('deploy old style synthesis to new style bootstrap', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + ], + }); +})); +test_helpers_1.integTest('can create a legacy bootstrap stack with --public-access-block-configuration=false', cdk_1.withDefaultFixture(async (fixture) => { + var _a; + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName, + publicAccessBlockConfiguration: false, + tags: 'Foo=Bar', + }); + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Tags).toEqual([ + { Key: 'Foo', Value: 'Bar' }, + ]); +})); +test_helpers_1.integTest('can create multiple legacy bootstrap stacks', cdk_1.withDefaultFixture(async (fixture) => { + var _a; + const bootstrapStackName1 = `${fixture.bootstrapStackName}-1`; + const bootstrapStackName2 = `${fixture.bootstrapStackName}-2`; + // deploy two toolkit stacks into the same environment (see #1416) + // one with tags + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName1, + tags: 'Foo=Bar', + }); + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName2, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName1 }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Tags).toEqual([ + { Key: 'Foo', Value: 'Bar' }, + ]); +})); +test_helpers_1.integTest('can dump the template, modify and use it to deploy a custom bootstrap stack', cdk_1.withDefaultFixture(async (fixture) => { + let template = await fixture.cdkBootstrapModern({ + // toolkitStackName doesn't matter for this particular invocation + toolkitStackName: fixture.bootstrapStackName, + showTemplate: true, + cliOptions: { + captureStderr: false, + }, + }); + expect(template).toContain('BootstrapVersion:'); + template += '\n' + [ + ' TwiddleDee:', + ' Value: Template got twiddled', + ].join('\n'); + const filename = path.join(fixture.integTestDir, `${fixture.qualifier}-template.yaml`); + fs.writeFileSync(filename, template, { encoding: 'utf-8' }); + await fixture.cdkBootstrapModern({ + toolkitStackName: fixture.bootstrapStackName, + template: filename, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); +})); +test_helpers_1.integTest('switch on termination protection, switch is left alone on re-bootstrap', cdk_1.withDefaultFixture(async (fixture) => { + var _a; + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + terminationProtection: true, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + force: true, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].EnableTerminationProtection).toEqual(true); +})); +test_helpers_1.integTest('add tags, left alone on re-bootstrap', cdk_1.withDefaultFixture(async (fixture) => { + var _a; + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + tags: 'Foo=Bar', + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + force: true, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); + expect((_a = response.Stacks) === null || _a === void 0 ? void 0 : _a[0].Tags).toEqual([ + { Key: 'Foo', Value: 'Bar' }, + ]); +})); +test_helpers_1.integTest('can deploy modern-synthesized stack even if bootstrap stack name is unknown', cdk_1.withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + // Deploy stack that uses file assets + await fixture.cdkDeploy('lambda', { + options: [ + // Explicity pass a name that's sure to not exist, otherwise the CLI might accidentally find a + // default bootstracp stack if that happens to be in the account already. + '--toolkit-stack-name', 'DefinitelyDoesNotExist', + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +})); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9vdHN0cmFwcGluZy5pbnRlZ3Rlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJib290c3RyYXBwaW5nLmludGVndGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0Isd0NBQWtFO0FBQ2xFLDBEQUFvRDtBQUVwRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyx5Q0FBeUM7SUFDeEYsT0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTO0lBQ3JCLE1BQU8sQ0FBQyxDQUFDLGFBQWE7QUFDeEIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUN6QixPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyx3REFBd0QsT0FBTyxLQUFLLENBQUMsQ0FBQztBQUUzRix3QkFBUyxDQUFDLGlDQUFpQyxFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTs7SUFDaEYsTUFBTSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFFdEQsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDL0IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLFNBQVMsRUFBRSxJQUFJO0tBQ2hCLENBQUMsQ0FBQztJQUVILE1BQU0sSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsZ0JBQWdCLEVBQUU7UUFDOUQsU0FBUyxFQUFFLGtCQUFrQjtLQUM5QixDQUFDLENBQUM7SUFFSCxNQUFNLE9BQUMsSUFBSSxDQUFDLE1BQU0sMENBQUcsQ0FBQyxFQUFFLFdBQVcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0FBQ3JFLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLG9FQUFvRSxFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUNuSCxNQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztJQUV0RCxNQUFNLHlCQUF5QixHQUFHLDRDQUE0QyxrQkFBWSxFQUFFLEVBQUUsQ0FBQztJQUMvRixNQUFNLHNCQUFzQixHQUFHLHdDQUF3QyxrQkFBWSxFQUFFLEVBQUUsQ0FBQztJQUN4RixPQUFPLENBQUMsc0JBQXNCLENBQUMseUJBQXlCLENBQUMsQ0FBQyxDQUFDLHFCQUFxQjtJQUNoRixPQUFPLENBQUMsc0JBQXNCLENBQUMsc0JBQXNCLENBQUMsQ0FBQyxDQUFDLHFGQUFxRjtJQUU3SSxtQkFBbUI7SUFDbkIsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDL0IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLG1CQUFtQixFQUFFLHlCQUF5QjtLQUMvQyxDQUFDLENBQUM7SUFFSCxxQ0FBcUM7SUFDckMsTUFBTSxPQUFPLENBQUMsU0FBUyxDQUFDLFFBQVEsRUFBRTtRQUNoQyxPQUFPLEVBQUUsQ0FBQyxzQkFBc0IsRUFBRSxrQkFBa0IsQ0FBQztLQUN0RCxDQUFDLENBQUM7SUFFSCx5Q0FBeUM7SUFDekMsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDL0IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLG1CQUFtQixFQUFFLHNCQUFzQjtRQUMzQyxrQkFBa0IsRUFBRSw2Q0FBNkM7S0FDbEUsQ0FBQyxDQUFDO0lBRUgsNkJBQTZCO0lBQzdCLDJFQUEyRTtJQUMzRSxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE9BQU8sRUFBRTtZQUNQLHNCQUFzQixFQUFFLGtCQUFrQjtZQUMxQyxTQUFTO1NBQ1Y7S0FDRixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRUosd0JBQVMsQ0FBQywrQ0FBK0MsRUFBRSx3QkFBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7SUFDOUYsTUFBTSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFFdEQsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDL0IsZ0JBQWdCLEVBQUUsa0JBQWtCO0tBQ3JDLENBQUMsQ0FBQztJQUVILHFDQUFxQztJQUNyQyxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE9BQU8sRUFBRTtZQUNQLHNCQUFzQixFQUFFLGtCQUFrQjtZQUMxQyxXQUFXLEVBQUUsb0NBQW9DLE9BQU8sQ0FBQyxTQUFTLEVBQUU7WUFDcEUsV0FBVyxFQUFFLHdDQUF3QztTQUN0RDtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLG1EQUFtRCxFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUNsRyxNQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztJQUV0RCxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixnQkFBZ0IsRUFBRSxrQkFBa0I7UUFDcEMsa0JBQWtCLEVBQUUsNkNBQTZDO0tBQ2xFLENBQUMsQ0FBQztJQUVILHFDQUFxQztJQUNyQyxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE9BQU8sRUFBRTtZQUNQLHNCQUFzQixFQUFFLGtCQUFrQjtZQUMxQyxXQUFXLEVBQUUsb0NBQW9DLE9BQU8sQ0FBQyxTQUFTLEVBQUU7WUFDcEUsV0FBVyxFQUFFLHdDQUF3QztTQUN0RDtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLHVFQUF1RSxFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUN0SCxNQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztJQUV0RCxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixnQkFBZ0IsRUFBRSxrQkFBa0I7UUFDcEMsa0JBQWtCLEVBQUUsNkNBQTZDO0tBQ2xFLENBQUMsQ0FBQztJQUVILHFDQUFxQztJQUNyQyxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE9BQU8sRUFBRTtZQUNQLHNCQUFzQixFQUFFLGtCQUFrQjtZQUMxQyxXQUFXLEVBQUUsb0NBQW9DLE9BQU8sQ0FBQyxTQUFTLEVBQUU7WUFDcEUsV0FBVyxFQUFFLHdDQUF3QztTQUN0RDtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLG1EQUFtRCxFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUNsRyxNQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztJQUV0RCxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixnQkFBZ0IsRUFBRSxrQkFBa0I7UUFDcEMsa0JBQWtCLEVBQUUsNkNBQTZDO0tBQ2xFLENBQUMsQ0FBQztJQUVILHFDQUFxQztJQUNyQyxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFO1FBQ2hDLE9BQU8sRUFBRTtZQUNQLHNCQUFzQixFQUFFLGtCQUFrQjtTQUMzQztLQUNGLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFSix3QkFBUyxDQUFDLG9GQUFvRixFQUFFLHdCQUFrQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTs7SUFDbkksTUFBTSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFFdEQsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDL0IsT0FBTyxFQUFFLElBQUk7UUFDYixnQkFBZ0IsRUFBRSxrQkFBa0I7UUFDcEMsOEJBQThCLEVBQUUsS0FBSztRQUNyQyxJQUFJLEVBQUUsU0FBUztLQUNoQixDQUFDLENBQUM7SUFFSCxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLGdCQUFnQixFQUFFLEVBQUUsU0FBUyxFQUFFLGtCQUFrQixFQUFFLENBQUMsQ0FBQztJQUN2RyxNQUFNLE9BQUMsUUFBUSxDQUFDLE1BQU0sMENBQUcsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4QyxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRTtLQUM3QixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRUosd0JBQVMsQ0FBQyw2Q0FBNkMsRUFBRSx3QkFBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7O0lBQzVGLE1BQU0sbUJBQW1CLEdBQUcsR0FBRyxPQUFPLENBQUMsa0JBQWtCLElBQUksQ0FBQztJQUM5RCxNQUFNLG1CQUFtQixHQUFHLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixJQUFJLENBQUM7SUFFOUQsa0VBQWtFO0lBQ2xFLGdCQUFnQjtJQUNoQixNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixPQUFPLEVBQUUsSUFBSTtRQUNiLGdCQUFnQixFQUFFLG1CQUFtQjtRQUNyQyxJQUFJLEVBQUUsU0FBUztLQUNoQixDQUFDLENBQUM7SUFDSCxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixPQUFPLEVBQUUsSUFBSTtRQUNiLGdCQUFnQixFQUFFLG1CQUFtQjtLQUN0QyxDQUFDLENBQUM7SUFFSCxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLGdCQUFnQixFQUFFLEVBQUUsU0FBUyxFQUFFLG1CQUFtQixFQUFFLENBQUMsQ0FBQztJQUN4RyxNQUFNLE9BQUMsUUFBUSxDQUFDLE1BQU0sMENBQUcsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4QyxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRTtLQUM3QixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRUosd0JBQVMsQ0FBQyw2RUFBNkUsRUFBRSx3QkFBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7SUFDNUgsSUFBSSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDOUMsaUVBQWlFO1FBQ2pFLGdCQUFnQixFQUFFLE9BQU8sQ0FBQyxrQkFBa0I7UUFDNUMsWUFBWSxFQUFFLElBQUk7UUFDbEIsVUFBVSxFQUFFO1lBQ1YsYUFBYSxFQUFFLEtBQUs7U0FDckI7S0FDRixDQUFDLENBQUM7SUFFSCxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsU0FBUyxDQUFDLG1CQUFtQixDQUFDLENBQUM7SUFFaEQsUUFBUSxJQUFJLElBQUksR0FBRztRQUNqQixlQUFlO1FBQ2Ysa0NBQWtDO0tBQ25DLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRWIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLEdBQUcsT0FBTyxDQUFDLFNBQVMsZ0JBQWdCLENBQUMsQ0FBQztJQUN2RixFQUFFLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUM1RCxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQztRQUMvQixnQkFBZ0IsRUFBRSxPQUFPLENBQUMsa0JBQWtCO1FBQzVDLFFBQVEsRUFBRSxRQUFRO1FBQ2xCLGtCQUFrQixFQUFFLDZDQUE2QztLQUNsRSxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRUosd0JBQVMsQ0FBQyx3RUFBd0UsRUFBRSx3QkFBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7O0lBQ3ZILE1BQU0sa0JBQWtCLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixDQUFDO0lBRXRELE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQy9CLE9BQU8sRUFBRSxJQUFJO1FBQ2IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLHFCQUFxQixFQUFFLElBQUk7UUFDM0Isa0JBQWtCLEVBQUUsNkNBQTZDO0tBQ2xFLENBQUMsQ0FBQztJQUNILE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQy9CLE9BQU8sRUFBRSxJQUFJO1FBQ2IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLEtBQUssRUFBRSxJQUFJO0tBQ1osQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxnQkFBZ0IsRUFBRSxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxDQUFDLENBQUM7SUFDdkcsTUFBTSxPQUFDLFFBQVEsQ0FBQyxNQUFNLDBDQUFHLENBQUMsRUFBRSwyQkFBMkIsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUN6RSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRUosd0JBQVMsQ0FBQyxzQ0FBc0MsRUFBRSx3QkFBa0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7O0lBQ3JGLE1BQU0sa0JBQWtCLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixDQUFDO0lBRXRELE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQy9CLE9BQU8sRUFBRSxJQUFJO1FBQ2IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLElBQUksRUFBRSxTQUFTO1FBQ2Ysa0JBQWtCLEVBQUUsNkNBQTZDO0tBQ2xFLENBQUMsQ0FBQztJQUNILE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQy9CLE9BQU8sRUFBRSxJQUFJO1FBQ2IsZ0JBQWdCLEVBQUUsa0JBQWtCO1FBQ3BDLEtBQUssRUFBRSxJQUFJO0tBQ1osQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxnQkFBZ0IsRUFBRSxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxDQUFDLENBQUM7SUFDdkcsTUFBTSxPQUFDLFFBQVEsQ0FBQyxNQUFNLDBDQUFHLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUM7UUFDeEMsRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUU7S0FDN0IsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUVKLHdCQUFTLENBQUMsNkVBQTZFLEVBQUUsd0JBQWtCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxFQUFFO0lBQzVILE1BQU0sa0JBQWtCLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixDQUFDO0lBRXRELE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQy9CLGdCQUFnQixFQUFFLGtCQUFrQjtRQUNwQyxrQkFBa0IsRUFBRSw2Q0FBNkM7S0FDbEUsQ0FBQyxDQUFDO0lBRUgscUNBQXFDO0lBQ3JDLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxRQUFRLEVBQUU7UUFDaEMsT0FBTyxFQUFFO1lBQ1AsOEZBQThGO1lBQzlGLHlFQUF5RTtZQUN6RSxzQkFBc0IsRUFBRSx3QkFBd0I7WUFDaEQsV0FBVyxFQUFFLG9DQUFvQyxPQUFPLENBQUMsU0FBUyxFQUFFO1lBQ3BFLFdBQVcsRUFBRSx3Q0FBd0M7U0FDdEQ7S0FDRixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IHJhbmRvbVN0cmluZywgd2l0aERlZmF1bHRGaXh0dXJlIH0gZnJvbSAnLi4vaGVscGVycy9jZGsnO1xuaW1wb3J0IHsgaW50ZWdUZXN0IH0gZnJvbSAnLi4vaGVscGVycy90ZXN0LWhlbHBlcnMnO1xuXG5jb25zdCB0aW1lb3V0ID0gcHJvY2Vzcy5lbnYuQ09ERUJVSUxEX0JVSUxEX0lEID8gLy8gaWYgdGhlIHByb2Nlc3MgaXMgcnVubmluZyBpbiBDb2RlQnVpbGRcbiAgM182MDBfMDAwIDogLy8gMSBob3VyXG4gIDYwMF8wMDA7IC8vIDEwIG1pbnV0ZXNcbmplc3Quc2V0VGltZW91dCh0aW1lb3V0KTtcbnByb2Nlc3Muc3Rkb3V0LndyaXRlKGBib290c3RyYXBwaW5nLmludGVndGVzdC50czogU2V0dGluZyBqZXN0IHRpbWUgb3V0IHRvICR7dGltZW91dH0gbXNgKTtcblxuaW50ZWdUZXN0KCdjYW4gYm9vdHN0cmFwIHdpdGhvdXQgZXhlY3V0aW9uJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZSA9IGZpeHR1cmUuYm9vdHN0cmFwU3RhY2tOYW1lO1xuXG4gIGF3YWl0IGZpeHR1cmUuY2RrQm9vdHN0cmFwTGVnYWN5KHtcbiAgICB0b29sa2l0U3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUsXG4gICAgbm9FeGVjdXRlOiB0cnVlLFxuICB9KTtcblxuICBjb25zdCByZXNwID0gYXdhaXQgZml4dHVyZS5hd3MuY2xvdWRGb3JtYXRpb24oJ2Rlc2NyaWJlU3RhY2tzJywge1xuICAgIFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICB9KTtcblxuICBleHBlY3QocmVzcC5TdGFja3M/LlswXS5TdGFja1N0YXR1cykudG9FcXVhbCgnUkVWSUVXX0lOX1BST0dSRVNTJyk7XG59KSk7XG5cbmludGVnVGVzdCgndXBncmFkZSBsZWdhY3kgYm9vdHN0cmFwIHN0YWNrIHRvIG5ldyBib290c3RyYXAgc3RhY2sgd2hpbGUgaW4gdXNlJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZSA9IGZpeHR1cmUuYm9vdHN0cmFwU3RhY2tOYW1lO1xuXG4gIGNvbnN0IGxlZ2FjeUJvb3RzdHJhcEJ1Y2tldE5hbWUgPSBgYXdzLWNkay1ib290c3RyYXAtaW50ZWctdGVzdC1sZWdhY3ktYmNrdC0ke3JhbmRvbVN0cmluZygpfWA7XG4gIGNvbnN0IG5ld0Jvb3RzdHJhcEJ1Y2tldE5hbWUgPSBgYXdzLWNkay1ib290c3RyYXAtaW50ZWctdGVzdC12Mi1iY2t0LSR7cmFuZG9tU3RyaW5nKCl9YDtcbiAgZml4dHVyZS5yZW1lbWJlclRvRGVsZXRlQnVja2V0KGxlZ2FjeUJvb3RzdHJhcEJ1Y2tldE5hbWUpOyAvLyBUaGlzIG9uZSB3aWxsIGxlYWtcbiAgZml4dHVyZS5yZW1lbWJlclRvRGVsZXRlQnVja2V0KG5ld0Jvb3RzdHJhcEJ1Y2tldE5hbWUpOyAvLyBUaGlzIG9uZSBzaG91bGRuJ3QgbGVhayBpZiB0aGUgdGVzdCBzdWNjZWVkcywgYnV0IGxldCdzIGJlIHNhZmUgaW4gY2FzZSBpdCBkb2Vzbid0XG5cbiAgLy8gTGVnYWN5IGJvb3RzdHJhcFxuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcExlZ2FjeSh7XG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIGJvb3RzdHJhcEJ1Y2tldE5hbWU6IGxlZ2FjeUJvb3RzdHJhcEJ1Y2tldE5hbWUsXG4gIH0pO1xuXG4gIC8vIERlcGxveSBzdGFjayB0aGF0IHVzZXMgZmlsZSBhc3NldHNcbiAgYXdhaXQgZml4dHVyZS5jZGtEZXBsb3koJ2xhbWJkYScsIHtcbiAgICBvcHRpb25zOiBbJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lXSxcbiAgfSk7XG5cbiAgLy8gVXBncmFkZSBib290c3RyYXAgc3RhY2sgdG8gXCJuZXdcIiBzdHlsZVxuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcE1vZGVybih7XG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIGJvb3RzdHJhcEJ1Y2tldE5hbWU6IG5ld0Jvb3RzdHJhcEJ1Y2tldE5hbWUsXG4gICAgY2ZuRXhlY3V0aW9uUG9saWN5OiAnYXJuOmF3czppYW06OmF3czpwb2xpY3kvQWRtaW5pc3RyYXRvckFjY2VzcycsXG4gIH0pO1xuXG4gIC8vIChGb3JjZSkgZGVwbG95IHN0YWNrIGFnYWluXG4gIC8vIC0tZm9yY2UgdG8gYnlwYXNzIHRoZSBjaGVjayB3aGljaCBzYXlzIHRoYXQgdGhlIHRlbXBsYXRlIGhhc24ndCBjaGFuZ2VkLlxuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICAgICctLWZvcmNlJyxcbiAgICBdLFxuICB9KTtcbn0pKTtcblxuaW50ZWdUZXN0KCdjYW4gYW5kIGRlcGxveSBpZiBvbWl0dGluZyBleGVjdXRpb24gcG9saWNpZXMnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZml4dHVyZS5ib290c3RyYXBTdGFja05hbWU7XG5cbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBNb2Rlcm4oe1xuICAgIHRvb2xraXRTdGFja05hbWU6IGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgfSk7XG5cbiAgLy8gRGVwbG95IHN0YWNrIHRoYXQgdXNlcyBmaWxlIGFzc2V0c1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICAgICctLWNvbnRleHQnLCBgQGF3cy1jZGsvY29yZTpib290c3RyYXBRdWFsaWZpZXI9JHtmaXh0dXJlLnF1YWxpZmllcn1gLFxuICAgICAgJy0tY29udGV4dCcsICdAYXdzLWNkay9jb3JlOm5ld1N0eWxlU3RhY2tTeW50aGVzaXM9MScsXG4gICAgXSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IG5ldyBzdHlsZSBzeW50aGVzaXMgdG8gbmV3IHN0eWxlIGJvb3RzdHJhcCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBib290c3RyYXBTdGFja05hbWUgPSBmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcE1vZGVybih7XG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIGNmbkV4ZWN1dGlvblBvbGljeTogJ2Fybjphd3M6aWFtOjphd3M6cG9saWN5L0FkbWluaXN0cmF0b3JBY2Nlc3MnLFxuICB9KTtcblxuICAvLyBEZXBsb3kgc3RhY2sgdGhhdCB1c2VzIGZpbGUgYXNzZXRzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdsYW1iZGEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgICAgJy0tY29udGV4dCcsIGBAYXdzLWNkay9jb3JlOmJvb3RzdHJhcFF1YWxpZmllcj0ke2ZpeHR1cmUucXVhbGlmaWVyfWAsXG4gICAgICAnLS1jb250ZXh0JywgJ0Bhd3MtY2RrL2NvcmU6bmV3U3R5bGVTdGFja1N5bnRoZXNpcz0xJyxcbiAgICBdLFxuICB9KTtcbn0pKTtcblxuaW50ZWdUZXN0KCdkZXBsb3kgbmV3IHN0eWxlIHN5bnRoZXNpcyB0byBuZXcgc3R5bGUgYm9vdHN0cmFwICh3aXRoIGRvY2tlciBpbWFnZSknLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZml4dHVyZS5ib290c3RyYXBTdGFja05hbWU7XG5cbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBNb2Rlcm4oe1xuICAgIHRvb2xraXRTdGFja05hbWU6IGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICBjZm5FeGVjdXRpb25Qb2xpY3k6ICdhcm46YXdzOmlhbTo6YXdzOnBvbGljeS9BZG1pbmlzdHJhdG9yQWNjZXNzJyxcbiAgfSk7XG5cbiAgLy8gRGVwbG95IHN0YWNrIHRoYXQgdXNlcyBmaWxlIGFzc2V0c1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnZG9ja2VyJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsIGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICAgICctLWNvbnRleHQnLCBgQGF3cy1jZGsvY29yZTpib290c3RyYXBRdWFsaWZpZXI9JHtmaXh0dXJlLnF1YWxpZmllcn1gLFxuICAgICAgJy0tY29udGV4dCcsICdAYXdzLWNkay9jb3JlOm5ld1N0eWxlU3RhY2tTeW50aGVzaXM9MScsXG4gICAgXSxcbiAgfSk7XG59KSk7XG5cbmludGVnVGVzdCgnZGVwbG95IG9sZCBzdHlsZSBzeW50aGVzaXMgdG8gbmV3IHN0eWxlIGJvb3RzdHJhcCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBib290c3RyYXBTdGFja05hbWUgPSBmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcE1vZGVybih7XG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIGNmbkV4ZWN1dGlvblBvbGljeTogJ2Fybjphd3M6aWFtOjphd3M6cG9saWN5L0FkbWluaXN0cmF0b3JBY2Nlc3MnLFxuICB9KTtcblxuICAvLyBEZXBsb3kgc3RhY2sgdGhhdCB1c2VzIGZpbGUgYXNzZXRzXG4gIGF3YWl0IGZpeHR1cmUuY2RrRGVwbG95KCdsYW1iZGEnLCB7XG4gICAgb3B0aW9uczogW1xuICAgICAgJy0tdG9vbGtpdC1zdGFjay1uYW1lJywgYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIF0sXG4gIH0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2NhbiBjcmVhdGUgYSBsZWdhY3kgYm9vdHN0cmFwIHN0YWNrIHdpdGggLS1wdWJsaWMtYWNjZXNzLWJsb2NrLWNvbmZpZ3VyYXRpb249ZmFsc2UnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZml4dHVyZS5ib290c3RyYXBTdGFja05hbWU7XG5cbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBMZWdhY3koe1xuICAgIHZlcmJvc2U6IHRydWUsXG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIHB1YmxpY0FjY2Vzc0Jsb2NrQ29uZmlndXJhdGlvbjogZmFsc2UsXG4gICAgdGFnczogJ0Zvbz1CYXInLFxuICB9KTtcblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUgfSk7XG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5UYWdzKS50b0VxdWFsKFtcbiAgICB7IEtleTogJ0ZvbycsIFZhbHVlOiAnQmFyJyB9LFxuICBdKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdjYW4gY3JlYXRlIG11bHRpcGxlIGxlZ2FjeSBib290c3RyYXAgc3RhY2tzJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZTEgPSBgJHtmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZX0tMWA7XG4gIGNvbnN0IGJvb3RzdHJhcFN0YWNrTmFtZTIgPSBgJHtmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZX0tMmA7XG5cbiAgLy8gZGVwbG95IHR3byB0b29sa2l0IHN0YWNrcyBpbnRvIHRoZSBzYW1lIGVudmlyb25tZW50IChzZWUgIzE0MTYpXG4gIC8vIG9uZSB3aXRoIHRhZ3NcbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBMZWdhY3koe1xuICAgIHZlcmJvc2U6IHRydWUsXG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lMSxcbiAgICB0YWdzOiAnRm9vPUJhcicsXG4gIH0pO1xuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcExlZ2FjeSh7XG4gICAgdmVyYm9zZTogdHJ1ZSxcbiAgICB0b29sa2l0U3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUyLFxuICB9KTtcblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUxIH0pO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uVGFncykudG9FcXVhbChbXG4gICAgeyBLZXk6ICdGb28nLCBWYWx1ZTogJ0JhcicgfSxcbiAgXSk7XG59KSk7XG5cbmludGVnVGVzdCgnY2FuIGR1bXAgdGhlIHRlbXBsYXRlLCBtb2RpZnkgYW5kIHVzZSBpdCB0byBkZXBsb3kgYSBjdXN0b20gYm9vdHN0cmFwIHN0YWNrJywgd2l0aERlZmF1bHRGaXh0dXJlKGFzeW5jIChmaXh0dXJlKSA9PiB7XG4gIGxldCB0ZW1wbGF0ZSA9IGF3YWl0IGZpeHR1cmUuY2RrQm9vdHN0cmFwTW9kZXJuKHtcbiAgICAvLyB0b29sa2l0U3RhY2tOYW1lIGRvZXNuJ3QgbWF0dGVyIGZvciB0aGlzIHBhcnRpY3VsYXIgaW52b2NhdGlvblxuICAgIHRvb2xraXRTdGFja05hbWU6IGZpeHR1cmUuYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIHNob3dUZW1wbGF0ZTogdHJ1ZSxcbiAgICBjbGlPcHRpb25zOiB7XG4gICAgICBjYXB0dXJlU3RkZXJyOiBmYWxzZSxcbiAgICB9LFxuICB9KTtcblxuICBleHBlY3QodGVtcGxhdGUpLnRvQ29udGFpbignQm9vdHN0cmFwVmVyc2lvbjonKTtcblxuICB0ZW1wbGF0ZSArPSAnXFxuJyArIFtcbiAgICAnICBUd2lkZGxlRGVlOicsXG4gICAgJyAgICBWYWx1ZTogVGVtcGxhdGUgZ290IHR3aWRkbGVkJyxcbiAgXS5qb2luKCdcXG4nKTtcblxuICBjb25zdCBmaWxlbmFtZSA9IHBhdGguam9pbihmaXh0dXJlLmludGVnVGVzdERpciwgYCR7Zml4dHVyZS5xdWFsaWZpZXJ9LXRlbXBsYXRlLnlhbWxgKTtcbiAgZnMud3JpdGVGaWxlU3luYyhmaWxlbmFtZSwgdGVtcGxhdGUsIHsgZW5jb2Rpbmc6ICd1dGYtOCcgfSk7XG4gIGF3YWl0IGZpeHR1cmUuY2RrQm9vdHN0cmFwTW9kZXJuKHtcbiAgICB0b29sa2l0U3RhY2tOYW1lOiBmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICB0ZW1wbGF0ZTogZmlsZW5hbWUsXG4gICAgY2ZuRXhlY3V0aW9uUG9saWN5OiAnYXJuOmF3czppYW06OmF3czpwb2xpY3kvQWRtaW5pc3RyYXRvckFjY2VzcycsXG4gIH0pO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ3N3aXRjaCBvbiB0ZXJtaW5hdGlvbiBwcm90ZWN0aW9uLCBzd2l0Y2ggaXMgbGVmdCBhbG9uZSBvbiByZS1ib290c3RyYXAnLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZml4dHVyZS5ib290c3RyYXBTdGFja05hbWU7XG5cbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBNb2Rlcm4oe1xuICAgIHZlcmJvc2U6IHRydWUsXG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIHRlcm1pbmF0aW9uUHJvdGVjdGlvbjogdHJ1ZSxcbiAgICBjZm5FeGVjdXRpb25Qb2xpY3k6ICdhcm46YXdzOmlhbTo6YXdzOnBvbGljeS9BZG1pbmlzdHJhdG9yQWNjZXNzJyxcbiAgfSk7XG4gIGF3YWl0IGZpeHR1cmUuY2RrQm9vdHN0cmFwTW9kZXJuKHtcbiAgICB2ZXJib3NlOiB0cnVlLFxuICAgIHRvb2xraXRTdGFja05hbWU6IGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICBmb3JjZTogdHJ1ZSxcbiAgfSk7XG5cbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmaXh0dXJlLmF3cy5jbG91ZEZvcm1hdGlvbignZGVzY3JpYmVTdGFja3MnLCB7IFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lIH0pO1xuICBleHBlY3QocmVzcG9uc2UuU3RhY2tzPy5bMF0uRW5hYmxlVGVybWluYXRpb25Qcm90ZWN0aW9uKS50b0VxdWFsKHRydWUpO1xufSkpO1xuXG5pbnRlZ1Rlc3QoJ2FkZCB0YWdzLCBsZWZ0IGFsb25lIG9uIHJlLWJvb3RzdHJhcCcsIHdpdGhEZWZhdWx0Rml4dHVyZShhc3luYyAoZml4dHVyZSkgPT4ge1xuICBjb25zdCBib290c3RyYXBTdGFja05hbWUgPSBmaXh0dXJlLmJvb3RzdHJhcFN0YWNrTmFtZTtcblxuICBhd2FpdCBmaXh0dXJlLmNka0Jvb3RzdHJhcE1vZGVybih7XG4gICAgdmVyYm9zZTogdHJ1ZSxcbiAgICB0b29sa2l0U3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUsXG4gICAgdGFnczogJ0Zvbz1CYXInLFxuICAgIGNmbkV4ZWN1dGlvblBvbGljeTogJ2Fybjphd3M6aWFtOjphd3M6cG9saWN5L0FkbWluaXN0cmF0b3JBY2Nlc3MnLFxuICB9KTtcbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBNb2Rlcm4oe1xuICAgIHZlcmJvc2U6IHRydWUsXG4gICAgdG9vbGtpdFN0YWNrTmFtZTogYm9vdHN0cmFwU3RhY2tOYW1lLFxuICAgIGZvcmNlOiB0cnVlLFxuICB9KTtcblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZpeHR1cmUuYXdzLmNsb3VkRm9ybWF0aW9uKCdkZXNjcmliZVN0YWNrcycsIHsgU3RhY2tOYW1lOiBib290c3RyYXBTdGFja05hbWUgfSk7XG4gIGV4cGVjdChyZXNwb25zZS5TdGFja3M/LlswXS5UYWdzKS50b0VxdWFsKFtcbiAgICB7IEtleTogJ0ZvbycsIFZhbHVlOiAnQmFyJyB9LFxuICBdKTtcbn0pKTtcblxuaW50ZWdUZXN0KCdjYW4gZGVwbG95IG1vZGVybi1zeW50aGVzaXplZCBzdGFjayBldmVuIGlmIGJvb3RzdHJhcCBzdGFjayBuYW1lIGlzIHVua25vd24nLCB3aXRoRGVmYXVsdEZpeHR1cmUoYXN5bmMgKGZpeHR1cmUpID0+IHtcbiAgY29uc3QgYm9vdHN0cmFwU3RhY2tOYW1lID0gZml4dHVyZS5ib290c3RyYXBTdGFja05hbWU7XG5cbiAgYXdhaXQgZml4dHVyZS5jZGtCb290c3RyYXBNb2Rlcm4oe1xuICAgIHRvb2xraXRTdGFja05hbWU6IGJvb3RzdHJhcFN0YWNrTmFtZSxcbiAgICBjZm5FeGVjdXRpb25Qb2xpY3k6ICdhcm46YXdzOmlhbTo6YXdzOnBvbGljeS9BZG1pbmlzdHJhdG9yQWNjZXNzJyxcbiAgfSk7XG5cbiAgLy8gRGVwbG95IHN0YWNrIHRoYXQgdXNlcyBmaWxlIGFzc2V0c1xuICBhd2FpdCBmaXh0dXJlLmNka0RlcGxveSgnbGFtYmRhJywge1xuICAgIG9wdGlvbnM6IFtcbiAgICAgIC8vIEV4cGxpY2l0eSBwYXNzIGEgbmFtZSB0aGF0J3Mgc3VyZSB0byBub3QgZXhpc3QsIG90aGVyd2lzZSB0aGUgQ0xJIG1pZ2h0IGFjY2lkZW50YWxseSBmaW5kIGFcbiAgICAgIC8vIGRlZmF1bHQgYm9vdHN0cmFjcCBzdGFjayBpZiB0aGF0IGhhcHBlbnMgdG8gYmUgaW4gdGhlIGFjY291bnQgYWxyZWFkeS5cbiAgICAgICctLXRvb2xraXQtc3RhY2stbmFtZScsICdEZWZpbml0ZWx5RG9lc05vdEV4aXN0JyxcbiAgICAgICctLWNvbnRleHQnLCBgQGF3cy1jZGsvY29yZTpib290c3RyYXBRdWFsaWZpZXI9JHtmaXh0dXJlLnF1YWxpZmllcn1gLFxuICAgICAgJy0tY29udGV4dCcsICdAYXdzLWNkay9jb3JlOm5ld1N0eWxlU3RhY2tTeW50aGVzaXM9MScsXG4gICAgXSxcbiAgfSk7XG59KSk7XG4iXX0= \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index 205d7014503dc..f0e332eb351fa 100644 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -1,14 +1,28 @@ const path = require('path'); -const cdk = require('@aws-cdk/core'); -const ec2 = require('@aws-cdk/aws-ec2'); -const ssm = require('@aws-cdk/aws-ssm'); -const iam = require('@aws-cdk/aws-iam'); -const sns = require('@aws-cdk/aws-sns'); -const lambda = require('@aws-cdk/aws-lambda'); -const docker = require('@aws-cdk/aws-ecr-assets'); -const core = require('@aws-cdk/core') + +var constructs = require('constructs'); +if (process.env.PACKAGE_LAYOUT_VERSION === '1') { + var cdk = require('@aws-cdk/core'); + var ec2 = require('@aws-cdk/aws-ec2'); + var ssm = require('@aws-cdk/aws-ssm'); + var iam = require('@aws-cdk/aws-iam'); + var sns = require('@aws-cdk/aws-sns'); + var lambda = require('@aws-cdk/aws-lambda'); + var docker = require('@aws-cdk/aws-ecr-assets'); +} else { + var cdk = require('aws-cdk-lib'); + var { + aws_ec2: ec2, + aws_ssm: ssm, + aws_iam: iam, + aws_sns: sns, + aws_lambda: lambda, + aws_ecr_assets: docker + } = require('aws-cdk-lib'); +} + +const { Annotations } = cdk; const { StackWithNestedStack, StackWithNestedStackUsingParameters } = require('./nested-stack'); -const { Annotations } = require('@aws-cdk/core'); const stackPrefix = process.env.STACK_NAME_PREFIX; if (!stackPrefix) { @@ -48,11 +62,11 @@ class YourStack extends cdk.Stack { class StackUsingContext extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); - new core.CfnOutput(this, 'Output', { + new cdk.CfnOutput(this, 'Output', { value: this.availabilityZones, }); } @@ -167,7 +181,7 @@ class MissingSSMParameterStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - const parameterName = this.node.tryGetContext('test:ssm-parameter-name'); + const parameterName = constructs.Node.of(this).tryGetContext('test:ssm-parameter-name'); if (parameterName) { const param = getSsmParameterValue(this, parameterName); new iam.Role(this, 'PhonyRole', { assumedBy: new iam.AccountPrincipal(param) }); @@ -181,7 +195,7 @@ class LambdaStack extends cdk.Stack { const fn = new lambda.Function(this, 'my-function', { code: lambda.Code.asset(path.join(__dirname, 'lambda')), - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler' }); @@ -199,7 +213,7 @@ class DockerStack extends cdk.Stack { // Add at least a single resource (WaitConditionHandle), otherwise this stack will never // be deployed (and its assets never built) - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); } @@ -216,7 +230,7 @@ class DockerStackWithCustomFile extends cdk.Stack { // Add at least a single resource (WaitConditionHandle), otherwise this stack will never // be deployed (and its assets never built) - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); } @@ -228,7 +242,7 @@ class FailedStack extends cdk.Stack { super(parent, id, props); // fails on 'Property PolicyDocument cannot be empty'. - new core.CfnResource(this, 'EmptyPolicy', { + new cdk.CfnResource(this, 'EmptyPolicy', { type: 'AWS::IAM::Policy' }) @@ -243,9 +257,10 @@ class DefineVpcStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - new ec2.Vpc(this, 'VPC', { + const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 1, - }).node.applyAspect(new cdk.Tag(VPC_TAG_NAME, VPC_TAG_VALUE)); + }) + cdk.Aspects.of(vpc).add(new cdk.Tag(VPC_TAG_NAME, VPC_TAG_VALUE)); } } diff --git a/packages/aws-cdk/test/integ/cli/app/nested-stack.js b/packages/aws-cdk/test/integ/cli/app/nested-stack.js index 3b3125963679b..87a45bb64b036 100644 --- a/packages/aws-cdk/test/integ/cli/app/nested-stack.js +++ b/packages/aws-cdk/test/integ/cli/app/nested-stack.js @@ -1,6 +1,14 @@ -const cfn = require('@aws-cdk/aws-cloudformation'); -const sns = require('@aws-cdk/aws-sns'); -const { Stack, CfnParameter } = require('@aws-cdk/core'); +if (process.env.PACKAGE_LAYOUT_VERSION === '1') { + var cfn = require('@aws-cdk/aws-cloudformation'); + var sns = require('@aws-cdk/aws-sns'); + var { Stack, CfnParameter } = require('@aws-cdk/core'); +} else { + var { + aws_cloudformation: cfn, + aws_sns: sns, + } = require('aws-cdk-lib'); + var { Stack, CfnParameter } = require('aws-cdk-lib'); +} class StackWithNestedStack extends Stack { constructor(scope, id) { diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index 6fce054dbbf10..1298c77a5fca8 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -129,23 +129,6 @@ integTest('deploy old style synthesis to new style bootstrap', withDefaultFixtur }); })); -integTest('deploying new style synthesis to old style bootstrap fails', withDefaultFixture(async (fixture) => { - const bootstrapStackName = fixture.bootstrapStackName; - - await fixture.cdkBootstrapLegacy({ - toolkitStackName: bootstrapStackName, - }); - - // Deploy stack that uses file assets, this fails because the bootstrap stack - // is version checked. - await expect(fixture.cdkDeploy('lambda', { - options: [ - '--toolkit-stack-name', bootstrapStackName, - '--context', '@aws-cdk/core:newStyleStackSynthesis=1', - ], - })).rejects.toThrow('exited with error'); -})); - integTest('can create a legacy bootstrap stack with --public-access-block-configuration=false', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.bootstrapStackName; diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index 873e3a1787ee5..127734a136491 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -2,7 +2,7 @@ import { promises as fs } from 'fs'; import * as os from 'os'; import * as path from 'path'; import { retry, sleep } from '../helpers/aws'; -import { cloneDirectory, shell, withDefaultFixture } from '../helpers/cdk'; +import { cloneDirectory, MAJOR_VERSION, shell, withDefaultFixture } from '../helpers/cdk'; import { integTest } from '../helpers/test-helpers'; jest.setTimeout(600 * 1000); @@ -40,7 +40,7 @@ integTest('Termination protection', withDefaultFixture(async (fixture) => { integTest('cdk synth', withDefaultFixture(async (fixture) => { await fixture.cdk(['synth', fixture.fullStackName('test-1')]); - expect(fixture.template('test-1')).toEqual({ + expect(fixture.template('test-1')).toEqual(expect.objectContaining({ Resources: { topic69831491: { Type: 'AWS::SNS::Topic', @@ -49,10 +49,10 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => { }, }, }, - }); + })); await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false }); - expect(fixture.template('test-2')).toEqual({ + expect(fixture.template('test-2')).toEqual(expect.objectContaining({ Resources: { topic152D84A37: { Type: 'AWS::SNS::Topic', @@ -67,8 +67,7 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => { }, }, }, - }); - + })); })); integTest('ssm parameter provider error', withDefaultFixture(async (fixture) => { @@ -223,12 +222,12 @@ integTest('deploy with parameters', withDefaultFixture(async (fixture) => { StackName: stackArn, }); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}bazinga`, }, - ]); + ); })); integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', withDefaultFixture(async (fixture) => { @@ -262,12 +261,12 @@ integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and crea // THEN expect (stackArn).not.toEqual(newStackArn); // new stack was created expect(newStackResponse.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(newStackResponse.Stacks?.[0].Parameters).toEqual([ + expect(newStackResponse.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, }, - ]); + ); })); integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', withDefaultFixture(async (fixture) => { @@ -313,12 +312,12 @@ integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', withDefaultF // THEN expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, }, - ]); + ); })); integTest('deploy with wildcard and parameters', withDefaultFixture(async (fixture) => { @@ -348,16 +347,18 @@ integTest('deploy with parameters multi', withDefaultFixture(async (fixture) => StackName: stackArn, }); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'DisplayNameParam', ParameterValue: paramVal1, }, + ); + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'OtherDisplayNameParam', ParameterValue: paramVal2, }, - ]); + ); })); integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => { @@ -382,82 +383,86 @@ integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => } })); -integTest('deploy with role', withDefaultFixture(async (fixture) => { - const roleName = `${fixture.stackNamePrefix}-test-role`; - - await deleteRole(); - - const createResponse = await fixture.aws.iam('createRole', { - RoleName: roleName, - AssumeRolePolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [{ - Action: 'sts:AssumeRole', - Principal: { Service: 'cloudformation.amazonaws.com' }, - Effect: 'Allow', - }, { - Action: 'sts:AssumeRole', - Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, - Effect: 'Allow', - }], - }), - }); - const roleArn = createResponse.Role.Arn; - try { - await fixture.aws.iam('putRolePolicy', { +if (MAJOR_VERSION === '1') { + // NOTE: this doesn't currently work with modern-style synthesis, as the bootstrap + // role by default will not have permission to iam:PassRole the created role. + integTest('deploy with role', withDefaultFixture(async (fixture) => { + const roleName = `${fixture.stackNamePrefix}-test-role`; + + await deleteRole(); + + const createResponse = await fixture.aws.iam('createRole', { RoleName: roleName, - PolicyName: 'DefaultPolicy', - PolicyDocument: JSON.stringify({ + AssumeRolePolicyDocument: JSON.stringify({ Version: '2012-10-17', Statement: [{ - Action: '*', - Resource: '*', + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, Effect: 'Allow', }], }), }); + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { + RoleName: roleName, + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: '*', + Resource: '*', + Effect: 'Allow', + }], + }), + }); - await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { - await fixture.aws.sts('assumeRole', { - RoleArn: roleArn, - RoleSessionName: 'testing', + await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', + }); }); - }); - // In principle, the role has replicated from 'us-east-1' to wherever we're testing. - // Give it a little more sleep to make sure CloudFormation is not hitting a box - // that doesn't have it yet. - await sleep(5000); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await sleep(5000); - await fixture.cdkDeploy('test-2', { - options: ['--role-arn', roleArn], - }); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); - // Immediately delete the stack again before we delete the role. - // - // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack - // operations will fail when CloudFormation tries to assume the role that's already gone. - await fixture.cdkDestroy('test-2'); + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); - } finally { - await deleteRole(); - } + } finally { + await deleteRole(); + } - async function deleteRole() { - try { - for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { - await fixture.aws.iam('deleteRolePolicy', { - RoleName: roleName, - PolicyName: policyName, - }); + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); + } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } catch (e) { + if (e.message.indexOf('cannot be found') > -1) { return; } + throw e; } - await fixture.aws.iam('deleteRole', { RoleName: roleName }); - } catch (e) { - if (e.message.indexOf('cannot be found') > -1) { return; } - throw e; } - } -})); + })); +} integTest('cdk diff', withDefaultFixture(async (fixture) => { const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); @@ -734,7 +739,7 @@ integTest('templates on disk contain metadata resource, also in nested assemblie expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); // Load template from nested assembly - const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*-stage-StackInStage.template.json']); + const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*StackInStage*.template.json']); expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); })); diff --git a/packages/aws-cdk/test/integ/helpers/cdk.ts b/packages/aws-cdk/test/integ/helpers/cdk.ts index 4a09f43063600..12e60efe243f7 100644 --- a/packages/aws-cdk/test/integ/helpers/cdk.ts +++ b/packages/aws-cdk/test/integ/helpers/cdk.ts @@ -12,10 +12,23 @@ 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; +const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION ?? '*'; + +export let MAJOR_VERSION = FRAMEWORK_VERSION.split('.')[0]; +if (MAJOR_VERSION === '*') { + if (process.env.REPO_ROOT) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const releaseJson = require(path.resolve(process.env.REPO_ROOT, 'release.json')); + MAJOR_VERSION = `${releaseJson.majorVersion}`; + } else { + // eslint-disable-next-line no-console + console.error('[WARNING] Have to guess at major version. Guessing version 1 to not break anything, but this should not happen'); + MAJOR_VERSION = '1'; + } +} process.stdout.write(`Using regions: ${REGIONS}\n`); -process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION}\n`); +process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION} (major version ${MAJOR_VERSION})\n`); const REGION_POOL = new ResourcePool(REGIONS); @@ -63,17 +76,26 @@ export function withCdkApp(block: (context: let success = true; try { - const version = FRAMEWORK_VERSION ?? '*'; - await installNpmPackages(fixture, { - '@aws-cdk/core': version, - '@aws-cdk/aws-sns': version, - '@aws-cdk/aws-iam': version, - '@aws-cdk/aws-lambda': version, - '@aws-cdk/aws-ssm': version, - '@aws-cdk/aws-ecr-assets': version, - '@aws-cdk/aws-cloudformation': version, - '@aws-cdk/aws-ec2': version, - }); + const installationVersion = FRAMEWORK_VERSION; + + if (MAJOR_VERSION === '1') { + await installNpmPackages(fixture, { + '@aws-cdk/core': installationVersion, + '@aws-cdk/aws-sns': installationVersion, + '@aws-cdk/aws-iam': installationVersion, + '@aws-cdk/aws-lambda': installationVersion, + '@aws-cdk/aws-ssm': installationVersion, + '@aws-cdk/aws-ecr-assets': installationVersion, + '@aws-cdk/aws-cloudformation': installationVersion, + '@aws-cdk/aws-ec2': installationVersion, + 'constructs': '^3', + }); + } else { + await installNpmPackages(fixture, { + 'aws-cdk-lib': installationVersion, + 'constructs': '^10', + }); + } await ensureBootstrapped(fixture); @@ -377,6 +399,7 @@ export class TestFixture { AWS_REGION: this.aws.region, AWS_DEFAULT_REGION: this.aws.region, STACK_NAME_PREFIX: this.stackNamePrefix, + PACKAGE_LAYOUT_VERSION: MAJOR_VERSION, ...options.modEnv, }, }); @@ -518,11 +541,22 @@ let sanityChecked: boolean | undefined; * by hand so let's just mass-automate it. */ async function ensureBootstrapped(fixture: TestFixture) { - // Use the default name for the bootstrap stack - if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { - // use whatever version of bootstrap is the default for this particular version of the CLI - await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); - } + // Always use the modern bootstrap stack, otherwise we may get the error + // "refusing to downgrade from version 7 to version 0" when bootstrapping with default + // settings using a v1 CLI. + // + // It doesn't matter for tests: when they want to test something about an actual legacy + // bootstrap stack, they'll create a bootstrap stack with a non-default name to test that exact property. + const envSpecifier = `aws://${await fixture.aws.account()}/${fixture.aws.region}`; + if (ALREADY_BOOTSTRAPPED_IN_THIS_RUN.has(envSpecifier)) { return; } + + await fixture.cdk(['bootstrap', envSpecifier], { + modEnv: { + // Even for v1, use new bootstrap + CDK_NEW_BOOTSTRAP: '1', + }, + }); + ALREADY_BOOTSTRAPPED_IN_THIS_RUN.add(envSpecifier); } /** @@ -666,3 +700,5 @@ const installNpm7 = memoize0(async (): Promise => { return path.join(installDir, 'node_modules', '.bin', 'npm'); }); + +const ALREADY_BOOTSTRAPPED_IN_THIS_RUN = new Set(); \ No newline at end of file diff --git a/packages/aws-cdk/test/integ/init/test-go.sh b/packages/aws-cdk/test/integ/init/test-go.sh index 6b461f3e25ef5..58959855073f0 100755 --- a/packages/aws-cdk/test/integ/init/test-go.sh +++ b/packages/aws-cdk/test/integ/init/test-go.sh @@ -5,13 +5,14 @@ set -eu scriptdir=$(cd $(dirname $0) && pwd) source ${scriptdir}/common.bash +dist_root=$(cd ${DIST_ROOT:-.} && pwd) header Go #------------------------------------------------------------------ if [[ "${1:-}" == "" ]]; then - templates="app" + templates="app sample-app" else templates="$@" fi @@ -22,5 +23,8 @@ for template in $templates; do setup cdk init -l go $template + go mod edit -replace github.com/aws/aws-cdk-go/awscdk=$dist_root/go/awscdk + go mod tidy + go test cdk synth done diff --git a/packages/aws-cdk/test/integ/init/test-java.sh b/packages/aws-cdk/test/integ/init/test-java.sh index f27441809da3c..d2c95984fb178 100755 --- a/packages/aws-cdk/test/integ/init/test-java.sh +++ b/packages/aws-cdk/test/integ/init/test-java.sh @@ -22,5 +22,6 @@ for template in $templates; do setup cdk init -l java $template + mvn package cdk synth done diff --git a/packages/aws-cdk/test/integ/init/test-python.sh b/packages/aws-cdk/test/integ/init/test-python.sh index 1f5163df5ae48..d40d94d4534ca 100755 --- a/packages/aws-cdk/test/integ/init/test-python.sh +++ b/packages/aws-cdk/test/integ/init/test-python.sh @@ -26,6 +26,8 @@ for template in $templates; do source .venv/bin/activate type -p pip pip install -r requirements.txt + ./.venv/bin/pip install -r requirements-dev.txt + pytest cdk synth done diff --git a/packages/aws-cdk/test/integ/run-against-dist b/packages/aws-cdk/test/integ/run-against-dist index ed17ddee88243..c7ade80dbb1f3 100755 --- a/packages/aws-cdk/test/integ/run-against-dist +++ b/packages/aws-cdk/test/integ/run-against-dist @@ -24,6 +24,11 @@ if [[ ! -f $dist_root/build.json ]]; then fi export CANDIDATE_VERSION=$(node -p "require('${dist_root}/build.json').version") + +# FRAMEWORK_VERSION is the version that will be 'npm install'ed by the tests +if [[ "${FRAMEWORK_VERSION:-}" = "" ]]; then + export FRAMEWORK_VERSION=${CANDIDATE_VERSION} +fi export TEST_RUNNER=dist serve_npm_packages diff --git a/packages/aws-cdk/test/integ/run-against-dist.bash b/packages/aws-cdk/test/integ/run-against-dist.bash index 0d5dc245df5e7..84162031f5068 100644 --- a/packages/aws-cdk/test/integ/run-against-dist.bash +++ b/packages/aws-cdk/test/integ/run-against-dist.bash @@ -4,6 +4,8 @@ npmws=/tmp/cdk-rundist rm -rf $npmws mkdir -p $npmws +set -x + # 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=() diff --git a/packages/aws-cdk/test/integ/run-against-release b/packages/aws-cdk/test/integ/run-against-release index 0865bd331a25d..1e4c0a8b3aba1 100755 --- a/packages/aws-cdk/test/integ/run-against-release +++ b/packages/aws-cdk/test/integ/run-against-release @@ -12,7 +12,14 @@ rm -rf $npmws mkdir -p $npmws # Install the CLI and put it on the PATH -(cd $npmws && npm install aws-cdk) +(cd $npmws && npm install aws-cdk@${RELEASE_TAG:-latest}) + +# FRAMEWORK_VERSION is the version that will be 'npm install'ed by the tests +if [[ "${FRAMEWORK_VERSION:-}" = "" ]]; then + cli_version=$(cd $npmws && node -p "require('aws-cdk/package.json').version") + export FRAMEWORK_VERSION=${cli_version} +fi + export PATH=$npmws/node_modules/.bin:$PATH export TEST_RUNNER=release diff --git a/packages/aws-cdk/test/integ/test-cli-regression.bash b/packages/aws-cdk/test/integ/test-cli-regression.bash index 12cbe81791d65..1770ee269d87f 100644 --- a/packages/aws-cdk/test/integ/test-cli-regression.bash +++ b/packages/aws-cdk/test/integ/test-cli-regression.bash @@ -54,12 +54,13 @@ function run_regression_against_framework_version() { echo "Downloading aws-cdk ${PREVIOUS_VERSION} tarball from npm" npm pack aws-cdk@${PREVIOUS_VERSION} - tar -zxvf aws-cdk-${PREVIOUS_VERSION}.tgz + tar -zxf 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}" + cp -r ${temp_dir}/package/test/integ/helpers "${integ_under_test}" patch_dir="${integdir}/cli-regression-patches/v${PREVIOUS_VERSION}" # delete possibly stale junit.xml file @@ -73,5 +74,10 @@ function run_regression_against_framework_version() { # 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 + export FRAMEWORK_VERSION=${!FRAMEWORK_VERSION_IDENTIFIER} + + # Show the versions we settled on + echo "♈️ Regression testing [cli $(cdk --version)] against [framework ${FRAMEWORK_VERSION}] using [tests ${PREVIOUS_VERSION}}]" + + ${integ_under_test}/test.sh } diff --git a/packages/aws-cdk/test/settings.test.ts b/packages/aws-cdk/test/settings.test.ts index 0f283b386006b..aef16e6bac946 100644 --- a/packages/aws-cdk/test/settings.test.ts +++ b/packages/aws-cdk/test/settings.test.ts @@ -100,6 +100,16 @@ test('bundling stacks defaults to * for deploy', () => { expect(settings.get(['bundlingStacks'])).toEqual(['*']); }); +test('bundling stacks defaults to * for watch', () => { + // GIVEN + const settings = Settings.fromCommandLineArguments({ + _: [Command.WATCH], + }); + + // THEN + expect(settings.get(['bundlingStacks'])).toEqual(['*']); +}); + test('bundling stacks with deploy exclusively', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ @@ -112,6 +122,18 @@ test('bundling stacks with deploy exclusively', () => { expect(settings.get(['bundlingStacks'])).toEqual(['cool-stack']); }); +test('bundling stacks with watch exclusively', () => { + // GIVEN + const settings = Settings.fromCommandLineArguments({ + _: [Command.WATCH], + exclusively: true, + STACKS: ['cool-stack'], + }); + + // THEN + expect(settings.get(['bundlingStacks'])).toEqual(['cool-stack']); +}); + test('should include outputs-file in settings', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ diff --git a/packages/aws-cdk/test/usersettings.test.ts b/packages/aws-cdk/test/usersettings.test.ts index 948b3b3f907bc..92d7db4a44025 100644 --- a/packages/aws-cdk/test/usersettings.test.ts +++ b/packages/aws-cdk/test/usersettings.test.ts @@ -69,4 +69,24 @@ test('load context from all 3 files if available', async () => { expect(config.context.get('project')).toBe('foobar'); expect(config.context.get('foo')).toBe('bar'); expect(config.context.get('test')).toBe('bar'); +}); + +test('throws an error if the `build` key is specified in the user config', async () => { + // GIVEN + const GIVEN_CONFIG: Map = new Map([ + [USER_CONFIG, { + build: 'foobar', + }], + ]); + + // WHEN + mockedFs.pathExists.mockImplementation(path => { + return GIVEN_CONFIG.has(path); + }); + mockedFs.readJSON.mockImplementation(path => { + return GIVEN_CONFIG.get(path); + }); + + // THEN + await expect(new Configuration().load()).rejects.toEqual(new Error('The `build` key cannot be specified in the user config (~/.cdk.json), specify it in the project config (cdk.json) instead')); }); \ No newline at end of file diff --git a/packages/aws-cdk/test/util/cloudformation.test.ts b/packages/aws-cdk/test/util/cloudformation.test.ts index c8c034ba23f67..40a748d50ea37 100644 --- a/packages/aws-cdk/test/util/cloudformation.test.ts +++ b/packages/aws-cdk/test/util/cloudformation.test.ts @@ -1,3 +1,4 @@ +import { SSMPARAM_NO_INVALIDATE } from '@aws-cdk/cx-api'; import { CloudFormationStack, TemplateParameters } from '../../lib/api/util/cloudformation'; import { MockedObject, MockSdkProvider, SyncHandlerSubsetOf } from './mock-sdk'; @@ -81,10 +82,32 @@ test('if a parameter is retrieved from SSM, the parameters always count as chang const oldValues = { Foo: '/Some/Key' }; // If we don't pass a new value - expect(params.updateExisting({}, oldValues).hasChanges(oldValues)).toEqual(true); + expect(params.updateExisting({}, oldValues).hasChanges(oldValues)).toEqual('ssm'); // If we do pass a new value but it's the same as the old one - expect(params.updateExisting({ Foo: '/Some/Key' }, oldValues).hasChanges(oldValues)).toEqual(true); + expect(params.updateExisting({ Foo: '/Some/Key' }, oldValues).hasChanges(oldValues)).toEqual('ssm'); +}); + +test('if a parameter is retrieved from SSM, the parameters doesnt count as changed if it has the magic marker', () => { + const params = TemplateParameters.fromTemplate({ + Parameters: { + Foo: { + Type: 'AWS::SSM::Parameter::Name', + Default: '/Some/Key', + Description: `blabla ${SSMPARAM_NO_INVALIDATE}`, + }, + }, + }); + const oldValues = { Foo: '/Some/Key' }; + + // If we don't pass a new value + expect(params.updateExisting({}, oldValues).hasChanges(oldValues)).toEqual(false); + + // If we do pass a new value but it's the same as the old one + expect(params.updateExisting({ Foo: '/Some/Key' }, oldValues).hasChanges(oldValues)).toEqual(false); + + // If we do pass a new value and it's different + expect(params.updateExisting({ Foo: '/OTHER/Key' }, oldValues).hasChanges(oldValues)).toEqual(true); }); test('empty string is a valid update value', () => { diff --git a/packages/aws-cdk/test/util/mock-child_process.ts b/packages/aws-cdk/test/util/mock-child_process.ts index 539fd06273399..d7645b4c2ce1a 100644 --- a/packages/aws-cdk/test/util/mock-child_process.ts +++ b/packages/aws-cdk/test/util/mock-child_process.ts @@ -6,16 +6,11 @@ if (!(child_process as any).spawn.mockImplementationOnce) { } export interface Invocation { - commandLine: string[]; + commandLine: string; cwd?: string; exitCode?: number; stdout?: string; - /** - * Only match a prefix of the command (don't care about the details of the arguments) - */ - prefix?: boolean; - /** * Run this function as a side effect, if present */ @@ -26,14 +21,8 @@ export function mockSpawn(...invocations: Invocation[]) { let mock = (child_process.spawn as any); for (const _invocation of invocations) { const invocation = _invocation; // Mirror into variable for closure - mock = mock.mockImplementationOnce((binary: string, args: string[], options: child_process.SpawnOptions) => { - if (invocation.prefix) { - // Match command line prefix - expect([binary, ...args].slice(0, invocation.commandLine.length)).toEqual(invocation.commandLine); - } else { - // Match full command line - expect([binary, ...args]).toEqual(invocation.commandLine); - } + mock = mock.mockImplementationOnce((binary: string, options: child_process.SpawnOptions) => { + expect(binary).toEqual(invocation.commandLine); if (invocation.cwd != null) { expect(options.cwd).toBe(invocation.cwd); @@ -60,8 +49,8 @@ export function mockSpawn(...invocations: Invocation[]) { }); } - mock.mockImplementation((binary: string, args: string[], _options: any) => { - throw new Error(`Did not expect call of ${JSON.stringify([binary, ...args])}`); + mock.mockImplementation((binary: string, _options: any) => { + throw new Error(`Did not expect call of ${binary}`); }); } diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index 19ab5ae3cc9f5..c97dccc98d7f8 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -70,6 +70,10 @@ export class MockSdkProvider extends SdkProvider { (this.sdk as any).ecr = jest.fn().mockReturnValue(partialAwsService(stubs)); } + public stubEcs(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + (this.sdk as any).ecs = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); + } + /** * Replace the S3 client with the given object */ @@ -101,6 +105,14 @@ export class MockSdkProvider extends SdkProvider { public stubLambda(stubs: SyncHandlerSubsetOf) { (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs)); } + + public stubStepFunctions(stubs: SyncHandlerSubsetOf) { + (this.sdk as any).stepFunctions = jest.fn().mockReturnValue(partialAwsService(stubs)); + } + + public stubGetEndpointSuffix(stub: () => string) { + this.sdk.getEndpointSuffix = stub; + } } export class MockSdk implements ISDK { @@ -112,9 +124,14 @@ export class MockSdk implements ISDK { public readonly s3 = jest.fn(); public readonly route53 = jest.fn(); public readonly ecr = jest.fn(); + public readonly ecs = jest.fn(); public readonly elbv2 = jest.fn(); public readonly secretsManager = jest.fn(); public readonly kms = jest.fn(); + public readonly stepFunctions = jest.fn(); + public readonly getEndpointSuffix = jest.fn(); + public readonly appendCustomUserAgent = jest.fn(); + public readonly removeCustomUserAgent = jest.fn(); public currentAccount(): Promise { return Promise.resolve({ accountId: '123456789012', partition: 'aws' }); @@ -140,6 +157,13 @@ export class MockSdk implements ISDK { public stubSsm(stubs: SyncHandlerSubsetOf) { this.ssm.mockReturnValue(partialAwsService(stubs)); } + + /** + * Replace the getEndpointSuffix client with the given object + */ + public stubGetEndpointSuffix(stub: () => string) { + this.getEndpointSuffix.mockReturnValue(stub()); + } } /** @@ -170,7 +194,7 @@ export class MockSdk implements ISDK { * types of the handlers on the input object from the ACTUAL AWS Service class, * so that you don't have to declare them. */ -function partialAwsService(fns: SyncHandlerSubsetOf): S { +function partialAwsService(fns: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}): S { // Super unsafe in here because I don't know how to make TypeScript happy, // but at least the outer types make sure everything that happens in here works out. const ret: any = {}; @@ -178,6 +202,9 @@ function partialAwsService(fns: SyncHandlerSubsetOf): S { for (const [key, handler] of Object.entries(fns)) { ret[key] = (args: any) => new FakeAWSResponse((handler as any)(args)); } + for (const [key, value] of Object.entries(additionalProperties)) { + ret[key] = value; + } return ret; } diff --git a/packages/awslint/package.json b/packages/awslint/package.json index c3303317ce6e8..733b922439ed3 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -18,16 +18,16 @@ "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.38.0", - "camelcase": "^6.2.0", + "@jsii/spec": "^1.45.0", + "camelcase": "^6.2.1", "colors": "^1.4.0", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.38.0", + "jsii-reflect": "^1.45.0", "yargs": "^16.2.0" }, "devDependencies": { "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yargs": "^15.0.14", "@aws-cdk/pkglint": "0.0.0", "typescript": "~3.9.10", @@ -37,9 +37,9 @@ "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", "@aws-cdk/eslint-plugin": "0.0.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.5.2", - "jest": "^26.6.3" + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^24.7.0", + "jest": "^27.3.1" }, "repository": { "type": "git", diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index e5572280b04df..e92c49df3412b 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -31,14 +31,14 @@ "license": "Apache-2.0", "devDependencies": { "@types/archiver": "^5.3.0", - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", "@types/mime": "^2.0.3", "@types/mock-fs": "^4.13.1", "@types/node": "^10.17.60", "@types/yargs": "^15.0.14", "@aws-cdk/cdk-build-tools": "0.0.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "jszip": "^3.7.1", "mock-fs": "^4.14.0", "@aws-cdk/pkglint": "0.0.0" @@ -49,7 +49,7 @@ "archiver": "^5.3.0", "aws-sdk": "^2.848.0", "glob": "^7.2.0", - "mime": "^2.5.2", + "mime": "^2.6.0", "yargs": "^16.2.0" }, "repository": { diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index b0fc6a3e4d5d4..b26d769698217 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -28,13 +28,13 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.38.0", + "codemaker": "^1.44.2", "yaml": "1.10.2" }, "devDependencies": { - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yaml": "1.9.7", - "jest": "^26.6.3", + "jest": "^27.3.1", "typescript": "~3.9.10" }, "keywords": [ diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 3682a01a1e602..496bc37da628e 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -29,6 +29,7 @@ "homepage": "https://github.com/aws/aws-cdk", "scripts": { "build": "echo Nothing to build", + "test": "echo OK", "package": "mkdir -p dist/js && cd dist/js && npm pack ../../", "build+test": "npm run build", "build+test+package": "npm run build+test && npm run package", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 2032a5a1de378..3279ac0ea734c 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -134,6 +134,7 @@ "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-iot-actions": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", "@aws-cdk/aws-iotanalytics": "0.0.0", "@aws-cdk/aws-iotcoredeviceadvisor": "0.0.0", @@ -182,6 +183,7 @@ "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-opsworks": "0.0.0", "@aws-cdk/aws-opsworkscm": "0.0.0", + "@aws-cdk/aws-panorama": "0.0.0", "@aws-cdk/aws-pinpoint": "0.0.0", "@aws-cdk/aws-pinpointemail": "0.0.0", "@aws-cdk/aws-qldb": "0.0.0", @@ -189,6 +191,7 @@ "@aws-cdk/aws-ram": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-redshift": "0.0.0", + "@aws-cdk/aws-rekognition": "0.0.0", "@aws-cdk/aws-resourcegroups": "0.0.0", "@aws-cdk/aws-robomaker": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", @@ -229,6 +232,7 @@ "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0", + "@aws-cdk/aws-wisdom": "0.0.0", "@aws-cdk/aws-workspaces": "0.0.0", "@aws-cdk/aws-xray": "0.0.0", "@aws-cdk/cfnspec": "0.0.0", @@ -243,18 +247,18 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.38.0", + "jsii-reflect": "^1.45.0", "jsonschema": "^1.4.0", "yaml": "1.10.2", "yargs": "^16.2.0" }, "devDependencies": { "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.14", - "jest": "^26.6.3", - "jsii": "^1.38.0" + "jest": "^27.3.1", + "jsii": "^1.45.0" }, "keywords": [ "aws", diff --git a/packages/decdk/test/fixture/tsconfig.json b/packages/decdk/test/fixture/tsconfig.json index 1b7d2bae0fab0..7ca946aa3a374 100644 --- a/packages/decdk/test/fixture/tsconfig.json +++ b/packages/decdk/test/fixture/tsconfig.json @@ -8,7 +8,7 @@ "inlineSourceMap": true, "inlineSources": true, "lib": [ - "es2018" + "es2019" ], "module": "CommonJS", "newLine": "lf", @@ -24,7 +24,7 @@ "strictNullChecks": true, "strictPropertyInitialization": true, "stripInternal": false, - "target": "ES2018", + "target": "ES2019", "composite": false, "tsBuildInfoFile": "tsconfig.tsbuildinfo" }, diff --git a/packages/individual-packages/package.json b/packages/individual-packages/package.json index 751a472a56807..4922b1e2d9bee 100644 --- a/packages/individual-packages/package.json +++ b/packages/individual-packages/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "private": true, "scripts": { - "build": "echo OK" + "build": "echo OK", + "test": "echo OK" }, "devDependencies": { "lerna": "^4.0.0" diff --git a/packages/monocdk/.gitignore b/packages/monocdk/.gitignore index 129f2f8e0bc37..f74c8e90c5eef 100644 --- a/packages/monocdk/.gitignore +++ b/packages/monocdk/.gitignore @@ -1,19 +1,17 @@ -*.js -*.d.ts -!deps.js -!gen.js -lib/ -tsconfig.json -.jsii -*.tsbuildinfo +# Ignore everything (because we're going to be generating into this +# directory) except certain source files. + +* +!LICENSE +!CONTRIBUTING.md +!NOTICE +!README.md +!scripts/* + dist .LAST_PACKAGE .LAST_BUILD *.snk -!.eslintrc.js - -# Ignore barrel import entry points -/*.ts - -junit.xml \ No newline at end of file +junit.xml +!.eslintrc.js \ No newline at end of file diff --git a/packages/monocdk/NOTICE b/packages/monocdk/NOTICE index bf88ea022136c..bd46bd848ec36 100644 --- a/packages/monocdk/NOTICE +++ b/packages/monocdk/NOTICE @@ -372,438 +372,3 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- - -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires - -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** lodash.clonedeep - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- \ No newline at end of file diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 526eb3ce967b3..ea10f3a7ec3c3 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -2,8 +2,8 @@ "name": "monocdk", "version": "0.0.0", "description": "An experiment to bundle the entire CDK into a single module", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git", @@ -91,33 +91,23 @@ "bundledDependencies": [ "@balena/dockerignore", "case", - "colors", - "diff", - "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", - "string-width", - "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "colors": "^1.4.0", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", - "ignore": "^5.1.8", + "ignore": "^5.1.9", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", - "string-width": "^4.2.3", - "table": "^6.7.2", "yaml": "1.10.2" }, "devDependencies": { @@ -228,6 +218,7 @@ "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-iot-actions": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", "@aws-cdk/aws-iotanalytics": "0.0.0", "@aws-cdk/aws-iotcoredeviceadvisor": "0.0.0", @@ -276,6 +267,7 @@ "@aws-cdk/aws-opensearchservice": "0.0.0", "@aws-cdk/aws-opsworks": "0.0.0", "@aws-cdk/aws-opsworkscm": "0.0.0", + "@aws-cdk/aws-panorama": "0.0.0", "@aws-cdk/aws-pinpoint": "0.0.0", "@aws-cdk/aws-pinpointemail": "0.0.0", "@aws-cdk/aws-qldb": "0.0.0", @@ -283,6 +275,7 @@ "@aws-cdk/aws-ram": "0.0.0", "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-redshift": "0.0.0", + "@aws-cdk/aws-rekognition": "0.0.0", "@aws-cdk/aws-resourcegroups": "0.0.0", "@aws-cdk/aws-robomaker": "0.0.0", "@aws-cdk/aws-route53": "0.0.0", @@ -323,6 +316,7 @@ "@aws-cdk/aws-waf": "0.0.0", "@aws-cdk/aws-wafregional": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0", + "@aws-cdk/aws-wisdom": "0.0.0", "@aws-cdk/aws-workspaces": "0.0.0", "@aws-cdk/aws-xray": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", diff --git a/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture b/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture deleted file mode 100644 index ae4b1befd4b20..0000000000000 --- a/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture +++ /dev/null @@ -1,18 +0,0 @@ -import { CfnOutput, Construct, Token } from '@aws-cdk/core'; - -declare interface SumProps { - readonly lhs: number; - readonly rhs: number; -} -declare class Sum extends Construct { - public readonly result: number; - constructor(scope: Construct, id: string, props: SumProps); -} - -class fixture$construct extends Construct { - public constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/basic-constructs.ts-fixture b/packages/monocdk/rosetta/basic-constructs.ts-fixture deleted file mode 100644 index 19ffd84abf486..0000000000000 --- a/packages/monocdk/rosetta/basic-constructs.ts-fixture +++ /dev/null @@ -1,22 +0,0 @@ -// Fixture with packages imported, but nothing else -import * as cdk from '@aws-cdk/core'; -import * as appreg from '@aws-cdk/aws-servicecatalogappregistry'; - -class Fixture extends cdk.Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const application = new appreg.Application(stack, 'MyApplication', { - applicationName: 'MyApplication', - }); - - const attributeGroup = new appreg.AttributeGroup(stack, 'MyAttributeGroup', { - attributeGroupName: 'testAttributeGroup', - attributes: { - key: 'value', - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/basic-portfolio.ts-fixture b/packages/monocdk/rosetta/basic-portfolio.ts-fixture deleted file mode 100644 index d8925e6645aa7..0000000000000 --- a/packages/monocdk/rosetta/basic-portfolio.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { - displayName: "MyFirstPortfolio", - providerName: "MyTeam", - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/canary.ts-fixture b/packages/monocdk/rosetta/canary.ts-fixture deleted file mode 100644 index d1950f9d9bcbe..0000000000000 --- a/packages/monocdk/rosetta/canary.ts-fixture +++ /dev/null @@ -1,21 +0,0 @@ -// Fixture with a canary already created, named `canary` -import { Construct, Duration, Stack } from '@aws-cdk/core'; -import * as synthetics from '@aws-cdk/aws-synthetics'; -import * as path from 'path'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const canary = new synthetics.Canary(this, 'MyCanary', { - schedule: synthetics.Schedule.rate(Duration.minutes(5)), - test: synthetics.Test.custom({ - code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), - handler: 'index.handler', - }), - runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_1, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/client-vpn.ts-fixture b/packages/monocdk/rosetta/client-vpn.ts-fixture deleted file mode 100644 index 4886d590211df..0000000000000 --- a/packages/monocdk/rosetta/client-vpn.ts-fixture +++ /dev/null @@ -1,17 +0,0 @@ -// Fixture with packages imported and a VPC created -import { Construct, Stack } from '@aws-cdk/core'; -import iam = require('@aws-cdk/aws-iam'); -import ec2 = require('@aws-cdk/aws-ec2'); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - const samlProvider = new iam.SamlProvider(this, 'Provider', { - metadataDocument: SamlMetadataDocument.fromXml('xml'), - }) - - /// here - } -} diff --git a/packages/monocdk/rosetta/cluster.ts-fixture b/packages/monocdk/rosetta/cluster.ts-fixture deleted file mode 100644 index 82d98ca3e381e..0000000000000 --- a/packages/monocdk/rosetta/cluster.ts-fixture +++ /dev/null @@ -1,20 +0,0 @@ -// Fixture with cluster already created -import { Construct, SecretValue, Stack } from '@aws-cdk/core'; -import { Vpc } from '@aws-cdk/aws-ec2'; -import { Cluster, Table, TableAction, User } from '@aws-cdk/aws-redshift'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new Vpc(this, 'Vpc'); - const cluster = new Cluster(this, 'Cluster', { - vpc, - masterUser: { - masterUsername: 'admin', - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/conns.ts-fixture b/packages/monocdk/rosetta/conns.ts-fixture deleted file mode 100644 index f29d9a1816a6e..0000000000000 --- a/packages/monocdk/rosetta/conns.ts-fixture +++ /dev/null @@ -1,26 +0,0 @@ -// Fixture with fake connectables -import { Construct, Stack } from '@aws-cdk/core'; -import ec2 = require('@aws-cdk/aws-ec2'); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - - const loadBalancer = new FakeConnectable(); - const appFleet = new FakeConnectable(); - const dbFleet = new FakeConnectable(); - const rdsDatabase = new FakeConnectable(); - const fleet1 = new FakeConnectable(); - const fleet2 = new FakeConnectable(); - const listener = new FakeConnectable(); - const myEndpoint = new FakeConnectable(); - - /// here - } -} - -class FakeConnectable implements ec2.IConnectable { - public readonly connections = new ec2.Connections({ securityGroups: [] }); -} diff --git a/packages/monocdk/rosetta/default.ts-fixture b/packages/monocdk/rosetta/default.ts-fixture deleted file mode 100644 index 558cc09b1c049..0000000000000 --- a/packages/monocdk/rosetta/default.ts-fixture +++ /dev/null @@ -1,65 +0,0 @@ -import * as cfn from '@aws-cdk/aws-cloudformation'; -import * as customresources from '@aws-cdk/custom-resources'; -import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as sns from '@aws-cdk/aws-sns'; -import * as sqs from '@aws-cdk/aws-sqs'; -import * as s3 from '@aws-cdk/aws-s3'; -import { - App, - Aws, - CfnCondition, - CfnDynamicReference, - CfnDynamicReferenceService, - CfnInclude, - CfnJson, - CfnMapping, - CfnOutput, - CfnParameter, - CfnResource, - CfnResourceProps, - ConcreteDependable, - Construct, - CustomResource, - CustomResourceProvider, - CustomResourceProviderRuntime, - DependableTrait, - Duration, - Fn, - IConstruct, - SecretValue, - Size, - SizeRoundingBehavior, - Stack, - StackProps, - Stage, - Token, -} from '@aws-cdk/core'; - -declare const app: App; -declare const arn: 'arn:partition:service:region:account-id:resource-id'; -declare const cfnResource: CfnResource; -declare const construct: Construct; -declare const constructA: Construct; -declare const constructB: Construct; -declare const constructC: Construct; -declare const functionProps: lambda.FunctionProps; -declare const isCompleteHandler: lambda.Function; -declare const myBucket: s3.IBucket; -declare const myFunction: lambda.IFunction; -declare const myProvider: CustomResourceProvider; -declare const myTopic: sns.ITopic; -declare const onEventHandler: lambda.Function; -declare const resourceProps: CfnResourceProps; -declare const stack: Stack; - -declare class MyStack extends Stack {} -declare class YourStack extends Stack {} - -class fixture$construct extends Construct { - public constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/function.ts-fixture b/packages/monocdk/rosetta/function.ts-fixture deleted file mode 100644 index 91eafe0abfcc0..0000000000000 --- a/packages/monocdk/rosetta/function.ts-fixture +++ /dev/null @@ -1,18 +0,0 @@ -// Fixture with function (`fn`) already created -import * as path from 'path'; -import { Construct, Stack } from '@aws-cdk/core'; -import { Alias, Code, Function, Runtime } from '@aws-cdk/aws-lambda'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const fn = new Function(this, 'MyFunction', { - runtime: Runtime.NODEJS_12_X, - handler: 'index.handler', - code: Code.fromAsset(path.join(__dirname, 'lambda-handler')), - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/migrate-opensearch.ts-fixture b/packages/monocdk/rosetta/migrate-opensearch.ts-fixture deleted file mode 100644 index bb93c1d40f369..0000000000000 --- a/packages/monocdk/rosetta/migrate-opensearch.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as es from '@aws-cdk/aws-elasticsearch'; -import * as iam from '@aws-cdk/aws-iam'; -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - -declare const role: iam.IRole; -declare const elasticsearchVersion: es.ElasticsearchVersion; -declare const openSearchVersion: opensearch.EngineVersion; - -class Fixture extends cdk.Construct { - constructor(scope: cdk.Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/portfolio-product.ts-fixture b/packages/monocdk/rosetta/portfolio-product.ts-fixture deleted file mode 100644 index 20a1db30bf3ee..0000000000000 --- a/packages/monocdk/rosetta/portfolio-product.ts-fixture +++ /dev/null @@ -1,28 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { - displayName: "MyFirstPortfolio", - providerName: "MyTeam", - }); - - const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', { - productName: "My Product", - owner: "Product Owner", - productVersions: [ - { - productVersionName: "v1", - cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromUrl( - 'https://raw.githubusercontent.com/awslabs/aws-cloudformation-templates/master/aws/services/ServiceCatalog/Product.yaml'), - }, - ] - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-batch-job.ts-fixture b/packages/monocdk/rosetta/with-batch-job.ts-fixture deleted file mode 100644 index 47672ba140841..0000000000000 --- a/packages/monocdk/rosetta/with-batch-job.ts-fixture +++ /dev/null @@ -1,38 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -import * as batch from '@aws-cdk/aws-batch'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as ecs from '@aws-cdk/aws-ecs'; -import * as path from 'path'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { - isDefault: true, - }); - - const batchQueue = new batch.JobQueue(this, 'JobQueue', { - computeEnvironments: [ - { - order: 1, - computeEnvironment: new batch.ComputeEnvironment(this, 'ComputeEnv', { - computeResources: { vpc }, - }), - }, - ], - }); - - const batchJobDefinition = new batch.JobDefinition(this, 'JobDefinition', { - container: { - image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, 'batchjob-image')), - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-bucket.ts-fixture b/packages/monocdk/rosetta/with-bucket.ts-fixture deleted file mode 100644 index d0851cff49639..0000000000000 --- a/packages/monocdk/rosetta/with-bucket.ts-fixture +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture with a bucket already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -import * as s3 from '@aws-cdk/aws-s3'; -declare const bucket: s3.Bucket; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-delivery-stream.ts-fixture b/packages/monocdk/rosetta/with-delivery-stream.ts-fixture deleted file mode 100644 index c7b75b20d2c1b..0000000000000 --- a/packages/monocdk/rosetta/with-delivery-stream.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a delivery stream already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const deliveryStream: DeliveryStream; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-destination.ts-fixture b/packages/monocdk/rosetta/with-destination.ts-fixture deleted file mode 100644 index 37d78bf7a43d3..0000000000000 --- a/packages/monocdk/rosetta/with-destination.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a destination already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const destination: IDestination; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture b/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture deleted file mode 100644 index 092b572afa726..0000000000000 --- a/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture +++ /dev/null @@ -1,30 +0,0 @@ -// Fixture with file system and an EC2 instance created in a VPC -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as efs from '@aws-cdk/aws-efs'; -import * as ec2 from '@aws-cdk/aws-ec2'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - - const fileSystem = new efs.FileSystem(this, 'FileSystem', { - vpc, - }); - - const instance = new ec2.Instance(this, 'instance', { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.LARGE), - machineImage: new ec2.AmazonLinuxImage({ - generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 - }), - vpc, - vpcSubnets: { - subnetType: ec2.SubnetType.PUBLIC, - } - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture b/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture deleted file mode 100644 index de9aa90eedfc2..0000000000000 --- a/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture +++ /dev/null @@ -1,26 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as cognito from '@aws-cdk/aws-cognito'; -import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const postAuthFn = new lambda.Function(this, 'postAuthFn', { - code: lambda.Code.fromInline('post authentication'), - runtime: lambda.Runtime.NODEJS_12_X, - handler: 'index.handler', - }); - - const userpool = new cognito.UserPool(this, 'myuserpool', { - lambdaTriggers: { - postAuthentication: postAuthFn, - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-plan.ts-fixture b/packages/monocdk/rosetta/with-plan.ts-fixture deleted file mode 100644 index 8dbfd6ac72c89..0000000000000 --- a/packages/monocdk/rosetta/with-plan.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as backup from '@aws-cdk/aws-backup'; -import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as events from '@aws-cdk/aws-events'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture b/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture deleted file mode 100644 index 115e1ece7e254..0000000000000 --- a/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture +++ /dev/null @@ -1,23 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; -import * as events from '@aws-cdk/aws-events'; -import * as kinesis from '@aws-cdk/aws-kinesis'; -import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const stream = new kinesis.Stream(this, 'MyStream'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture b/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture deleted file mode 100644 index 98d029d8d8283..0000000000000 --- a/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture +++ /dev/null @@ -1,23 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; -import * as events from '@aws-cdk/aws-events'; -import * as sqs from '@aws-cdk/aws-sqs'; -import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const queue = new sqs.Queue(this, 'MyQueue'); - - /// here - } -} diff --git a/scripts/@aws-cdk/script-tests/package.json b/scripts/@aws-cdk/script-tests/package.json index f30db59bcecd9..b7f81e397503b 100644 --- a/scripts/@aws-cdk/script-tests/package.json +++ b/scripts/@aws-cdk/script-tests/package.json @@ -12,6 +12,6 @@ "build+extract": "npm run build" }, "devDependencies": { - "jest": "^26.6.3" + "jest": "^27.3.1" } } diff --git a/scripts/@aws-cdk/script-tests/resolve-version.test.js b/scripts/@aws-cdk/script-tests/resolve-version.test.js index aee3779e56eed..6264bb281dd32 100644 --- a/scripts/@aws-cdk/script-tests/resolve-version.test.js +++ b/scripts/@aws-cdk/script-tests/resolve-version.test.js @@ -3,7 +3,7 @@ const os = require('os'); const path = require('path'); const resolveVersion = require('../../resolve-version-lib'); -beforeAll(() => spyOn(console, 'error')); +beforeAll(() => jest.spyOn(console, 'error')); happy({ name: 'stable release', diff --git a/scripts/bump.js b/scripts/bump.js index 14bb70fb0a5c8..3daca6711605f 100755 --- a/scripts/bump.js +++ b/scripts/bump.js @@ -74,7 +74,7 @@ async function main() { console.error("🎉 Calling our 'cdk-release' package to make the bump"); console.error("ℹ️ Set the LEGACY_BUMP env variable to use the old 'standard-version' bump instead"); const cdkRelease = require('@aws-cdk/cdk-release'); - cdkRelease(opts); + cdkRelease.createRelease(opts); } } diff --git a/scripts/cache-load.sh b/scripts/cache-load.sh new file mode 100755 index 0000000000000..23aff254f4f1f --- /dev/null +++ b/scripts/cache-load.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# If the environment variable S3_BUILD_CACHE is set, download and extract the +# tarball it points to into the directory $HOME/.s3buildcache. +# +set -eu + +cachedir=$HOME/.s3buildcache +mkdir -p $cachedir + +if [[ "${S3_BUILD_CACHE:-}" = "" ]]; then + exit 0 +fi + +echo "🧳 Build cache enabled: ${S3_BUILD_CACHE}" +if ! aws s3 ls ${S3_BUILD_CACHE} > /dev/null; then + echo "🧳⚠️ Cache not found." + exit 0 +fi + +if ! (cd $cachedir && aws s3 cp ${S3_BUILD_CACHE} - | tar xzv); then + echo "🧳⚠️ Something went wrong fetching the cache. Continuing anyway." +fi diff --git a/scripts/cache-store.sh b/scripts/cache-store.sh new file mode 100755 index 0000000000000..aaf99cbca4f0f --- /dev/null +++ b/scripts/cache-store.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# If the environment variable S3_BUILD_CACHE is set, compress and upload the +# contents of $HOME/.s3buildcache to it. +# +set -eu + +cachedir=$HOME/.s3buildcache +mkdir -p $cachedir + +if [[ "${S3_BUILD_CACHE:-}" = "" ]]; then + exit 0 +fi + +echo "🧳 Storing build cache at: ${S3_BUILD_CACHE}" + +if ! (cd $cachedir && tar czv . | aws s3 cp - ${S3_BUILD_CACHE}); then + echo "🧳⚠️ Something went wrong storing the cache." +fi + +echo "🧳 Finished." diff --git a/scripts/check-yarn-lock.js b/scripts/check-yarn-lock.js index 8fd59b5a60bc8..1941ad074ece6 100755 --- a/scripts/check-yarn-lock.js +++ b/scripts/check-yarn-lock.js @@ -36,9 +36,11 @@ async function main() { const yarnPackages = yarnLockPackages(); const projects = await new Project(repoRoot()).getPackages(); + const localPackageNames = new Set(projects.map(p => p.name)); + function errorIfNotInYarnLock(package, dependencyName, dependencyVersion) { const dependencyId = `${dependencyName}@${dependencyVersion}`; - const isLocalDependency = dependencyVersion === '0.0.0' || dependencyVersion === '^0.0.0'; + const isLocalDependency = localPackageNames.has(dependencyName); if (!isLocalDependency && !yarnPackages.has(dependencyId)) { throw new Error(`ERROR! Dependency ${dependencyId} from ${package.name} not present in yarn.lock. Please run 'yarn install' and try again!`); } diff --git a/scripts/create-release-notes.js b/scripts/create-release-notes.js new file mode 100755 index 0000000000000..1e6096c38bc00 --- /dev/null +++ b/scripts/create-release-notes.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +const cdkRelease = require('@aws-cdk/cdk-release'); +const ver = require('./resolve-version'); + +async function main() { + await cdkRelease.createReleaseNotes({ + versionFile: ver.versionFile, + changelogFile: ver.changelogFile, + alphaChangelogFile: ver.alphaChangelogFile, + releaseNotesFile: 'RELEASE_NOTES.md', + }); +} + +main().catch(err => { + console.error(err.stack); + process.exit(1); +}); diff --git a/scripts/list-deprecated-apis.js b/scripts/list-deprecated-apis.js index 284022b4825e1..a4596ebdf1a69 100755 --- a/scripts/list-deprecated-apis.js +++ b/scripts/list-deprecated-apis.js @@ -3,51 +3,78 @@ const path = require('path'); const jsiiReflect = require('jsii-reflect'); -async function main() { +class MarkdownPrinter { + printHeader() { + process.stdout.write(`# List of deprecated APIs in v1\n`); + process.stdout.write('\n'); + process.stdout.write(`| Module | API Element | Message |\n`); + process.stdout.write(`|--------|-------------|---------|\n`); + } + + printIfDeprecated(mod, name, el) { + try { + if (el.docs.deprecated) { + // Add zero-width spaces after . and _ to allow for line breaking long identifiers + // (WindowsVersion.WINDOWS_SERVER_2012_RTM_CHINESE_TRADITIONAL_HONG_KONG_SAR_64BIT_BASE is a fun one...) + // For consistency with the original format, replace the '#' separators with '.' + const apiName = name.replace('#', '.').replace(/(\.|_)/g, '$1\u200B'); + + // Some deprecation reasons start with '- ' for misguided reasons. Get rid of it, and also get rid of newlines. + const reason = el.docs.deprecationReason.replace(/^-/, '').replace(/\n/g, ' ').trim(); + + process.stdout.write(`| ${mod} | ${apiName} | ${reason} |\n`); + } + } catch (e) { + console.error(`While processing ${mod}.${name}:`, e); + } + } +} + +/** For use as input to the --strip-deprecated argument in jsii */ +class StripDeprecatedPrinter { + printHeader() { } + + printIfDeprecated(mod, name, el) { + try { + if (el.docs.deprecated) { + // Remove the method parens + process.stdout.write(`${mod}.${name.replace(/\(\)$/, '')}\n`); + } + } catch (e) { + console.error(`While processing ${mod}.${name}:`, e); + } + } +} + +async function main(printer) { const typesystem = new jsiiReflect.TypeSystem(); // Decdk depends on everything, so that's a perfect directory to load as closure await typesystem.loadNpmDependencies(path.resolve(__dirname, '..', 'packages', 'decdk'), { validate: false }); - process.stdout.write(`# List of deprecated APIs in v1\n`); - process.stdout.write('\n'); - process.stdout.write(`| Module | API Element | Message |\n`); - process.stdout.write(`|--------|-------------|---------|\n`); + printer.printHeader(); for (const assembly of typesystem.assemblies) { for (const type of assembly.types) { - printIfDeprecated(assembly.fqn, type.name, type); + printer.printIfDeprecated(assembly.fqn, type.name, type); if (type.isEnumType()) { - type.members.forEach(e => printIfDeprecated(assembly.fqn, `${type.name}.${e.name}`, e)); + type.members.forEach(e => printer.printIfDeprecated(assembly.fqn, `${type.name}#${e.name}`, e)); } if (type.isInterfaceType() || type.isClassType() || type.isDataType()) { - type.ownProperties.forEach(p => printIfDeprecated(assembly.fqn, `${type.name}.${p.name}`, p)); + type.ownProperties.forEach(p => printer.printIfDeprecated(assembly.fqn, `${type.name}#${p.name}`, p)); type.ownMethods.forEach(method => { - printIfDeprecated(assembly.fqn, `${type.name}.${method.name}()`, method); - method.parameters.forEach(p => printIfDeprecated(assembly.fqn, `${type.name}.${method.name}(): ${p.name}`, p)); + printer.printIfDeprecated(assembly.fqn, `${type.name}#${method.name}()`, method); + method.parameters.forEach(p => printer.printIfDeprecated(assembly.fqn, `${type.name}#${method.name}(): ${p.name}`, p)); }); } } } } -function printIfDeprecated(mod, name, el) { - try { - if (el.docs.deprecated) { - // Add zero-width spaces after . and _ to allow for line breaking long identifiers - // (WindowsVersion.WINDOWS_SERVER_2012_RTM_CHINESE_TRADITIONAL_HONG_KONG_SAR_64BIT_BASE is a fun one...) - const apiName = name.replace(/(\.|_)/g, '$1\u200B'); - - // Some deprecation reasons start with '- ' for misguided reasons. Get rid of it, and also get rid of newlines. - const reason = el.docs.deprecationReason.replace(/^-/, '').replace(/\n/g, ' ').trim(); - - process.stdout.write(`| ${mod} | ${apiName} | ${reason} |\n`); - } - } catch (e) { - console.error(`While processing ${mod}.${name}:`, e); - } -} +const printer = (process.argv.length > 2 && process.argv[2] === '--plain') + ? new StripDeprecatedPrinter() + : new MarkdownPrinter(); -main().catch(e => console.error(e)); +main(printer).catch(e => console.error(e)); diff --git a/scripts/list-packages b/scripts/list-packages index 95a2c5ecc0379..fe89f84db9410 100755 --- a/scripts/list-packages +++ b/scripts/list-packages @@ -12,7 +12,9 @@ if (process.argv.length < 4) { process.exit(1); } -child_process.exec('lerna ls --toposort --json', { shell: true }, (error, stdout) => { +const lerna = path.resolve(__dirname, '..', 'node_modules', '.bin', 'lerna'); + +child_process.exec(`${lerna} ls --toposort --json`, { shell: true }, (error, stdout) => { if (error) { console.error('Error: ', error); process.exit(-1); diff --git a/scripts/run-rosetta.sh b/scripts/run-rosetta.sh new file mode 100755 index 0000000000000..1bec4074305e4 --- /dev/null +++ b/scripts/run-rosetta.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Run jsii-rosetta on all jsii packages, using the S3 build cache if available. +# +# Usage: run-rosetta [--infuse] [--pkgs-from PKGSFILE] +# +# Performs three steps, in that order: +# +# 1. Run `rosetta extract` to read and translate all examples from all JSII +# assemblies. +# +# 2. Run `rosetta infuse` to traverse all examples we have, and copy them +# to classes that don't have an example yet. +# +# 3. Run `tools/@aws-cdk/generate-examples` to find all types that *still* +# don't have examples associated with tme, and generate synthetic examples. +# +# If you already have a file with a list of all the JSII package directories +# in it, pass it as the first argument. Otherwise, this script will run +# 'list-packages' to determine a list itself. +set -eu +scriptdir=$(cd $(dirname $0) && pwd) + +ROSETTA=${ROSETTA:-npx jsii-rosetta} + +infuse=false +jsii_pkgs_file="" +while [[ $# -gt 0 ]]; do + case $1 in + --infuse) + infuse="true" + ;; + --pkgs-from) + jsii_pkgs_file="$2" + shift + ;; + -h|--help) + echo "Usage: run-rosetta.sh [--infuse] [--pkgs-from FILE]" >&2 + exit 1 + ;; + *) + echo "Unrecognized argument: $1" >&2 + exit 1 + ;; + esac + shift +done + + +if [[ "${jsii_pkgs_file}" = "" ]]; then + echo "Collecting package list..." >&2 + TMPDIR=${TMPDIR:-$(dirname $(mktemp -u))} + node $scriptdir/list-packages $TMPDIR/jsii.txt $TMPDIR/nonjsii.txt + jsii_pkgs_file=$TMPDIR/jsii.txt +fi + +rosetta_cache_file=$HOME/.s3buildcache/rosetta-cache.tabl.json +rosetta_cache_opts="" +genexample_cache_opts="" +if [[ -f $rosetta_cache_file ]]; then + rosetta_cache_opts="--cache-from ${rosetta_cache_file}" + genexample_cache_opts="--cache-from ${rosetta_cache_file}" +fi + +#---------------------------------------------------------------------- + +# Compile examples with respect to "decdk" directory, as all packages will +# be symlinked there so they can all be included. +echo "💎 Extracting code samples" >&2 +$ROSETTA \ + --compile \ + --verbose \ + --output samples.tabl.json \ + $rosetta_cache_opts \ + --directory packages/decdk \ + $(cat $jsii_pkgs_file) + + +if $infuse; then + echo "💎 Infusing examples back into assemblies" >&2 + $ROSETTA infuse \ + --verbose \ + samples.tabl.json \ + $(cat $jsii_pkgs_file) + + echo "💎 Generating synthetic examples for the remainder" >&2 + time $scriptdir/../tools/@aws-cdk/generate-examples/bin/generate-examples \ + $genexample_cache_opts \ + --append-to samples.tabl.json \ + $(cat $jsii_pkgs_file) + +fi + +#---------------------------------------------------------------------- + +if [[ -d $(dirname $rosetta_cache_file) ]]; then + # If the cache directory is available, copy the current tablet into it + cp samples.tabl.json $rosetta_cache_file +fi diff --git a/scripts/transform.sh b/scripts/transform.sh index da780c3df49ff..0c2cc2477468f 100755 --- a/scripts/transform.sh +++ b/scripts/transform.sh @@ -25,12 +25,11 @@ createSymlinks() { runtarget="build" run_tests="true" -extract_snippets="false" skip_build="" while [[ "${1:-}" != "" ]]; do case $1 in -h|--help) - echo "Usage: transform.sh [--skip-test/build] [--extract]" + echo "Usage: transform.sh [--skip-test/build]" exit 1 ;; --skip-test|--skip-tests) @@ -39,9 +38,6 @@ while [[ "${1:-}" != "" ]]; do --skip-build) skip_build="true" ;; - --extract) - extract_snippets="true" - ;; *) echo "Unrecognized options: $1" exit 1 @@ -52,9 +48,6 @@ done if [ "$run_tests" == "true" ]; then runtarget="$runtarget+test" fi -if [ "$extract_snippets" == "true" ]; then - runtarget="$runtarget+extract" -fi export NODE_OPTIONS="--max-old-space-size=4096 --experimental-worker ${NODE_OPTIONS:-}" @@ -66,5 +59,6 @@ cd "$individual_packages_folder" createSymlinks "$individual_packages_folder" if [ "$skip_build" != "true" ]; then - PHASE=transform yarn lerna run --stream $runtarget + TRANSFORM_CONCURRENCY=${TRANSFORM_CONCURRENCY:-8} + PHASE=transform yarn lerna run --stream --concurrency ${TRANSFORM_CONCURRENCY} $runtarget fi diff --git a/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts b/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts index ccc6addd9ae5a..af35264465e38 100644 --- a/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts +++ b/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts @@ -30,13 +30,24 @@ async function main() { }, }; + const unitTestOptions = { + ...defaultShellOptions, + env: { + ...defaultShellOptions.env, + + // by default, fail when deprecated symbols are used in tests. + // tests that verify behaviour of deprecated symbols must use the `testDeprecated()` API. + JSII_DEPRECATED: 'fail', + }, + }; + if (options.test) { - await shell(options.test, defaultShellOptions); + await shell(options.test, unitTestOptions); } const testFiles = await unitTestFiles(); if (testFiles.length > 0) { - await shell([args.jest], defaultShellOptions); + await shell([args.jest], unitTestOptions); } // Run integration test if the package has integ test files diff --git a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js index b8e056af3c8a3..1b907f3b6ffc6 100644 --- a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js +++ b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js @@ -21,6 +21,7 @@ module.exports = { coveragePathIgnorePatterns: [ "/lib/.*\\.generated\\.[jt]s", "/test/.*\\.[jt]s", + "/.warnings.jsii.js", ], reporters: [ "default", diff --git a/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts b/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts new file mode 100644 index 0000000000000..d64f3b4e1f5c3 --- /dev/null +++ b/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts @@ -0,0 +1,54 @@ +/* eslint-disable jest/no-export */ + +/** + * A proxy over the jest 'describe()' block. + * By default, unit tests in the CDK repo disallow the use of deprecated + * symbols (classes, interfaces, properties, methods, etc.) in the unit tests + * or within the "code under test". + * Use this block to override when the test is verifying the behaviour of + * deprecated APIs. + */ +export function describeDeprecated(name: string, fn: jest.EmptyFunction) { + describe(name, () => { + let deprecated: string | undefined; + beforeEach(() => { + deprecated = DeprecatedSymbols.quiet(); + }); + afterEach(() => { + DeprecatedSymbols.reset(deprecated); + }); + fn(); + }); +} + +/** + * A proxy over the jest 'test()' block. + * By default, unit tests in the CDK repo disallow the use of deprecated + * symbols (classes, interfaces, properties, methods, etc.) in the unit tests + * or within the "code under test". + * Use this block to override when the test is verifying the behaviour of + * deprecated APIs. + */ +export function testDeprecated(name: string, fn: () => any, timeout?: number) { + test(name, () => { + const deprecated = DeprecatedSymbols.quiet(); + fn(); + DeprecatedSymbols.reset(deprecated); + }, timeout); +} + +namespace DeprecatedSymbols { + export function quiet(): string | undefined { + const deprecated = process.env.JSII_DEPRECATED; + process.env.JSII_DEPRECATED = 'quiet'; + return deprecated; + } + + export function reset(deprecated: string | undefined) { + if (deprecated === undefined) { + delete process.env.JSII_DEPRECATED; + } else { + process.env.JSII_DEPRECATED = deprecated; + } + } +} \ No newline at end of file diff --git a/tools/@aws-cdk/cdk-build-tools/lib/index.ts b/tools/@aws-cdk/cdk-build-tools/lib/index.ts index 4494512263a5e..bfac1b81c127e 100644 --- a/tools/@aws-cdk/cdk-build-tools/lib/index.ts +++ b/tools/@aws-cdk/cdk-build-tools/lib/index.ts @@ -1 +1,3 @@ export { shell } from './os'; +export * from './feature-flag'; +export * from './deprecated-symbols'; diff --git a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts index 5b79a2d675a0a..513d1b8dfdd11 100644 --- a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts +++ b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts @@ -97,9 +97,9 @@ export interface CompilerOverrides { */ export function packageCompiler(compilers: CompilerOverrides, options?: CDKBuildOptions): string[] { if (isJsii()) { - const args = ['--silence-warnings=reserved-word']; + const args = ['--silence-warnings=reserved-word', '--add-deprecation-warnings']; if (options?.stripDeprecated) { - args.push('--strip-deprecated'); + args.push(`--strip-deprecated ${path.join(__dirname, '..', '..', '..', '..', 'deprecated_apis.txt')}`); } return [compilers.jsii || require.resolve('jsii/bin/jsii'), ...args]; } else { diff --git a/tools/@aws-cdk/cdk-build-tools/package.json b/tools/@aws-cdk/cdk-build-tools/package.json index e3ea1cbc93a34..5d6761ff9d491 100644 --- a/tools/@aws-cdk/cdk-build-tools/package.json +++ b/tools/@aws-cdk/cdk-build-tools/package.json @@ -35,13 +35,15 @@ }, "license": "Apache-2.0", "devDependencies": { + "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", - "@types/yargs": "^15.0.14", - "@types/semver": "^7.3.8", - "@aws-cdk/pkglint": "0.0.0" + "@types/jest": "^27.0.2", + "@types/semver": "^7.3.9", + "@types/yargs": "^15.0.14" }, "dependencies": { + "@aws-cdk/eslint-plugin": "0.0.0", + "@aws-cdk/yarn-cling": "0.0.0", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "awslint": "0.0.0", @@ -49,22 +51,20 @@ "eslint": "^7.32.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", - "@aws-cdk/eslint-plugin": "0.0.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.5.2", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^24.7.0", "fs-extra": "^9.1.0", - "jest": "^26.6.3", - "jest-junit": "^11.1.0", - "jsii": "^1.38.0", - "jsii-pacmak": "^1.38.0", - "jsii-reflect": "^1.38.0", + "jest": "^27.3.1", + "jest-junit": "^13.0.0", + "jsii": "^1.45.0", + "jsii-pacmak": "^1.45.0", + "jsii-reflect": "^1.45.0", "markdownlint-cli": "^0.29.0", "nyc": "^15.1.0", "semver": "^7.3.5", - "ts-jest": "^26.5.6", + "ts-jest": "^27.0.7", "typescript": "~3.9.10", - "yargs": "^16.2.0", - "@aws-cdk/yarn-cling": "0.0.0" + "yargs": "^16.2.0" }, "keywords": [ "aws", diff --git a/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts index 3c5cb049cf0d0..cdca064874099 100644 --- a/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -6,7 +6,7 @@ import { DEFAULT_SYNTH_OPTIONS, IntegrationTests } from '../lib/integ-helpers'; /* eslint-disable no-console */ -const IGNORE_ASSETS_PRAGMA = 'pragma:ignore-assets'; +const VERIFY_ASSET_HASHES = 'pragma:include-assets-hashes'; async function main() { const tests = await new IntegrationTests('test').fromCliArgs(); // always assert all tests @@ -22,7 +22,8 @@ async function main() { let expected = await test.readExpected(); let actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); - if ((await test.pragmas()).includes(IGNORE_ASSETS_PRAGMA)) { + // We will always ignore asset hashes, unless specifically requested not to + if (!(await test.pragmas()).includes(VERIFY_ASSET_HASHES)) { expected = canonicalizeTemplate(expected); actual = canonicalizeTemplate(actual); } diff --git a/tools/@aws-cdk/cdk-release/lib/index.ts b/tools/@aws-cdk/cdk-release/lib/index.ts index b908e4cccdc7d..d058aec7e98ab 100644 --- a/tools/@aws-cdk/cdk-release/lib/index.ts +++ b/tools/@aws-cdk/cdk-release/lib/index.ts @@ -1,16 +1,17 @@ -import * as path from 'path'; -import * as fs from 'fs-extra'; import { getConventionalCommitsFromGitHistory } from './conventional-commits'; import { defaults } from './defaults'; import { bump } from './lifecycles/bump'; import { writeChangelogs } from './lifecycles/changelog'; import { commit } from './lifecycles/commit'; import { debug, debugObject } from './private/print'; -import { PackageInfo, ReleaseOptions, Versions } from './types'; +import { PackageInfo, ReleaseOptions } from './types'; +import { readVersion } from './versions'; // eslint-disable-next-line @typescript-eslint/no-require-imports const lerna_project = require('@lerna/project'); -module.exports = async function main(opts: ReleaseOptions): Promise { +export * from './release-notes'; + +export async function createRelease(opts: ReleaseOptions): Promise { // handle the default options const args: ReleaseOptions = { ...defaults, @@ -34,15 +35,6 @@ module.exports = async function main(opts: ReleaseOptions): Promise { await commit(args, newVersion.stableVersion, [args.versionFile, ...changelogResults.map(r => r.filePath)]); }; -function readVersion(versionFile: string): Versions { - const versionPath = path.resolve(process.cwd(), versionFile); - const contents = JSON.parse(fs.readFileSync(versionPath, { encoding: 'utf-8' })); - return { - stableVersion: contents.version, - alphaVersion: contents.alphaVersion, - }; -} - function getProjectPackageInfos(): PackageInfo[] { const packages = lerna_project.Project.getPackagesSync(); diff --git a/tools/@aws-cdk/cdk-release/lib/private/files.ts b/tools/@aws-cdk/cdk-release/lib/private/files.ts index d9a68dd894c87..6009633bc72b2 100644 --- a/tools/@aws-cdk/cdk-release/lib/private/files.ts +++ b/tools/@aws-cdk/cdk-release/lib/private/files.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -interface WriteFileOpts { +export interface WriteFileOpts { readonly dryRun?: boolean; } diff --git a/tools/@aws-cdk/cdk-release/lib/release-notes.ts b/tools/@aws-cdk/cdk-release/lib/release-notes.ts new file mode 100644 index 0000000000000..694613b6c7a0f --- /dev/null +++ b/tools/@aws-cdk/cdk-release/lib/release-notes.ts @@ -0,0 +1,69 @@ +// eslint-disable-next-line @typescript-eslint/no-require-imports +import parseChangelog = require('changelog-parser'); +import { WriteFileOpts, writeFile } from './private/files'; +import { debugObject, LoggingOptions } from './private/print'; +import { Versions } from './types'; +import { readVersion } from './versions'; + +export interface ReleaseNotesOpts { + /** path to the version file for the current branch (e.g., version.v2.json) */ + versionFile: string; + /** path to the primary changelog file (e.g., 'CHANGELOG.v2.md') */ + changelogFile: string; + /** (optional) path to the independent alpha changelog file (e.g., 'CHANGELOG.v2.alpha.md') */ + alphaChangelogFile?: string; + /** path to write out the final release notes (e.g., 'RELEASE_NOTES.md'). */ + releaseNotesFile: string; +} + +/** + * Creates a release notes file from one (or more) changelog files for the current version. + * If an alpha version and alpha changelog file aren't present, this is identical to the contents + * of the (main) changelog for the current version. Otherwise, a combined release is put together + * from the contents of the stable and alpha changelogs. + */ +export async function createReleaseNotes(opts: ReleaseNotesOpts & LoggingOptions & WriteFileOpts) { + const currentVersion = readVersion(opts.versionFile); + debugObject(opts, 'Current version info', currentVersion); + + writeFile(opts, opts.releaseNotesFile, await releaseNoteContents(currentVersion, opts)); +} + +async function releaseNoteContents(currentVersion: Versions, opts: ReleaseNotesOpts) { + const stableChangelogContents = await readChangelogSection(opts.changelogFile, currentVersion.stableVersion); + // If we don't have an alpha version and distinct alpha changelog, the release notes are just the main changelog section. + if (!opts.alphaChangelogFile || !currentVersion.alphaVersion) { return stableChangelogContents; } + + const alphaChangelogContents = await readChangelogSection(opts.alphaChangelogFile, currentVersion.alphaVersion); + + // See https://github.com/aws/aws-cdk-rfcs/blob/master/text/0249-v2-experiments.md#changelog--release-notes for format + return [ + stableChangelogContents, + '---', + // DO NOT CHANGE THE FORMAT OF THE FOLLOWING LINE. This will cause the v2 publishing verification canary to skip verification of Alpha modules. + // See https://github.com/cdklabs/cdk-ops/pull/1769. + `## Alpha modules (${currentVersion.alphaVersion})`, + alphaChangelogContents, + ].join('\n'); +} + +async function readChangelogSection(changelogFile: string, version: string) { + const changelog = await parseChangelog(changelogFile) as Changelog; + const entry = (changelog.versions || []).find(section => section.version === version); + if (!entry) { + throw new Error(`No changelog entry found for version ${version} in ${changelogFile}`); + } + return entry.body; +} + +/** @types/changelog-parser only returns `object`; this is slightly more helpful */ +interface Changelog { + title: string; + description: string; + versions?: ChangelogVersion[]; +} +interface ChangelogVersion { + version: string; + title: string; + body: string; +} diff --git a/tools/@aws-cdk/cdk-release/lib/versions.ts b/tools/@aws-cdk/cdk-release/lib/versions.ts new file mode 100644 index 0000000000000..d92ed61a5f0e5 --- /dev/null +++ b/tools/@aws-cdk/cdk-release/lib/versions.ts @@ -0,0 +1,12 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { Versions } from './types'; + +export function readVersion(versionFile: string): Versions { + const versionPath = path.resolve(process.cwd(), versionFile); + const contents = JSON.parse(fs.readFileSync(versionPath, { encoding: 'utf-8' })); + return { + stableVersion: contents.version, + alphaVersion: contents.alphaVersion, + }; +} diff --git a/tools/@aws-cdk/cdk-release/package.json b/tools/@aws-cdk/cdk-release/package.json index 9e06c857907db..9b308c8891716 100644 --- a/tools/@aws-cdk/cdk-release/package.json +++ b/tools/@aws-cdk/cdk-release/package.json @@ -30,24 +30,26 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", + "@types/changelog-parser": "^2.7.1", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yargs": "^15.0.14", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "dependencies": { "@lerna/project": "^4.0.0", + "changelog-parser": "^2.8.0", "conventional-changelog": "^3.1.24", "conventional-changelog-config-spec": "^2.1.0", "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-parser": "^3.2.2", "conventional-changelog-writer": "^4.1.0", + "conventional-commits-parser": "^3.2.3", + "detect-indent": "^6.1.0", + "detect-newline": "^3.1.0", "fs-extra": "^9.1.0", "git-raw-commits": "^2.0.10", "semver": "^7.3.5", - "stringify-package": "^1.0.1", - "detect-indent": "^6.1.0", - "detect-newline": "^3.1.0" + "stringify-package": "^1.0.1" }, "keywords": [ "aws", diff --git a/tools/@aws-cdk/cdk-release/test/release-notes.test.ts b/tools/@aws-cdk/cdk-release/test/release-notes.test.ts new file mode 100644 index 0000000000000..f190b6b84a332 --- /dev/null +++ b/tools/@aws-cdk/cdk-release/test/release-notes.test.ts @@ -0,0 +1,61 @@ +import * as files from '../lib/private/files'; +import { createReleaseNotes } from '../lib/release-notes'; +import * as versions from '../lib/versions'; + +/** MOCKS */ +const mockWriteFile = jest.spyOn(files, 'writeFile').mockImplementation(() => jest.fn()); +const mockReadVersion = jest.spyOn(versions, 'readVersion'); +jest.mock('changelog-parser', () => { return jest.fn(); }); +// eslint-disable-next-line @typescript-eslint/no-require-imports +const changelogParser = require('changelog-parser'); +/** MOCKS */ + +beforeEach(() => { jest.resetAllMocks(); }); + +const DEFAULT_OPTS = { + changelogFile: 'CHANGELOG.md', + releaseNotesFile: 'RELEASE_NOTES.md', + versionFile: 'versions.json', +}; + +test('without alpha releases, only the stable changelog is returned', async () => { + mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3' }; }); + mockChangelogOnceForVersion('1.2.3', 'foo'); + + await createReleaseNotes(DEFAULT_OPTS); + + expectReleaseNotes('foo'); +}); + +test('with alpha releases the contents of both are returned as separate sections', async () => { + mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3', alphaVersion: '1.2.3-alpha' }; }); + mockChangelogOnceForVersion('1.2.3', 'foo'); // stable + mockChangelogOnceForVersion('1.2.3-alpha', 'bar'); // alpha + + await createReleaseNotes({ ...DEFAULT_OPTS, alphaChangelogFile: 'CHANGELOG.alpha.md' }); + + expectReleaseNotes([ + 'foo', + '---', + '## Alpha modules (1.2.3-alpha)', + 'bar', + ]); +}); + +test('throws if no matching version is found in the changelog', async () => { + mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3' }; }); + mockChangelogOnceForVersion('4.5.6', 'foo'); + + await expect(createReleaseNotes(DEFAULT_OPTS)) + .rejects + .toThrow(/No changelog entry found for version 1.2.3 in CHANGELOG.md/); +}); + +function mockChangelogOnceForVersion(version: string, body: string) { + changelogParser.mockImplementationOnce((_: string) => { return { versions: [{ version, body }] }; }); +} + +function expectReleaseNotes(contents: string | string[]) { + const data = (typeof contents === 'string') ? contents : contents.join('\n'); + expect(mockWriteFile).toBeCalledWith(expect.any(Object), 'RELEASE_NOTES.md', data); +} diff --git a/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts b/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts index 2c145546acdb2..ec7500f05a1dc 100644 --- a/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts +++ b/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts @@ -138,7 +138,7 @@ export class AugmentationGenerator { dimStrings.push(`${key}: ${field}`); } - this.code.line(` dimensions: { ${dimStrings.join(', ') } },`); + this.code.line(` dimensionsMap: { ${dimStrings.join(', ') } },`); this.code.line(' ...props'); this.code.line(' }).attachTo(this);'); this.code.line('};'); diff --git a/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts b/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts index 6a540f81c7895..3388c391f1f3b 100644 --- a/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts +++ b/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts @@ -64,7 +64,7 @@ export class CannedMetricsGenerator { this.code.line('return {'); this.code.line(` namespace: '${metric.namespace}',`); this.code.line(` metricName: '${metric.metricName}',`); - this.code.line(' dimensions,'); + this.code.line(' dimensionsMap: dimensions,'); this.code.line(` statistic: '${metric.defaultStat}',`); this.code.line('};'); this.code.closeBlock(); @@ -93,7 +93,7 @@ export class CannedMetricsGenerator { } private emitTypeDef() { - this.code.line('type MetricWithDims = { namespace: string, metricName: string, statistic: string, dimensions: D };'); + this.code.line('type MetricWithDims = { namespace: string, metricName: string, statistic: string, dimensionsMap: D };'); } } diff --git a/tools/@aws-cdk/cfn2ts/package.json b/tools/@aws-cdk/cfn2ts/package.json index c1a2f4c805fec..7e386255d5681 100644 --- a/tools/@aws-cdk/cfn2ts/package.json +++ b/tools/@aws-cdk/cfn2ts/package.json @@ -32,7 +32,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.38.0", + "codemaker": "^1.44.2", "fast-json-patch": "^3.1.0", "fs-extra": "^9.1.0", "yargs": "^16.2.0" @@ -41,9 +41,9 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/yargs": "^15.0.14", - "jest": "^26.6.3" + "jest": "^27.3.1" }, "keywords": [ "aws", diff --git a/tools/@aws-cdk/eslint-plugin/package.json b/tools/@aws-cdk/eslint-plugin/package.json index 60cf20ed45b16..06c52ff0197af 100644 --- a/tools/@aws-cdk/eslint-plugin/package.json +++ b/tools/@aws-cdk/eslint-plugin/package.json @@ -14,13 +14,13 @@ "build+extract": "npm run build" }, "devDependencies": { - "@types/eslint": "^7.28.0", + "@types/eslint": "^7.29.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/node": "^10.17.60", "@types/estree": "*", "eslint-plugin-rulesdir": "^0.2.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "typescript": "~3.9.10" }, "dependencies": { diff --git a/tools/@aws-cdk/eslint-plugin/test/rules/fixtures.test.ts b/tools/@aws-cdk/eslint-plugin/test/rules/fixtures.test.ts index 394e342d8a49a..6442467eec59c 100644 --- a/tools/@aws-cdk/eslint-plugin/test/rules/fixtures.test.ts +++ b/tools/@aws-cdk/eslint-plugin/test/rules/fixtures.test.ts @@ -35,44 +35,37 @@ fs.readdirSync(fixturesRoot).filter(f => fs.lstatSync(path.join(fixturesRoot, f) const fixtureFiles = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.ts') && !f.endsWith('.expected.ts')); fixtureFiles.forEach(f => { - test(f, async (done) => { + it(f, async () => { const originalFilePath = path.join(fixturesDir, f); const expectedFixedFilePath = path.join(fixturesDir, `${path.basename(f, '.ts')}.expected.ts`); const expectedErrorFilepath = path.join(fixturesDir, `${path.basename(f, '.ts')}.error.txt`); const fix = fs.existsSync(expectedFixedFilePath); const checkErrors = fs.existsSync(expectedErrorFilepath); if (fix && checkErrors) { - done.fail(`Expected only a fixed file or an expected error message file. Both ${expectedFixedFilePath} and ${expectedErrorFilepath} are present.`); - return; + fail(`Expected only a fixed file or an expected error message file. Both ${expectedFixedFilePath} and ${expectedErrorFilepath} are present.`); } else if (fix) { const actualFile = await lintAndFix(originalFilePath, outputDir); const actual = await fs.readFile(actualFile, { encoding: 'utf8' }); const expected = await fs.readFile(expectedFixedFilePath, { encoding: 'utf8' }); if (actual !== expected) { - done.fail(`Linted file did not match expectations. Expected: ${expectedFixedFilePath}. Actual: ${actualFile}`); - return; + fail(`Linted file did not match expectations. Expected: ${expectedFixedFilePath}. Actual: ${actualFile}`); } - done(); return; } else if (checkErrors) { const actualErrorMessages = await lint(originalFilePath) - const expectedErrorMessages = await (await fs.readFile(expectedErrorFilepath, { encoding: 'utf8' })).split('\n'); + const expectedErrorMessages = (await fs.readFile(expectedErrorFilepath, { encoding: 'utf8' })).split('\n'); if (expectedErrorMessages.length !== actualErrorMessages?.length) { - done.fail(`Number of messages from linter did not match expectations. Linted file: ${originalFilePath}. Expected number of messages: ${expectedErrorMessages.length}. Actual number of messages: ${actualErrorMessages?.length}.`); - return; + fail(`Number of messages from linter did not match expectations. Linted file: ${originalFilePath}. Expected number of messages: ${expectedErrorMessages.length}. Actual number of messages: ${actualErrorMessages?.length}.`); } actualErrorMessages.forEach(actualMessage => { if(!(expectedErrorMessages.find(expectedMessage => expectedMessage === actualMessage.message))) { - done.fail(`Error message not found in .error.txt file. Linted file: ${originalFilePath}. Actual message: ${actualMessage.message}. Expected messages: ${expectedErrorMessages}`); - return; + fail(`Error message not found in .error.txt file. Linted file: ${originalFilePath}. Actual message: ${actualMessage.message}. Expected messages: ${expectedErrorMessages}`); } }); - done(); return; } else { - done.fail(`Expected fixed file or expected error file not found.`); + fail(`Expected fixed file or expected error file not found.`); } - done(); }); }); }); diff --git a/tools/@aws-cdk/generate-examples/.eslintrc.js b/tools/@aws-cdk/generate-examples/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/tools/@aws-cdk/generate-examples/.gitignore b/tools/@aws-cdk/generate-examples/.gitignore new file mode 100644 index 0000000000000..405a05419d4b6 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.gitignore @@ -0,0 +1,10 @@ +*.js +*.js.map +*.d.ts +dist + +*.snk +!.eslintrc.js +!config/*.js +junit.xml +!jest.config.js diff --git a/tools/@aws-cdk/generate-examples/.npmignore b/tools/@aws-cdk/generate-examples/.npmignore new file mode 100644 index 0000000000000..9aca5ee7d9678 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.npmignore @@ -0,0 +1,14 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +.LAST_BUILD +*.snk +.eslintrc.js + +# exclude cdk artifacts +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/LICENSE b/tools/@aws-cdk/generate-examples/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/tools/@aws-cdk/generate-examples/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-2021 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/tools/@aws-cdk/generate-examples/NOTICE b/tools/@aws-cdk/generate-examples/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/tools/@aws-cdk/generate-examples/README.md b/tools/@aws-cdk/generate-examples/README.md new file mode 100644 index 0000000000000..a363579acebc0 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/README.md @@ -0,0 +1,9 @@ +# Generate synthetic examples + +This tool is designed to run during the build. It will find all classes in the +JSII assembly that don't yet have any example code associated with them, and +will generate a synthetic example that shows how to instantiate the type. + +This is a method of last resort: we'd obviously prefer hand-written examples, +but this will make sure L1s will at least get something usable (which otherwise +would not have any examples at all). diff --git a/tools/@aws-cdk/generate-examples/bin/generate-examples b/tools/@aws-cdk/generate-examples/bin/generate-examples new file mode 100755 index 0000000000000..8950f29a1fcbf --- /dev/null +++ b/tools/@aws-cdk/generate-examples/bin/generate-examples @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./generate-examples.js'); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/bin/generate-examples.ts b/tools/@aws-cdk/generate-examples/bin/generate-examples.ts new file mode 100644 index 0000000000000..d03462865612b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/bin/generate-examples.ts @@ -0,0 +1,54 @@ +import * as yargs from 'yargs'; + +import { generateMissingExamples } from '../lib/generate-missing-examples'; + +async function main() { + const args = yargs + .usage('$0 [ASSEMBLY..]') + .option('cache-from', { + alias: 'C', + type: 'string', + describe: 'Reuse translations from the given tablet file', + requiresArg: true, + default: undefined, + }) + .option('append-to', { + alias: 'a', + type: 'string', + describe: 'Append translations to the given tablet file', + requiresArg: true, + default: undefined, + }) + .option('directory', { + alias: 'd', + type: 'string', + describe: 'Directory to run the compilation in (with dependencies set up)', + requiresArg: true, + default: undefined, + }) + .option('strict', { + alias: 's', + type: 'boolean', + describe: 'Whether to exit with an error if there are diagnostics', + default: false, + }) + .help() + .strict() + .showHelpOnFail(false) + .argv; + + const assemblyDirs = args._.map(x => `${x}`); + + await generateMissingExamples(assemblyDirs.length > 0 ? assemblyDirs : ['.'], { + cacheFromTablet: args['cache-from'], + appendToTablet: args['append-to'], + directory: args.directory, + strict: args.strict, + }); +} + +main().catch(e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/jest.config.js b/tools/@aws-cdk/generate-examples/jest.config.js new file mode 100644 index 0000000000000..c4f65f19ab3d7 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('../cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 60, + }, + }, +}; diff --git a/tools/@aws-cdk/generate-examples/lib/assemblies.ts b/tools/@aws-cdk/generate-examples/lib/assemblies.ts new file mode 100644 index 0000000000000..5efa530849ea2 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/assemblies.ts @@ -0,0 +1,40 @@ +import * as path from 'path'; +import * as spec from '@jsii/spec'; +import * as fs from 'fs-extra'; +import { TypeScriptSnippet } from 'jsii-rosetta'; + +/** + * Replaces the file where the original assembly file *should* be found with a new assembly file. + * Recalculates the fingerprint of the assembly to avoid tampering detection. + */ +export async function replaceAssembly(assembly: spec.Assembly, directory: string): Promise { + const fileName = path.join(directory, '.jsii'); + await fs.writeJson(fileName, _fingerprint(assembly), { + encoding: 'utf8', + spaces: 2, + }); +} + +/** + * Replaces the old fingerprint with '***********'. + * + * @rmuller says fingerprinting is useless, as we do not actually check + * if an assembly is changed. Instead of keeping the old (wrong) fingerprint + * or spending extra time calculating a new fingerprint, we replace with '**********' + * that demonstrates the fingerprint has changed. + */ +function _fingerprint(assembly: spec.Assembly): spec.Assembly { + assembly.fingerprint = '*'.repeat(10); + return assembly; +} + +/** + * Insert an example into the docs of a type + */ +export function insertExample(example: TypeScriptSnippet, type: spec.Type): void { + if (type.docs) { + type.docs.example = example.visibleSource; + } else { + type.docs = { example: example.visibleSource }; + } +} diff --git a/tools/@aws-cdk/generate-examples/lib/code.ts b/tools/@aws-cdk/generate-examples/lib/code.ts new file mode 100644 index 0000000000000..ae5e75082be19 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/code.ts @@ -0,0 +1,83 @@ +import { Declaration } from './declaration'; +import { sortBy } from './utils'; + +/** + * Information on a segment of code and the declarations necessary to make the code valid. + */ +export class Code { + public static concatAll(...xs: Array): Code { + return xs.map(Code.force).reduce((a, b) => a.append(b), new Code('')); + } + + private static force(x: Code | string): Code { + if (x instanceof Code) { + return x; + } + return new Code(x); + } + + /** + * Construct a Code, consisting of a code fragment and a list of declarations that are meant + * to be rendered at the top of the code snippet. + */ + constructor(public readonly code: string, public readonly declarations: Declaration[] = []) { + } + + /** + * Appends and returns a new Code that safely combines two code fragments along + * with their declarations. + */ + public append(rhs: Code | string): Code { + if (typeof rhs === 'string') { + return new Code(this.code + rhs, this.declarations); + } + + return new Code(this.code + rhs.code, [...this.declarations, ...rhs.declarations]); + } + + public toString() { + return this.render(); + } + + private render(separator = '\n\n') { + return (this.renderDeclarations().join('\n') + separator + this.code).trimStart(); + } + + /** + * Renders variable declarations. Assumes that there are no duplicates in the declarations. + */ + public renderDeclarations(): string[] { + sortBy(this.declarations, (d) => d.sortKey); + const decs = deduplicate(this.declarations); + // Add separator only if necessary + const decStrings = [...decs.map((d) => d.render())]; + // only supports two groups and not more + for (let i = 0; i < decs.length-1; i++) { + if (decs[i].sortKey[0] !== decs[i+1].sortKey[0]) { + decStrings.splice(i+1, 0, ''); + break; + } + } + return decStrings; + } + + public renderCode(): string { + return this.code; + } +} + +/** + * Deduplicates a sorted array of Declarations. + */ +function deduplicate(declarations: Declaration[]): Declaration[] { + if (declarations.length === 0) { return declarations; } + + const newDeclarations: Declaration[] = []; + newDeclarations.push(declarations[0]); + for (let i = 1; i < declarations.length; i++) { + if (!declarations[i].equals(declarations[i-1])) { + newDeclarations.push(declarations[i]); + } + } + return newDeclarations; +} diff --git a/tools/@aws-cdk/generate-examples/lib/declaration.ts b/tools/@aws-cdk/generate-examples/lib/declaration.ts new file mode 100644 index 0000000000000..df4282db96096 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/declaration.ts @@ -0,0 +1,82 @@ +import * as reflect from 'jsii-reflect'; + +import { module, typeNamespacedName } from './module-utils'; + +export abstract class Declaration { + constructor(public readonly sortKey: Array) {} + + public abstract equals(rhs: Declaration): boolean; + public abstract render(): string; +} + +/** + * An Import statement that will get rendered at the top of the code snippet. + */ +export class Import extends Declaration { + public readonly importName: string; + public readonly moduleName: string; + public readonly submoduleName?: string; + public readonly type: reflect.Type; + + public constructor(type: reflect.Type) { + const { importName, moduleName, submoduleName } = module(type); + + super([0, moduleName]); + + this.importName = importName; + this.moduleName = moduleName; + this.submoduleName = submoduleName; + this.type = type; + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + let what; + if (!this.submoduleName) { + what = `* as ${this.importName}`; + } else if (this.submoduleName === this.importName) { + what = `{ ${this.importName} }`; + } else { + what = `{ ${this.submoduleName} as ${this.importName} }`; + } + return `import ${what} from '${this.moduleName}';`; + } +} + +/** + * A declared constant that will be rendered at the top of the code snippet after the imports. + */ +export class Assumption extends Declaration { + public constructor(private readonly type: reflect.Type, private readonly name: string) { + super([1, name]); + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + return `declare const ${this.name}: ${module(this.type).importName}.${typeNamespacedName(this.type)};`; + } +} + +/** + * An assumption for an 'any' time. This will be treated the same as 'Assumption' but with a + * different render result. + */ +export class AnyAssumption extends Declaration { + public constructor(private readonly name: string) { + super([1, name]); + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + return `declare const ${this.name}: any;`; + } +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts b/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts new file mode 100644 index 0000000000000..3d3e6f74531bf --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts @@ -0,0 +1,155 @@ +/* eslint-disable no-console */ +import { promises as fs } from 'fs'; +import { Assembly, TypeSystem } from 'jsii-reflect'; + +// This import should come from @jsii/spec. Replace when that is possible. +import { LanguageTablet, RosettaTranslator, SnippetLocation, SnippetParameters, TypeScriptSnippet, typeScriptSnippetFromCompleteSource } from 'jsii-rosetta'; +import { insertExample, replaceAssembly } from './assemblies'; +import { generateAssignmentStatement } from './generate'; + +const COMMENT_WARNING = [ + '// The code below shows an example of how to instantiate this type.', + '// The values are placeholders you should change.', +]; + +export interface GenerateExamplesOptions { + readonly cacheFromTablet?: string; + readonly appendToTablet?: string; + readonly directory?: string; + readonly strict?: boolean; +} + +export async function generateMissingExamples(assemblyLocations: string[], options: GenerateExamplesOptions) { + const typesystem = new TypeSystem(); + + // load all assemblies into typesystem + const loadedAssemblies = await Promise.all(assemblyLocations.map(async (assemblyLocation) => { + if (!(await statFile(assemblyLocation))?.isDirectory) { + throw new Error(`Assembly location not a directory: ${assemblyLocation}`); + } + + return { assemblyLocation, assembly: await typesystem.load(assemblyLocation, { validate: false }) }; + })); + + const snippets = loadedAssemblies.flatMap(({ assembly }) => { + // Classes and structs + const documentableTypes = [ + ...assembly.classes.filter(c => !c.docs.example), + ...assembly.interfaces.filter(i => !i.docs.example && i.datatype), + ]; + + console.log(`${assembly.name}: ${documentableTypes.length} classes to document`); + if (documentableTypes.length === 0) { return []; } + + const failed = new Array(); + const generatedSnippets = documentableTypes.flatMap((classType) => { + const example = generateAssignmentStatement(classType); + if (!example) { + failed.push(classType.name); + return []; + } + + // To successfully compile, we need to generate the right 'Construct' import + const completeSource = [ + ...COMMENT_WARNING, + ...example.renderDeclarations(), + '', + '/// !hide', + correctConstructImport(assembly), + 'class MyConstruct extends Construct {', + 'constructor(scope: Construct, id: string) {', + 'super(scope, id);', + '/// !show', + example.renderCode(), + '/// !hide', + '} }', + ].join('\n').trimLeft(); + const location: SnippetLocation = { api: { api: 'type', fqn: classType.fqn }, field: { field: 'example' } }; + + const tsSnippet: TypeScriptSnippet = typeScriptSnippetFromCompleteSource( + completeSource, + location, + true, + { + [SnippetParameters.$COMPILATION_DIRECTORY]: options.directory ?? process.cwd(), + }); + + insertExample(tsSnippet, classType.spec); + return [tsSnippet]; + }); + + console.log([ + `${assembly.name}: annotated ${generatedSnippets.length} classes`, + ...(failed.length > 0 ? [`failed: ${failed.join(', ')}`] : []), + ].join(', ')); + + return generatedSnippets; + }); + + const rosetta = new RosettaTranslator({ + includeCompilerDiagnostics: true, + assemblies: loadedAssemblies.map(({ assembly }) => assembly.spec), + }); + + if (options.cacheFromTablet) { + await rosetta.loadCache(options.cacheFromTablet); + } + + // Will mutate the 'snippets' array + const { remaining } = rosetta.readFromCache(snippets); + + console.log(`Translating ${remaining.length} snippets`); + const results = await rosetta.translateAll(remaining); + if (results.diagnostics.length > 0) { + for (const diag of results.diagnostics) { + console.log(diag.formattedMessage); + } + + if (options.strict) { + process.exitCode = 1; + } + } + + // Copy everything from the rosetta tablet into our output tablet + if (options.appendToTablet) { + console.log(`Appending to ${options.appendToTablet}`); + const outputTablet = new LanguageTablet(); + if ((await statFile(options.appendToTablet)) !== undefined) { + await outputTablet.load(options.appendToTablet); + } + + for (const key of rosetta.tablet.snippetKeys) { + const snip = rosetta.tablet.tryGetSnippet(key); + if (snip) { + outputTablet.addSnippet(snip); + } + } + + await outputTablet.save(options.appendToTablet); + } + + console.log(`Saving ${loadedAssemblies.length} assemblies`); + await Promise.all((loadedAssemblies).map(({ assembly, assemblyLocation }) => + replaceAssembly(assembly.spec, assemblyLocation))); +} + +async function statFile(fileName: string) { + try { + return await fs.stat(fileName); + } catch (e) { + if (e.code === 'ENOENT') { return undefined; } + throw e; + } +} + +function correctConstructImport(assembly: Assembly) { + if (assembly.name === 'monocdk') { + return 'import { Construct } from "monocdk";'; + } + + if (assembly.dependencies.some(d => d.assembly.name === '@aws-cdk/core')) { + return 'import { Construct } from "@aws-cdk/core";'; + } + + return 'import { Construct } from "constructs";'; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/generate.ts b/tools/@aws-cdk/generate-examples/lib/generate.ts new file mode 100644 index 0000000000000..728b6cd3d7f1d --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/generate.ts @@ -0,0 +1,387 @@ +import * as spec from '@jsii/spec'; +import * as reflect from 'jsii-reflect'; +import { TypeSystem } from 'jsii-reflect'; + +import { Code } from './code'; +import { AnyAssumption, Assumption, Import } from './declaration'; +import { escapeIdentifier, typeReference } from './module-utils'; +import { sortBy } from './utils'; + +/** + * Special types that have a standard way of coming up with an example value + */ +const SPECIAL_TYPE_EXAMPLES: Record = { + '@aws-cdk/core.Duration': 'cdk.Duration.minutes(30)', + 'aws-cdk-lib.Duration': 'cdk.Duration.minutes(30)', +}; + +/** + * Context on the example that we are building. + * This object persists throughout the recursive call + * and provides the function with the same information + * on the typesystem and which types have already been + * rendered. This helps to prevent infinite recursion. + */ +class ExampleContext { + private readonly _typeSystem: TypeSystem; + private readonly _rendered: Set = new Set(); + + constructor(typeSystem: TypeSystem) { + this._typeSystem = typeSystem; + } + + public get typeSystem() { + return this._typeSystem; + } + + public get rendered() { + return this._rendered; + } +} + +export function generateAssignmentStatement(type: reflect.ClassType | reflect.InterfaceType): Code | undefined { + const context = new ExampleContext(type.system); + + if (type.isClassType()) { + const expression = exampleValueForClass(context, type, 0); + if (!expression) { return undefined; } + return Code.concatAll( + `const ${lowercaseFirstLetter(type.name)} = `, + expression, + ';', + ); + } + + if (type.isInterfaceType()) { + const expression = exampleValueForStruct(context, type, 0); + if (!expression) { return undefined; } + + return Code.concatAll( + `const ${lowercaseFirstLetter(type.name)}: `, + typeReference(type), + ' = ', + expression, + ';', + ); + } + + return undefined; +} + +function exampleValueForClass(context: ExampleContext, classType: reflect.ClassType, level: number): Code | undefined { + const staticFactoryMethods = getStaticFactoryMethods(classType); + const staticFactoryProperties = getStaticFactoryProperties(classType); + const initializer = getAccessibleConstructor(classType); + + if (initializer && initializer.parameters.length >= 3) { + return generateClassInstantiationExample(context, initializer, level); + } + + if (staticFactoryMethods.length >= 3) { + return generateStaticFactoryMethodExample(context, staticFactoryMethods[0], level); + } + + if (staticFactoryProperties.length >= 3) { + return generateStaticFactoryPropertyExample(staticFactoryProperties[0]); + } + + if (initializer) { + return generateClassInstantiationExample(context, initializer, level); + } + + if (staticFactoryMethods.length >= 1) { + return generateStaticFactoryMethodExample(context, staticFactoryMethods[0], level); + } + + if (staticFactoryProperties.length >= 1) { + return generateStaticFactoryPropertyExample(staticFactoryProperties[0]); + } + + return undefined; +} + +function getAccessibleConstructor(classType: reflect.ClassType): reflect.Initializer | undefined { + if (classType.abstract || !classType.initializer || classType.initializer.protected) { + return undefined; + } + return classType.initializer; +} + +/** + * Return the list of static methods on classtype that return either classtype or a supertype of classtype. + */ +function getStaticFactoryMethods(classType: reflect.ClassType): reflect.Method[] { + return classType.allMethods.filter(method => + method.static && extendsRef(classType, method.returns.type), + ); +} + +/** + * Return the list of static methods on classtype that return either classtype or a supertype of classtype. + */ +function getStaticFactoryProperties(classType: reflect.ClassType): reflect.Property[] { + return classType.allProperties.filter(prop => + prop.static && extendsRef(classType, prop.type), + ); +} + +function generateClassInstantiationExample(context: ExampleContext, initializer: reflect.Initializer, level: number): Code { + return Code.concatAll( + 'new ', + typeReference(initializer.parentType), + '(', + parameterList(context, initializer.parameters, level), + ')', + ); +} + +function parameterList(context: ExampleContext, parameters: reflect.Parameter[], level: number) { + const length = parameters.length; + return Code.concatAll( + ...parameters.map((p, i) => { + if (length - 1 === i) { + return exampleValueForParameter(context, p, i, level); + } else { + return exampleValueForParameter(context, p, i, level).append(', '); + } + }), + ); +} + +function generateStaticFactoryMethodExample( + context: ExampleContext, + staticFactoryMethod: reflect.Method, + level: number, +) { + return Code.concatAll( + typeReference(staticFactoryMethod.parentType), + '.', + staticFactoryMethod.name, + '(', + parameterList(context, staticFactoryMethod.parameters, level), + ')', + ); +} + +function generateStaticFactoryPropertyExample(staticFactoryProperty: reflect.Property) { + return Code.concatAll( + typeReference(staticFactoryProperty.parentType), + '.', + staticFactoryProperty.name, + ); +} + +/** + * Generate an example value of the given parameter. + */ +function exampleValueForParameter(context: ExampleContext, param: reflect.Parameter, position: number, level: number): Code { + if (param.name === 'scope' && position === 0) { + return new Code('this'); + } + + if (param.name === 'id' && position === 1) { + return new Code(`'My${param.parentType.name}'`); + } + if (param.optional) { + return new Code('/* all optional props */ ').append(exampleValue(context, param.type, param.name, level)); + } + return exampleValue(context, param.type, param.name, level); +} + +/** + * Generate an example value of the given type. + */ +function exampleValue(context: ExampleContext, typeRef: reflect.TypeReference, name: string, level: number): Code { + // Process primitive types, base case + if (typeRef.primitive !== undefined) { + switch (typeRef.primitive) { + case spec.PrimitiveType.String: + return new Code(`'${name}'`); + case spec.PrimitiveType.Number: + return new Code('123'); + case spec.PrimitiveType.Boolean: + return new Code('false'); + case spec.PrimitiveType.Date: + return new Code('new Date()'); + default: + return new Code(name, [new AnyAssumption(name)]); + } + } + + // Just pick the first type if it is a union type + if (typeRef.unionOfTypes !== undefined) { + const newType = getBaseUnionType(typeRef.unionOfTypes); + return exampleValue(context, newType, name, level); + } + // If its a collection create a collection of one element + if (typeRef.arrayOfType !== undefined) { + return Code.concatAll('[', exampleValue(context, typeRef.arrayOfType, name, level), ']'); + } + + if (typeRef.mapOfType !== undefined) { + return exampleValueForMap(context, typeRef.mapOfType, name, level); + } + + if (typeRef.fqn) { + const fqn = typeRef.fqn; + // See if we have information on this type in the assembly + const newType = context.typeSystem.findFqn(fqn); + + if (fqn in SPECIAL_TYPE_EXAMPLES) { + return new Code(SPECIAL_TYPE_EXAMPLES[fqn], [new Import(newType)]); + } + + if (newType.isEnumType()) { + return Code.concatAll( + typeReference(newType), + '.', + newType.members[0].name); + } + + // If this is struct and we're not already rendering it (recursion breaker), expand + if (isStructType(newType)) { + if (context.rendered.has(newType.fqn)) { + // Recursion breaker -- if we go by the default behavior end up saying something like: + // + // const myProperty = { + // stringProp: 'stringProp', + // deepProp: myProperty, // <-- value recursion! + // }; + // + // Which TypeScript's type analyzer can't automatically derive a type for. We need to + // annotate SOMETHING. A simple fix is to use a different variable name so the value + // isn't self-recursive. + + return addAssumedVariableDeclaration(newType, '_'); + } + + + context.rendered.add(newType.fqn); + const ret = exampleValueForStruct(context, newType, level); + context.rendered.delete(newType.fqn); + return ret; + } + + // For all other types we will assume you already have a variable of the appropriate type. + return addAssumedVariableDeclaration(newType); + } + + throw new Error('If this happens, then reflect.typeRefernce must have a new value'); +} + +function getBaseUnionType(types: reflect.TypeReference[]): reflect.TypeReference { + for (const newType of types) { + if (newType.fqn?.endsWith('.IResolvable')) { + continue; + } + return newType; + } + return types[0]; +} + +/** + * Add an assumption and import for a variable that will be declared as a constant. + * If the variable is an IXxx Interface, guess a possible implementation of that interface + * by checking if stripping the I results in an Xxx type that extends IXxx. + */ +function addAssumedVariableDeclaration(type: reflect.Type, suffix = ''): Code { + let newType = type; + if (type.isInterfaceType() && !type.datatype) { + // guess corresponding non-interface type if possible + newType = guessConcreteType(type); + } + const variableName = escapeIdentifier(lowercaseFirstLetter(stripLeadingI(newType.name))) + suffix; + return new Code(variableName, [new Assumption(newType, variableName), new Import(newType)]); +} + +/** + * Remove a leading 'I' from a name, if it's being followed by another capital letter + */ +function stripLeadingI(name: string) { + return name.replace(/^I([A-Z])/, '$1'); +} + +/** + * This function tries to guess the corresponding type to an IXxx Interface. + * If it does not find that this type exists, it will return the original type. + */ +function guessConcreteType(type: reflect.InterfaceType): reflect.Type { + const concreteClassName = type.name.substr(1); // Strip off the leading 'I' + + const parts = type.fqn.split('.'); + parts[parts.length - 1] = concreteClassName; + const newFqn = parts.join('.'); + + const newType = type.system.tryFindFqn(newFqn); + return newType && newType.extends(type) ? newType : type; +} + +/** + * Helper function to generate an example value for a map. + */ +function exampleValueForMap(context: ExampleContext, map: reflect.TypeReference, name: string, level: number): Code { + return Code.concatAll( + '{\n', + new Code(`${tab(level + 1)}${name}Key: `).append(exampleValue(context, map, name, level + 1)).append(',\n'), + `${tab(level)}}`, + ); +} + +/** + * Helper function to generate an example value for a struct. + */ +function exampleValueForStruct(context: ExampleContext, struct: reflect.InterfaceType, level: number): Code { + if (struct.allProperties.length === 0) { + return new Code('{ }'); + } + + const properties = [...struct.allProperties]; // Make a copy that we can sort + sortBy(properties, (p) => [p.optional ? 1 : 0, p.name]); + + const renderedProperties = properties.map((p) => + Code.concatAll( + `${tab(level + 1)}${p.name}: `, + exampleValue(context, p.type, p.name, level + 1), + ',\n', + ), + ); + + // Add an empty line between required and optional properties + for (let i = 0; i < properties.length - 1; i++) { + if (properties[i].optional !== properties[i + 1].optional) { + renderedProperties.splice(i + 1, 0, new Code(`\n${tab(level+1)}// the properties below are optional\n`)); + break; + } + } + + return Code.concatAll( + '{\n', + ...renderedProperties, + `${tab(level)}}`, + ); +} + +/** + * Returns whether the given type represents a struct + */ +function isStructType(type: reflect.Type): type is reflect.InterfaceType { + return type.isInterfaceType() && type.datatype; +} + +function extendsRef(subtype: reflect.ClassType, supertypeRef: reflect.TypeReference): boolean { + if (!supertypeRef.fqn) { + // Not a named type, can never extend + return false; + } + + const superType = subtype.system.findFqn(supertypeRef.fqn); + return subtype.extends(superType); +} + +function lowercaseFirstLetter(str: string): string { + return str.charAt(0).toLowerCase() + str.slice(1); +} + +function tab(level: number): string { + return ' '.repeat(level); +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/index.ts b/tools/@aws-cdk/generate-examples/lib/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tools/@aws-cdk/generate-examples/lib/module-utils.ts b/tools/@aws-cdk/generate-examples/lib/module-utils.ts new file mode 100644 index 0000000000000..e3151ab51c88e --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/module-utils.ts @@ -0,0 +1,115 @@ +import * as reflect from 'jsii-reflect'; +import { Code } from './code'; +import { Import } from './declaration'; + +/** + * Customary module import names that differ from what would be automatically generated. + */ +const SPECIAL_PACKAGE_ROOT_IMPORT_NAMES: Record = { + 'aws-cdk-lib': 'cdk', + '@aws-cdk/core': 'cdk', + '@aws-cdk/aws-applicationautoscaling': 'appscaling', + '@aws-cdk/aws-elasticloadbalancing': 'elb', + '@aws-cdk/aws-elasticloadbalancingv2': 'elbv2', +}; + +const SPECIAL_NAMESPACE_IMPORT_NAMES: Record = { + 'aws-cdk-lib.aws_applicationautoscaling': 'appscaling', + 'aws-cdk-lib.aws_elasticloadbalancing': 'elb', + 'aws-cdk-lib.aws_elasticloadbalancingv2': 'elbv2', +}; + +interface ImportedModule { + readonly importName: string; + readonly moduleName: string; + readonly submoduleName?: string; +} + +/** + * Parses the given type for human-readable information on the module + * that the type is from. Meant to serve as a single source of truth + * for parsing the type for module information. + */ +export function module(type: reflect.Type): ImportedModule { + const parts = analyzeTypeName(type); + + if (parts.submoduleNameParts.length > 0) { + const specialNameKey = [parts.assemblyName, ...parts.submoduleNameParts].join('.'); + + const importName = SPECIAL_NAMESPACE_IMPORT_NAMES[specialNameKey] ?? parts.submoduleNameParts.join('.'); + return { + importName: escapeIdentifier(importName.replace(/^aws_/g, '').replace(/[^a-z0-9_]/g, '_')), + moduleName: parts.assemblyName, + submoduleName: parts.submoduleNameParts.join('.'), + }; + } + + // Split '@aws-cdk/aws-s3' into ['@aws-cdk', 'aws-s3'] + const slashParts = type.assembly.name.split('/'); + const nonNamespacedPart = SPECIAL_PACKAGE_ROOT_IMPORT_NAMES[parts.assemblyName] ?? slashParts[1] ?? slashParts[0]; + return { + importName: escapeIdentifier(nonNamespacedPart.replace(/^aws-/g, '').replace(/[^a-z0-9_]/g, '_')), + moduleName: type.assembly.name, + }; +} + +/** + * Namespaced name inside a module + */ +export function typeNamespacedName(type: reflect.Type): string { + const parts = analyzeTypeName(type); + + return [ + ...parts.namespaceNameParts, + parts.simpleName, + ].join('.'); +} + +const KEYWORDS = ['function', 'default']; + +export function escapeIdentifier(ident: string): string { + return KEYWORDS.includes(ident) ? `${ident}_` : ident; +} + +export function moduleReference(type: reflect.Type) { + const imp = new Import(type); + return new Code(imp.importName, [imp]); +} + +export function typeReference(type: reflect.Type) { + return Code.concatAll( + moduleReference(type), + '.', + typeNamespacedName(type)); +} + +/** + * A type name consists of 4 parts which are all treated differently + */ +interface TypeNameParts { + readonly assemblyName: string; + readonly submoduleNameParts: string[]; + readonly namespaceNameParts: string[]; + readonly simpleName: string; +} + +function analyzeTypeName(type: reflect.Type): TypeNameParts { + // Need to divide the namespace into submodule and non-submodule + + // For type 'asm.b.c.d.Type' contains ['asm', 'b', 'c', 'd'] + const nsParts = type.fqn.split('.').slice(0, -1); + + const moduleFqns = new Set(type.assembly.allSubmodules.map((s) => s.fqn)); + + let split = nsParts.length; + while (split > 1 && !moduleFqns.has(nsParts.slice(0, split).join('.'))) { + split--; + } + + return { + assemblyName: type.assembly.name, + submoduleNameParts: nsParts.slice(1, split), + namespaceNameParts: nsParts.slice(split, nsParts.length), + simpleName: type.name, + }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/utils.ts b/tools/@aws-cdk/generate-examples/lib/utils.ts new file mode 100644 index 0000000000000..526664f645a22 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/utils.ts @@ -0,0 +1,28 @@ +export function sortBy(xs: A[], keyFn: (x: A) => Array) { + return xs.sort((a, b) => { + const aKey = keyFn(a); + const bKey = keyFn(b); + + for (let i = 0; i < Math.min(aKey.length, bKey.length); i++) { + // Compare aKey[i] to bKey[i] + const av = aKey[i]; + const bv = bKey[i]; + + if (av === bv) { continue; } + + if (typeof av !== typeof bv) { + throw new Error(`Type of sort key ${JSON.stringify(aKey)} not same as ${JSON.stringify(bKey)}`); + } + + if (typeof av === 'number' && typeof bv === 'number') { + return av - bv; + } + + if (typeof av === 'string' && typeof bv === 'string') { + return av.localeCompare(bv); + } + } + + return aKey.length - bKey.length; + }); +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/package.json b/tools/@aws-cdk/generate-examples/package.json new file mode 100644 index 0000000000000..ebb81537ffaf3 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/package.json @@ -0,0 +1,51 @@ +{ + "name": "@aws-cdk/generate-examples", + "version": "0.0.0", + "private": true, + "description": "Generate missing examples", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "git://github.com/aws/aws-cdk" + }, + "pkglint": { + "ignore": true + }, + "bin": { + "generate-examples": "bin/generate-examples" + }, + "scripts": { + "build": "tsc -b && chmod +x bin/generate-examples", + "test": "jest", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build && npm test", + "watch": "tsc -b -w", + "lint": "tsc -b && eslint . --ext=.ts" + }, + "keywords": [ + "aws", + "cdk" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.24", + "@types/yargs": "^15.0.14", + "jest": "^26.6.3", + "typescript": "~3.9.10" + }, + "nozem": { + "ostools": ["chmod", "cp"] + }, + "dependencies": { + "@jsii/spec": "1.45.0", + "jsii-reflect": "1.45.0", + "jsii-rosetta": "1.45.0", + "fs-extra": "^9.1.0", + "yargs": "^16.2.0" + } +} diff --git a/tools/@aws-cdk/generate-examples/test/code.test.ts b/tools/@aws-cdk/generate-examples/test/code.test.ts new file mode 100644 index 0000000000000..114cc8858faa1 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/code.test.ts @@ -0,0 +1,51 @@ +import { Code } from '../lib/code'; +import { AnyAssumption } from '../lib/declaration'; + +test('created successfully', () => { + const code = new Code('hello world', [new AnyAssumption('from Mars')]); + + expect(code.code).toEqual('hello world'); + expect(code.declarations.length).toEqual(1); + expect(code.declarations[0]).toBeInstanceOf(AnyAssumption); +}); + +test('can append', () => { + const code = new Code('hello ', [new AnyAssumption('from Mars')]) + .append('world') + .append(new Code('.', [new AnyAssumption('from Jupiter')])); + + expect(code.code).toEqual('hello world.'); + expect(code.declarations.length).toEqual(2); +}); + +test('concatAll works as expected', () => { + const code = Code.concatAll( + 'hello', + new Code(' world', [new AnyAssumption('from Mars')]), + ); + + expect(code.code).toEqual('hello world'); + expect(code.declarations.length).toEqual(1); +}); + +describe('code declarations on toString', () => { + test('deduplicated', () => { + const code = new Code('', [ + new AnyAssumption('duplicate'), + new AnyAssumption('duplicate'), + new AnyAssumption('unique'), + ]); + + expect(code.toString()).toEqual('declare const duplicate: any;\ndeclare const unique: any;\n\n'); + }); + + test('sorted', () => { + const code = new Code('', [ + new AnyAssumption('third'), + new AnyAssumption('second'), + new AnyAssumption('first'), + ]); + + expect(code.toString()).toEqual('declare const first: any;\ndeclare const second: any;\ndeclare const third: any;\n\n'); + }); +}); diff --git a/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts b/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts new file mode 100644 index 0000000000000..963b98bdc586b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts @@ -0,0 +1,60 @@ +import * as path from 'path'; + +import { LanguageTablet, TargetLanguage } from 'jsii-rosetta'; +import { generateMissingExamples } from '../lib/generate-missing-examples'; +import { DUMMY_ASSEMBLY_TARGETS, AssemblyFixture } from './testutil'; + +test('test end-to-end and translation to Python', async () => { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export interface MyClassProps { + readonly someString: string; + readonly someNumber: number; + } + + export class MyClass { + constructor(value: string, props: MyClassProps) { + Array.isArray(value); + Array.isArray(props); + } + } + `, + }, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + try { + const outputTablet = path.join(assembly.directory, 'test.tbl.json'); + + await generateMissingExamples([ + assembly.directory, + ], { + directory: assembly.directory, + appendToTablet: outputTablet, + }); + + const tablet = await LanguageTablet.fromFile(outputTablet); + + const pythons = tablet.snippetKeys + .map((key) => tablet.tryGetSnippet(key)!) + .map((snip) => snip.get(TargetLanguage.PYTHON)?.source); + + const classInstantiation = pythons.find((s) => s?.includes('= my_assembly.MyClass(')); + + expect(classInstantiation).toEqual([ + '# The code below shows an example of how to instantiate this type.', + '# The values are placeholders you should change.', + 'import my_assembly as my_assembly', + '', + 'my_class = my_assembly.MyClass(\"value\",', + ' some_number=123,', + ' some_string=\"someString\"', + ')', + ].join('\n')); + } finally { + await assembly.cleanup(); + } +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/generate.test.ts b/tools/@aws-cdk/generate-examples/test/generate.test.ts new file mode 100644 index 0000000000000..b15e3e8a31d04 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/generate.test.ts @@ -0,0 +1,323 @@ +import * as reflect from 'jsii-reflect'; + +import { generateAssignmentStatement } from '../lib/generate'; +import { AssemblyFixture, DUMMY_ASSEMBLY_TARGETS, MultipleSources } from './testutil'; + +describe('generateClassAssignment ', () => { + test('generates example for class with static methods', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public static firstMethod() { return new ClassA(); } + public static secondMethod() { return new ClassA(); } + public static thirdMethod() { return new ClassA(); } + private constructor() {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = my_assembly.ClassA.firstMethod();', + ], + }), + ); + + test('generates example for class with static properties', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public static readonly FIRST_PROPERTY = new ClassA(); + public static readonly SECOND_PROPERTY = new ClassA(); + public static readonly THIRD_PROPERTY = new ClassA(); + private constructor() {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = my_assembly.ClassA.FIRST_PROPERTY;', + ], + }), + ); + + test('generates example for class instantiation', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly a: string, public readonly b: string) {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(\'a\', \'b\');', + ], + }), + ); + + test('generates example for more complicated class instantiation', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps) {} + } + export interface ClassAProps { + readonly prop1: number, + readonly prop2: IProperty, + readonly prop3: string[], + readonly prop4: number | string, + readonly prop5?: any, + readonly prop6?: boolean, + readonly prop7?: { [key: string]: string }, + } + export interface IProperty { + readonly prop: string, + } + export class Property implements IProperty { + readonly prop: string; + public constructor() { this.prop = 'a'; } + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'declare const prop5: any;', + 'declare const property: my_assembly.Property;', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', {', + ' prop1: 123,', + ' prop2: property,', + ' prop3: [\'prop3\'],', + ' prop4: \'prop4\',', + '', + ' // the properties below are optional', + ' prop5: prop5,', + ' prop6: false,', + ' prop7: {', + ' prop7Key: \'prop7\',', + ' },', + '});', + ], + }), + ); + + test('returns undefined if class has no statics and private initializer', async () => { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export class ClassA { + private constructor() {} + }`, + }, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + + const type = ts.findClass('my_assembly.ClassA'); + expect(generateAssignmentStatement(type)).toBeUndefined(); + + await assembly.cleanup(); + }); + + test('optional properties are added in the correct spot', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps) {} + } + export interface ClassAProps { + readonly prop1: number, + readonly prop2?: number, + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', {', + ' prop1: 123,', + '', + ' // the properties below are optional', + ' prop2: 123,', + '});', + ], + }), + ); + + test( + 'comment added when all properties are optional', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps = {}) {} + } + export interface ClassAProps { + readonly prop1?: number, + readonly prop2?: number, + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', /* all optional props */ {', + ' prop1: 123,', + ' prop2: 123,', + '});', + ], + }), + ); +}); + +test( + 'generate example for struct', + expectedDocTest({ + sources: { + 'index.ts': ` + export interface SomeStruct { + readonly required: string; + readonly optional?: number; + } + `, + }, + typeName: 'SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeStruct = {', + ' required: \'required\',', + '', + ' // the properties below are optional', + ' optional: 123,', + '};', + ], + }), +); + +test( + 'rendering an enum value', + expectedDocTest({ + sources: { + 'index.ts': ` + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + `, + }, + typeName: 'SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeStruct = {', + ' someEnum: my_assembly.MyEnum.VALUE1,', + '};', + ], + }), +); + +test('rendering types in namespaces', expectedDocTest({ + sources: { + // This merges a class and a namespace (making the struct appear + // namespaced inside the class -- we do this for L1 structs) + 'index.ts': ` + export class SomeClass { + constructor(props: SomeClass.SomeStruct) { + Array.isArray(props); + } + } + + export namespace SomeClass { + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + } + `, + }, + typeName: 'SomeClass.SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeClass.SomeStruct = {', + ' someEnum: my_assembly.SomeClass.MyEnum.VALUE1,', + '};', + ], +})); + +test('rendering types in submodules', expectedDocTest({ + sources: { + 'index.ts': 'export * as sub from \'./other\';', + 'other.ts': ` + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + `, + }, + typeName: 'sub.SomeStruct', + expected: [ + 'import { sub } from \'my_assembly\';', + '', + 'const someStruct: sub.SomeStruct = {', + ' someEnum: sub.MyEnum.VALUE1,', + '};', + ], +})); + +interface DocTest { + readonly sources: MultipleSources; + readonly typeName: string; + readonly expected: string[]; +} + +function expectedDocTest(testParams: DocTest) { + return async () => { + const assembly = await AssemblyFixture.fromSource( + testParams.sources, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + try { + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + + const type = ts.findFqn(`my_assembly.${testParams.typeName}`); + if (!type.isClassType() && !type.isInterfaceType()) { + throw new Error('Expecting class or interface'); + } + expect(generateAssignmentStatement(type)?.toString()?.split('\n')).toEqual(testParams.expected); + } finally { + await assembly.cleanup(); + } + }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/module-utils.test.ts b/tools/@aws-cdk/generate-examples/test/module-utils.test.ts new file mode 100644 index 0000000000000..ecb9bea9326ce --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/module-utils.test.ts @@ -0,0 +1,161 @@ +import * as reflect from 'jsii-reflect'; + +import { module } from '../lib/module-utils'; +import { AssemblyFixture, DUMMY_ASSEMBLY_TARGETS } from './testutil'; + +describe('v1 names are correct: ', () => { + test('core', async () => { + // GIVEN + const mod = '@aws-cdk/core'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('cdk'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('special package root', async () => { + // GIVEN + const mod = '@aws-cdk/aws-elasticloadbalancingv2'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('elbv2'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('with "aws-"', async () => { + // GIVEN + const mod = '@aws-cdk/aws-s3'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('s3'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('without "aws-"', async () => { + // GIVEN + const mod = '@aws-cdk/pipelines'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('pipelines'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); +}); + +describe('v2 names are correct: ', () => { + test('core', async () => { + // GIVEN + const mod = 'aws-cdk-lib'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('cdk'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('special namespace', async () => { + // GIVEN + const mod = 'aws-cdk-lib/aws_elasticloadbalancingv2'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module( + ts.findClass('aws-cdk-lib.aws_elasticloadbalancingv2.ClassB'), + )).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'aws_elasticloadbalancingv2', + importName: 'elbv2', + }); + + await assembly.cleanup(); + }); + + test('with "aws_"', async () => { + // GIVEN + const mod = 'aws-cdk-lib/aws_s3'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module(ts.findClass('aws-cdk-lib.aws_s3.ClassB'))).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'aws_s3', + importName: 's3', + }); + + await assembly.cleanup(); + }); + + test('without "aws_"', async () => { + // GIVEN + const mod = 'aws-cdk-lib/pipelines'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module(ts.findClass('aws-cdk-lib.pipelines.ClassB'))).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'pipelines', + importName: 'pipelines', + }); + + await assembly.cleanup(); + }); +}); + +async function v1BuildAssemblyHelper(name: string) { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export class ClassA { } + `, + }, + { + name, + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + return { ts, assembly }; +} + +async function v2BuildAssemblyHelper(name: string) { + const [assemblyName, submoduleName] = name.split('/'); + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export * as ${submoduleName ?? 'dummy'} from "./submod"; + export class ClassA { } + `, + 'submod.ts': ` + export class ClassB { } + `, + }, + { + name: assemblyName, + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + return { ts, assembly }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/testutil.ts b/tools/@aws-cdk/generate-examples/test/testutil.ts new file mode 100644 index 0000000000000..f4cee0fe3bd5f --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/testutil.ts @@ -0,0 +1,65 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { PackageInfo, compileJsiiForTest } from 'jsii'; + +export type MultipleSources = { [key: string]: string; 'index.ts': string }; + +export class AssemblyFixture { + public static async fromSource( + source: string | MultipleSources, + packageInfo: Partial & { name: string }, + ) { + const { assembly, files } = await compileJsiiForTest(source, (pi) => { + Object.assign(pi, packageInfo); + }); + + // The following is silly, however: the helper has compiled the given source to + // an assembly, and output files, and then removed their traces from disk. + // But for the purposes of Rosetta, we need those files back on disk. So write + // them back out again >_< + // + // In fact we will drop them in 'node_modules/' so they can be imported + // as if they were installed. + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jsii-rosetta')); + const modDir = path.join(tmpDir, 'node_modules', packageInfo.name); + await fs.ensureDir(modDir); + + await fs.writeJSON(path.join(modDir, '.jsii'), assembly); + await fs.writeJSON(path.join(modDir, 'package.json'), { + name: packageInfo.name, + jsii: packageInfo.jsii, + }); + for (const [fileName, fileContents] of Object.entries(files)) { + // eslint-disable-next-line no-await-in-loop + await fs.writeFile(path.join(modDir, fileName), fileContents); + } + + return new AssemblyFixture(modDir); + } + + private constructor(public readonly directory: string) {} + + public async cleanup() { + await fs.remove(this.directory); + } +} + +export const DUMMY_ASSEMBLY_TARGETS = { + dotnet: { + namespace: 'Example.Test.Demo', + packageId: 'Example.Test.Demo', + }, + go: { moduleName: 'example.test/demo' }, + java: { + maven: { + groupId: 'example.test', + artifactId: 'demo', + }, + package: 'example.test.demo', + }, + python: { + distName: 'example-test.demo', + module: 'example_test_demo', + }, +}; diff --git a/tools/@aws-cdk/generate-examples/test/utils.test.ts b/tools/@aws-cdk/generate-examples/test/utils.test.ts new file mode 100644 index 0000000000000..51913f88869c5 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/utils.test.ts @@ -0,0 +1,7 @@ +import { sortBy } from '../lib/utils'; + +test('sortBy sorts successfully', () => { + const alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + sortBy(alist, (i) => [i*-1]); + expect(alist).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/tsconfig.json b/tools/@aws-cdk/generate-examples/tsconfig.json new file mode 100644 index 0000000000000..c321dc85b07b0 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "lib": ["es2018"], + "strict": true, + "alwaysStrict": true, + "declaration": true, + "inlineSourceMap": true, + "inlineSources": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "composite": true, + "incremental": true, + "experimentalDecorators": true + }, + "include": ["**/*.ts"] +} \ No newline at end of file diff --git a/tools/@aws-cdk/individual-pkg-gen/package.json b/tools/@aws-cdk/individual-pkg-gen/package.json index 4903c75ea0393..68e10ccfca040 100644 --- a/tools/@aws-cdk/individual-pkg-gen/package.json +++ b/tools/@aws-cdk/individual-pkg-gen/package.json @@ -29,7 +29,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/jest": "^26.0.24" + "@types/jest": "^27.0.2" }, "dependencies": { "aws-cdk-migration": "0.0.0", diff --git a/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts b/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts index ece10f8262a86..1886db3041836 100644 --- a/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts +++ b/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts @@ -3,6 +3,23 @@ import * as awsCdkMigration from 'aws-cdk-migration'; import * as fs from 'fs-extra'; // eslint-disable-next-line @typescript-eslint/no-require-imports const lerna_project = require('@lerna/project'); +// eslint-disable-next-line @typescript-eslint/no-require-imports +const ver = require('../../../scripts/resolve-version'); + +const CFN_STABILITY_BANNER = `${[ + '![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]) are always stable and safe to use.', + '>', + '> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib', +].join('\n')}\n\n`; + +const FEATURE_CFN_STABILITY_BANNER = `> **CFN Resources:** All classes with the \`Cfn\` prefix in this module ([CFN Resources]) are always +> stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib\n\n\n\n`; + +const FEATURE_CFN_STABILITY_LINE = /CFN Resources\s+\| !\[Stable]\(https:\/\/img\.shields\.io\/badge\/stable-success\.svg\?style=for-the-badge\)\n/gm; /** * @aws-cdk/ scoped packages that may be present in devDependencies and need to @@ -107,6 +124,13 @@ function transformPackages(): void { packageUnscopedName: `${pkg.name.substring('@aws-cdk/'.length)}`, }); fs.outputFileSync(destination, sourceCodeOutput); + } else if (sourceFileName === 'README.md') { + // Remove the stability banner for Cfn constructs, since they don't exist in the alpha modules + let sourceCode = fs.readFileSync(source).toString(); + [CFN_STABILITY_BANNER, FEATURE_CFN_STABILITY_BANNER, FEATURE_CFN_STABILITY_LINE].forEach(pattern => { + sourceCode = sourceCode.replace(pattern, ''); + }); + fs.outputFileSync(destination, sourceCode); } else { const stat = fs.statSync(source); if (stat.isDirectory()) { @@ -128,6 +152,9 @@ function transformPackageJson(pkg: any, source: string, destination: string, alp const pkgUnscopedName = `${pkg.name.substring('@aws-cdk/'.length)}`; packageJson.name += '-alpha'; + if (ver.alphaVersion) { + packageJson.version = ver.alphaVersion; + } packageJson.repository.directory = `packages/individual-packages/${pkgUnscopedName}`; // All individual packages are public by default on v1, and private by default on v2. @@ -141,6 +168,16 @@ function transformPackageJson(pkg: any, source: string, destination: string, alp } } + // disable the 'cloudformation' directive since alpha modules don't contain L1 resources. + const cdkBuild = packageJson['cdk-build']; + if (cdkBuild) { + delete cdkBuild.cloudformation; + packageJson['cdk-build'] = cdkBuild; + } + + // disable `cfn2ts` script since alpha modules don't contain L1 resources. + delete packageJson.scripts.cfn2ts; + // disable awslint (some rules are hard-coded to @aws-cdk/core) packageJson.awslint = { exclude: ['*:*'], @@ -170,9 +207,11 @@ function transformPackageJson(pkg: any, source: string, destination: string, alp jsiiTargets.python.distName += '-alpha'; jsiiTargets.python.module += '_alpha'; // Typically, only our top-level packages have a Go target. - // moduleName is needed; packageName will be automatically derived by from the package name. + // packageName has unusable chars and redundant 'aws' stripped. + // This generates names like 'awscdkfoobaralpha' (rather than 'awscdkawsfoobaralpha'). jsiiTargets.go = { moduleName: 'github.com/aws/aws-cdk-go', + packageName: packageJson.name.replace('/aws-', '').replace(/[^a-z0-9.]/gi, '').toLowerCase(), }; const finalPackageJson = transformPackageJsonDependencies(packageJson, pkg, alphaPackages); @@ -201,7 +240,7 @@ function transformPackageJsonDependencies(packageJson: any, pkg: any, alphaPacka break; default: if (alphaPackages[dependency]) { - alphaDependencies[alphaPackages[dependency]] = pkg.version; + alphaDependencies[alphaPackages[dependency]] = packageJson.version; } else if (v1BundledDependencies.indexOf(dependency) !== -1) { // ...other than third-party dependencies, which are in bundledDependencies bundledDependencies[dependency] = packageJson.dependencies[dependency]; @@ -221,7 +260,7 @@ function transformPackageJsonDependencies(packageJson: any, pkg: any, alphaPacka break; default: if (alphaPackages[v1DevDependency]) { - alphaDevDependencies[alphaPackages[v1DevDependency]] = pkg.version; + alphaDevDependencies[alphaPackages[v1DevDependency]] = packageJson.version; } else if (!v1DevDependency.startsWith('@aws-cdk/') || isRequiredTool(v1DevDependency)) { devDependencies[v1DevDependency] = packageJson.devDependencies[v1DevDependency]; } diff --git a/tools/@aws-cdk/pkglint/lib/rules.ts b/tools/@aws-cdk/pkglint/lib/rules.ts index 140674492f0cc..dab684878d98f 100644 --- a/tools/@aws-cdk/pkglint/lib/rules.ts +++ b/tools/@aws-cdk/pkglint/lib/rules.ts @@ -14,9 +14,8 @@ import { monoRepoRoot, } from './util'; -const AWS_SERVICE_NAMES = require('./aws-service-official-names.json'); // eslint-disable-line @typescript-eslint/no-require-imports - const PKGLINT_VERSION = require('../package.json').version; // eslint-disable-line @typescript-eslint/no-require-imports +const AWS_SERVICE_NAMES = require('./aws-service-official-names.json'); // eslint-disable-line @typescript-eslint/no-require-imports /** * Verify that the package name matches the directory name @@ -831,25 +830,33 @@ function cdkModuleName(name: string) { const isCdkPkg = name === '@aws-cdk/core'; const isLegacyCdkPkg = name === '@aws-cdk/cdk'; - name = name.replace(/^aws-cdk-/, ''); - name = name.replace(/^@aws-cdk\//, ''); + let suffix = name; + suffix = suffix.replace(/^aws-cdk-/, ''); + suffix = suffix.replace(/^@aws-cdk\//, ''); - const dotnetSuffix = name.split('-') + const dotnetSuffix = suffix.split('-') .map(s => s === 'aws' ? 'AWS' : caseUtils.pascal(s)) .join('.'); - const pythonName = name.replace(/^@/g, '').replace(/\//g, '.').split('.').map(caseUtils.kebab).join('.'); + const pythonName = suffix.replace(/^@/g, '').replace(/\//g, '.').split('.').map(caseUtils.kebab).join('.'); + + // list of packages with special-cased Maven ArtifactId. + const mavenIdMap: Record = { + '@aws-cdk/core': 'core', + '@aws-cdk/cdk': 'cdk', + '@aws-cdk/assertions': 'assertions', + '@aws-cdk/assertions-alpha': 'assertions-alpha', + }; + /* eslint-disable @typescript-eslint/indent */ + const mavenArtifactId = + name in mavenIdMap ? mavenIdMap[name] : + (suffix.startsWith('aws-') || suffix.startsWith('alexa-')) ? suffix.replace(/aws-/, '') : + suffix.startsWith('cdk-') ? suffix : `cdk-${suffix}`; + /* eslint-enable @typescript-eslint/indent */ return { - javaPackage: `software.amazon.awscdk${isLegacyCdkPkg ? '' : `.${name.replace(/aws-/, 'services-').replace(/-/g, '.')}`}`, - mavenArtifactId: - isLegacyCdkPkg - ? 'cdk' - : (isCdkPkg - ? 'core' - : (name.startsWith('aws-') || name.startsWith('alexa-') - ? name.replace(/aws-/, '') - : (name.startsWith('cdk-') ? name : `cdk-${name}`))), + javaPackage: `software.amazon.awscdk${isLegacyCdkPkg ? '' : `.${suffix.replace(/aws-/, 'services-').replace(/-/g, '.')}`}`, + mavenArtifactId, dotnetNamespace: `Amazon.CDK${isCdkPkg ? '' : `.${dotnetSuffix}`}`, python: { distName: `aws-cdk.${pythonName}`, @@ -1122,7 +1129,11 @@ export class MustHaveNodeEnginesDeclaration extends ValidationRule { public readonly name = 'package-info/engines'; public validate(pkg: PackageJson): void { - expectJSON(this.name, pkg, 'engines.node', '>= 10.13.0 <13 || >=13.7.0'); + if (cdkMajorVersion() === 2) { + expectJSON(this.name, pkg, 'engines.node', '>= 14.15.0'); + } else { + expectJSON(this.name, pkg, 'engines.node', '>= 10.13.0 <13 || >=13.7.0'); + } } } @@ -1642,6 +1653,7 @@ export class NoExperimentalDependents extends ValidationRule { ['@aws-cdk/aws-apigatewayv2-authorizers', ['@aws-cdk/aws-apigatewayv2']], ['@aws-cdk/aws-events-targets', ['@aws-cdk/aws-kinesisfirehose']], ['@aws-cdk/aws-kinesisfirehose-destinations', ['@aws-cdk/aws-kinesisfirehose']], + ['@aws-cdk/aws-iot-actions', ['@aws-cdk/aws-iot', '@aws-cdk/aws-kinesisfirehose']], ]); private readonly excludedModules = ['@aws-cdk/cloudformation-include']; diff --git a/tools/@aws-cdk/pkglint/package.json b/tools/@aws-cdk/pkglint/package.json index b87f7f7caa817..7bb152feb4287 100644 --- a/tools/@aws-cdk/pkglint/package.json +++ b/tools/@aws-cdk/pkglint/package.json @@ -39,18 +39,18 @@ "devDependencies": { "@aws-cdk/eslint-plugin": "0.0.0", "@types/fs-extra": "^8.1.2", - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", - "@types/semver": "^7.3.8", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", + "@types/semver": "^7.3.9", "@types/yargs": "^15.0.14", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.5.2", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^24.7.0", "eslint": "^7.32.0", - "jest": "^26.6.3", + "jest": "^27.3.1", "typescript": "~3.9.10" }, "nozem": { diff --git a/tools/@aws-cdk/prlint/package.json b/tools/@aws-cdk/prlint/package.json index 1c45caf95233d..a493c31a3921a 100644 --- a/tools/@aws-cdk/prlint/package.json +++ b/tools/@aws-cdk/prlint/package.json @@ -13,16 +13,16 @@ "dependencies": { "@actions/core": "^1.6.0", "@actions/github": "^2.2.0", - "conventional-commits-parser": "^3.2.2", + "conventional-commits-parser": "^3.2.3", "fs-extra": "^9.1.0", "github-api": "^3.4.0", "glob": "^7.2.0" }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/glob": "^7.1.4", - "@types/jest": "^26.0.24", - "jest": "^26.6.3", + "@types/glob": "^7.2.0", + "@types/jest": "^27.0.2", + "jest": "^27.3.1", "make-runnable": "^1.3.10", "typescript": "~3.9.10" }, diff --git a/tools/@aws-cdk/ubergen/bin/ubergen.ts b/tools/@aws-cdk/ubergen/bin/ubergen.ts index 885a7e66f6777..69f146a1067e6 100644 --- a/tools/@aws-cdk/ubergen/bin/ubergen.ts +++ b/tools/@aws-cdk/ubergen/bin/ubergen.ts @@ -2,22 +2,28 @@ import * as console from 'console'; import * as path from 'path'; import * as process from 'process'; import cfn2ts from '@aws-cdk/cfn2ts'; +import * as cfnspec from '@aws-cdk/cfnspec'; import * as fs from 'fs-extra'; import * as ts from 'typescript'; -const LIB_ROOT = path.resolve(process.cwd(), 'lib'); +// The directory where our 'package.json' lives +const MONOPACKAGE_ROOT = process.cwd(); + +// The directory where we're going to collect all the libraries. Currently +// purposely the same as the monopackage root so that our two import styles +// resolve to the same files. +const LIB_ROOT = MONOPACKAGE_ROOT; + const ROOT_PATH = findWorkspacePath(); -const UBER_PACKAGE_JSON_PATH = path.resolve(process.cwd(), 'package.json'); +const UBER_PACKAGE_JSON_PATH = path.join(MONOPACKAGE_ROOT, 'package.json'); async function main() { console.log(`🌴 workspace root path is: ${ROOT_PATH}`); - const uberPackageJson = await fs.readJson(UBER_PACKAGE_JSON_PATH); - const libraries = await findLibrariesToPackage(uberPackageJson); await verifyDependencies(uberPackageJson, libraries); await prepareSourceFiles(libraries, uberPackageJson); - await combineRosettaFixtures(libraries); + await combineRosettaFixtures(libraries, uberPackageJson); } main().then( @@ -223,7 +229,10 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag console.log('\t 👩🏻‍🔬 \'excludeExperimentalModules\' enabled. Regenerating all experimental modules as L1s using cfn2ts...'); } - await fs.remove(LIB_ROOT); + // Should not remove collection directory if we're currently in it. The OS would be unhappy. + if (LIB_ROOT !== process.cwd()) { + await fs.remove(LIB_ROOT); + } const indexStatements = new Array(); for (const library of libraries) { @@ -245,19 +254,32 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag console.log('\t🍺 Success!'); } -async function combineRosettaFixtures(libraries: readonly LibraryReference[]) { +async function combineRosettaFixtures(libraries: readonly LibraryReference[], uberPackageJson: PackageJson) { console.log('📝 Combining Rosetta fixtures...'); - const uberRosettaDir = path.resolve(LIB_ROOT, '..', 'rosetta'); + const uberRosettaDir = path.resolve(MONOPACKAGE_ROOT, 'rosetta'); await fs.remove(uberRosettaDir); + await fs.mkdir(uberRosettaDir); for (const library of libraries) { const packageRosettaDir = path.join(library.root, 'rosetta'); + const uberRosettaTargetDir = library.shortName === 'core' ? uberRosettaDir : path.join(uberRosettaDir, library.shortName.replace(/-/g, '_')); if (await fs.pathExists(packageRosettaDir)) { - await fs.copy(packageRosettaDir, uberRosettaDir, { - overwrite: true, - recursive: true, - }); + if (!fs.existsSync(uberRosettaTargetDir)) { + await fs.mkdir(uberRosettaTargetDir); + } + const files = await fs.readdir(packageRosettaDir); + for (const file of files) { + await fs.writeFile( + path.join(uberRosettaTargetDir, file), + await rewriteImportsInRosettaFixtures( + path.join(packageRosettaDir, file), + libraries, + uberPackageJson.name, + ), + { encoding: 'utf8' }, + ); + } } } @@ -284,12 +306,15 @@ async function transformPackage( const destinationLib = path.join(destination, 'lib'); await fs.mkdirp(destinationLib); await cfn2ts(cfnScopes, destinationLib); + // create a lib/index.ts which only exports the generated files fs.writeFileSync(path.join(destinationLib, 'index.ts'), /// logic copied from `create-missing-libraries.ts` cfnScopes.map(s => (s === 'AWS::Serverless' ? 'AWS::SAM' : s).split('::')[1].toLocaleLowerCase()) .map(s => `export * from './${s}.generated';`) .join('\n')); + await cfnspec.createLibraryReadme(cfnScopes[0], path.join(destination, 'README.md')); + await copyOrTransformFiles(destination, destination, allLibraries, uberPackageJson); } else { await copyOrTransformFiles(library.root, destination, allLibraries, uberPackageJson); @@ -310,12 +335,6 @@ async function transformPackage( }, { spaces: 2 }, ); - - await fs.writeFile( - path.resolve(LIB_ROOT, '..', `${library.shortName}.ts`), - `export * from './lib/${library.shortName}';\n`, - { encoding: 'utf8' }, - ); } return true; } @@ -374,10 +393,11 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl await fs.mkdirp(destination); return copyOrTransformFiles(source, destination, libraries, uberPackageJson); } + if (name.endsWith('.ts')) { return fs.writeFile( destination, - await rewriteImports(source, to, libraries), + await rewriteImportsInLibs(source, to, libraries), { encoding: 'utf8' }, ); } else if (name === 'cfn-types-2-classes.json') { @@ -395,9 +415,14 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl } await fs.writeJson(destination, cfnTypes2Classes, { spaces: 2 }); } else if (name === 'README.md') { + // Rewrite the README to both adjust imports and remove the redundant stability banner. + // (All modules included in ubergen-ed packages must be stable, so the banner is unnecessary.) + const newReadme = (await rewriteReadmeImports(source, uberPackageJson.name)) + .replace(/[\s\S]+/gm, ''); + return fs.writeFile( destination, - await rewriteReadmeImports(source), + newReadme, { encoding: 'utf8' }, ); } else { @@ -409,11 +434,11 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl } /** - * Rewrites the imports in README.md from v1 ('@aws-cdk/...') to v2 ('aws-cdk-lib'). + * Rewrites the imports in README.md from v1 ('@aws-cdk/...') to v2 ('aws-cdk-lib') or monocdk ('monocdk'). * Uses the module imports (import { aws_foo as foo } from 'aws-cdk-lib') for module imports, * and "barrel" imports for types (import { Bucket } from 'aws-cdk-lib/aws-s3'). */ -async function rewriteReadmeImports(fromFile: string): Promise { +async function rewriteReadmeImports(fromFile: string, libName: string): Promise { const readmeOriginal = await fs.readFile(fromFile, { encoding: 'utf8' }); return readmeOriginal // import * as s3 from '@aws-cdk/aws-s3' @@ -425,21 +450,51 @@ async function rewriteReadmeImports(fromFile: string): Promise { function rewriteCdkImports(_match: string, prefix: string, alias: string, module: string, suffix: string): string { if (module === 'core') { - return `${prefix}import * as ${alias} from 'aws-cdk-lib';${suffix}`; + return `${prefix}import * as ${alias} from '${libName}';${suffix}`; } else { - return `${prefix}import { ${module.replace(/-/g, '_')} as ${alias} } from 'aws-cdk-lib';${suffix}`; + return `${prefix}import { ${module.replace(/-/g, '_')} as ${alias} } from '${libName}';${suffix}`; } } function rewriteCdkTypeImports(_match: string, prefix: string, types: string, module: string, suffix: string): string { if (module === 'core') { - return `${prefix}import ${types} from 'aws-cdk-lib';${suffix}`; + return `${prefix}import ${types} from '${libName}';${suffix}`; } else { - return `${prefix}import ${types} from 'aws-cdk-lib/${module}';${suffix}`; + return `${prefix}import ${types} from '${libName}/${module}';${suffix}`; } } } -async function rewriteImports(fromFile: string, targetDir: string, libraries: readonly LibraryReference[]): Promise { +/** + * Rewrites imports in libaries, using the relative path (i.e. '../../assertions'). + */ +async function rewriteImportsInLibs(fromFile: string, targetDir: string, libraries: readonly LibraryReference[]): Promise { + return rewriteImports(fromFile, libraries, (importedFile) => path.relative(targetDir, importedFile)); +} + +/** + * Rewrites imports in rosetta fixtures, using the external path (i.e. 'aws-cdk-lib/assertions'). + */ +// eslint-disable-next-line max-len +async function rewriteImportsInRosettaFixtures(fromFile: string, libraries: readonly LibraryReference[], libName: string): Promise { + return rewriteImports(fromFile, libraries, (importedFile) => externalPath(libName, importedFile)); +} + +function externalPath(libName: string, filePath: string) { + const paths = filePath.split(path.sep); + const module = paths[paths.length-1]; + if (module === 'core') { + return libName; + } else { + return path.join(libName, module); + } +} + +/** + * Rewrites imports from a file, to target the target directory, using the given libraries. + * The function `newImport` determines the rewritten file path from the given file. + */ +// eslint-disable-next-line max-len +async function rewriteImports(fromFile: string, libraries: readonly LibraryReference[], newImport: (file: string) => string): Promise { const sourceFile = ts.createSourceFile( fromFile, await fs.readFile(fromFile, { encoding: 'utf8' }), @@ -504,9 +559,9 @@ async function rewriteImports(fromFile: string, targetDir: string, libraries: re const importedFile = moduleSpecifier === sourceLibrary.packageJson.name ? path.join(LIB_ROOT, sourceLibrary.shortName) : path.join(LIB_ROOT, sourceLibrary.shortName, moduleSpecifier.substr(sourceLibrary.packageJson.name.length + 1)); - return ts.createStringLiteral( - path.relative(targetDir, importedFile), - ); + + const importFilePath = newImport(importedFile); + return ts.createStringLiteral(importFilePath); } } diff --git a/tools/@aws-cdk/ubergen/package.json b/tools/@aws-cdk/ubergen/package.json index 910aa2152f1af..1f00f627e4c46 100644 --- a/tools/@aws-cdk/ubergen/package.json +++ b/tools/@aws-cdk/ubergen/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/cfnspec": "0.0.0", "fs-extra": "^9.1.0", "typescript": "~3.9.10" }, diff --git a/tools/@aws-cdk/yarn-cling/package.json b/tools/@aws-cdk/yarn-cling/package.json index b8c930eb66867..f34bbccc4b8af 100644 --- a/tools/@aws-cdk/yarn-cling/package.json +++ b/tools/@aws-cdk/yarn-cling/package.json @@ -38,11 +38,11 @@ }, "devDependencies": { "@aws-cdk/pkglint": "0.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.2", "@types/node": "^10.17.60", - "@types/semver": "^7.3.8", + "@types/semver": "^7.3.9", "@types/yarnpkg__lockfile": "^1.1.5", - "jest": "^26.6.3", + "jest": "^27.3.1", "typescript": "~3.9.10" }, "dependencies": { diff --git a/version.v1.json b/version.v1.json index 45b470868687b..e3f080f0508a7 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.126.0" + "version": "1.132.0" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 27c96a200ce69..36d2acb3df4ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,14 +4,14 @@ "@actions/core@^1.6.0": version "1.6.0" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.6.0.tgz#0568e47039bfb6a9170393a73f3b7eb3b22462cb" + resolved "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz#0568e47039bfb6a9170393a73f3b7eb3b22462cb" integrity sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw== dependencies: "@actions/http-client" "^1.0.11" "@actions/github@^2.2.0": version "2.2.0" - resolved "https://registry.yarnpkg.com/@actions/github/-/github-2.2.0.tgz#8952fe96b12b881fa39340f0e7202b04dc5c3e71" + resolved "https://registry.npmjs.org/@actions/github/-/github-2.2.0.tgz#8952fe96b12b881fa39340f0e7202b04dc5c3e71" integrity sha512-9UAZqn8ywdR70n3GwVle4N8ALosQs4z50N7XMXrSTUVOmVpaBC5kE3TRTT7qQdi3OaQV24mjGuJZsHUmhD+ZXw== dependencies: "@actions/http-client" "^1.0.3" @@ -20,44 +20,44 @@ "@actions/http-client@^1.0.11", "@actions/http-client@^1.0.3": version "1.0.11" - resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-1.0.11.tgz#c58b12e9aa8b159ee39e7dd6cbd0e91d905633c0" + resolved "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz#c58b12e9aa8b159ee39e7dd6cbd0e91d905633c0" integrity sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg== dependencies: tunnel "0.0.6" "@babel/code-frame@7.12.11": version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.15.8": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503" - integrity sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/compat-data@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" - integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== - -"@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10" - integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== - dependencies: - "@babel/code-frame" "^7.15.8" - "@babel/generator" "^7.15.8" - "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.8" - "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.8" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + +"@babel/compat-data@^7.16.0": + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.7.5": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4" + integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-compilation-targets" "^7.16.0" + "@babel/helper-module-transforms" "^7.16.0" + "@babel/helpers" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -65,274 +65,281 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.15.4", "@babel/generator@^7.15.8": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.8.tgz#fa56be6b596952ceb231048cf84ee499a19c0cd1" - integrity sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g== +"@babel/generator@^7.16.0", "@babel/generator@^7.7.2": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" + integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== dependencies: - "@babel/types" "^7.15.6" + "@babel/types" "^7.16.0" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-compilation-targets@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" - integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== +"@babel/helper-compilation-targets@^7.16.0": + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== dependencies: - "@babel/compat-data" "^7.15.0" + "@babel/compat-data" "^7.16.0" "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" + browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-function-name@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" - integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== dependencies: - "@babel/helper-get-function-arity" "^7.15.4" - "@babel/template" "^7.15.4" - "@babel/types" "^7.15.4" + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" -"@babel/helper-get-function-arity@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" - integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-hoist-variables@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" - integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-member-expression-to-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" - integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== +"@babel/helper-member-expression-to-functions@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4" + integrity sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-module-imports@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" - integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== +"@babel/helper-module-imports@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-module-transforms@^7.15.8": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz#d8c0e75a87a52e374a8f25f855174786a09498b2" - integrity sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg== +"@babel/helper-module-transforms@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5" + integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA== dependencies: - "@babel/helper-module-imports" "^7.15.4" - "@babel/helper-replace-supers" "^7.15.4" - "@babel/helper-simple-access" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" + "@babel/helper-module-imports" "^7.16.0" + "@babel/helper-replace-supers" "^7.16.0" + "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" -"@babel/helper-optimise-call-expression@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" - integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== +"@babel/helper-optimise-call-expression@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" + integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== -"@babel/helper-replace-supers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" - integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== +"@babel/helper-replace-supers@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz#73055e8d3cf9bcba8ddb55cad93fedc860f68f17" + integrity sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA== dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.4" - "@babel/helper-optimise-call-expression" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" + "@babel/helper-member-expression-to-functions" "^7.16.0" + "@babel/helper-optimise-call-expression" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" -"@babel/helper-simple-access@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" - integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== +"@babel/helper-simple-access@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" - integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== dependencies: - "@babel/types" "^7.15.4" + "@babel/types" "^7.16.0" -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": +"@babel/helper-validator-identifier@^7.15.7": version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== "@babel/helper-validator-option@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== -"@babel/helpers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" - integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== +"@babel/helpers@^7.16.0": + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz#27fc64f40b996e7074dc73128c3e5c3e7f55c43c" + integrity sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w== dependencies: - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.3" + "@babel/types" "^7.16.0" -"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== +"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== dependencies: - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.15.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016" - integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.7.2": + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" + integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/template@^7.15.4", "@babel/template@^7.3.3": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" - integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" - integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-function-name" "^7.15.4" - "@babel/helper-hoist-variables" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz#2feeb13d9334cc582ea9111d3506f773174179bb" + integrity sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/template@^7.16.0", "@babel/template@^7.3.3": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2": + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" + integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.3" + "@babel/types" "^7.16.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.15.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== +"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== dependencies: - "@babel/helper-validator-identifier" "^7.14.9" + "@babel/helper-validator-identifier" "^7.15.7" to-fast-properties "^2.0.0" "@balena/dockerignore@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" + resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cnakazawa/watch@^1.0.3": version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== dependencies: exec-sh "^0.3.2" @@ -340,19 +347,19 @@ "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== -"@cspotcode/source-map-support@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960" - integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg== +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== dependencies: "@cspotcode/source-map-consumer" "0.8.0" "@eslint/eslintrc@^0.4.3": version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" @@ -367,12 +374,12 @@ "@gar/promisify@^1.0.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== "@humanwhocodes/config-array@^0.5.0": version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== dependencies: "@humanwhocodes/object-schema" "^1.2.0" @@ -380,18 +387,18 @@ minimatch "^3.0.4" "@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@hutson/parse-repository-url@^3.0.0": version "3.0.2" - resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" + resolved "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -402,12 +409,12 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== dependencies: "@jest/types" "^26.6.2" @@ -417,9 +424,21 @@ jest-util "^26.6.2" slash "^3.0.0" +"@jest/console@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" + integrity sha512-RkFNWmv0iui+qsOr/29q9dyfKTTT5DCuP31kUwg7rmOKPT/ozLeGLKJKVIiOfbiKyleUZKIrHwhmiZWVe8IMdw== + dependencies: + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.3.1" + jest-util "^27.3.1" + slash "^3.0.0" + "@jest/core@^26.6.3": version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== dependencies: "@jest/console" "^26.6.2" @@ -451,9 +470,43 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/core@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/core/-/core-27.3.1.tgz#04992ef1b58b17c459afb87ab56d81e63d386925" + integrity sha512-DMNE90RR5QKx0EA+wqe3/TNEwiRpOkhshKNxtLxd4rt3IZpCt+RSL+FoJsGeblRZmqdK4upHA/mKKGPPRAifhg== + dependencies: + "@jest/console" "^27.3.1" + "@jest/reporters" "^27.3.1" + "@jest/test-result" "^27.3.1" + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^27.3.0" + jest-config "^27.3.1" + jest-haste-map "^27.3.1" + jest-message-util "^27.3.1" + jest-regex-util "^27.0.6" + jest-resolve "^27.3.1" + jest-resolve-dependencies "^27.3.1" + jest-runner "^27.3.1" + jest-runtime "^27.3.1" + jest-snapshot "^27.3.1" + jest-util "^27.3.1" + jest-validate "^27.3.1" + jest-watcher "^27.3.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/environment@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== dependencies: "@jest/fake-timers" "^26.6.2" @@ -461,9 +514,19 @@ "@types/node" "*" jest-mock "^26.6.2" +"@jest/environment@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.3.1.tgz#2182defbce8d385fd51c5e7c7050f510bd4c86b1" + integrity sha512-BCKCj4mOVLme6Tanoyc9k0ultp3pnmuyHw73UHRPeeZxirsU/7E3HC4le/VDb/SMzE1JcPnto+XBKFOcoiJzVw== + dependencies: + "@jest/fake-timers" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + jest-mock "^27.3.0" + "@jest/fake-timers@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== dependencies: "@jest/types" "^26.6.2" @@ -473,18 +536,39 @@ jest-mock "^26.6.2" jest-util "^26.6.2" +"@jest/fake-timers@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.3.1.tgz#1fad860ee9b13034762cdb94266e95609dfce641" + integrity sha512-M3ZFgwwlqJtWZ+QkBG5NmC23A9w+A6ZxNsO5nJxJsKYt4yguBd3i8TpjQz5NfCX91nEve1KqD9RA2Q+Q1uWqoA== + dependencies: + "@jest/types" "^27.2.5" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.3.1" + jest-mock "^27.3.0" + jest-util "^27.3.1" + "@jest/globals@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== dependencies: "@jest/environment" "^26.6.2" "@jest/types" "^26.6.2" expect "^26.6.2" +"@jest/globals@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.3.1.tgz#ce1dfb03d379237a9da6c1b99ecfaca1922a5f9e" + integrity sha512-Q651FWiWQAIFiN+zS51xqhdZ8g9b88nGCobC87argAxA7nMfNQq0Q0i9zTfQYgLa6qFXk2cGANEqfK051CZ8Pg== + dependencies: + "@jest/environment" "^27.3.1" + "@jest/types" "^27.2.5" + expect "^27.3.1" + "@jest/reporters@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -514,18 +598,58 @@ optionalDependencies: node-notifier "^8.0.0" +"@jest/reporters@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.3.1.tgz#28b5c1f5789481e23788048fa822ed15486430b9" + integrity sha512-m2YxPmL9Qn1emFVgZGEiMwDntDxRRQ2D58tiDQlwYTg5GvbFOKseYCcHtn0WsI8CG4vzPglo3nqbOiT8ySBT/w== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.3.1" + "@jest/test-result" "^27.3.1" + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.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.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^27.3.1" + jest-resolve "^27.3.1" + jest-util "^27.3.1" + jest-worker "^27.3.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + "@jest/source-map@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" +"@jest/source-map@^27.0.6": + version "27.0.6" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" + integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + "@jest/test-result@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== dependencies: "@jest/console" "^26.6.2" @@ -533,9 +657,19 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-result@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.3.1.tgz#89adee8b771877c69b3b8d59f52f29dccc300194" + integrity sha512-mLn6Thm+w2yl0opM8J/QnPTqrfS4FoXsXF2WIWJb2O/GBSyResL71BRuMYbYRsGt7ELwS5JGcEcGb52BNrumgg== + dependencies: + "@jest/console" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-sequencer@^26.6.3": version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== dependencies: "@jest/test-result" "^26.6.2" @@ -544,9 +678,19 @@ jest-runner "^26.6.3" jest-runtime "^26.6.3" +"@jest/test-sequencer@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz#4b3bde2dbb05ee74afdae608cf0768e3354683b1" + integrity sha512-siySLo07IMEdSjA4fqEnxfIX8lB/lWYsBPwNFtkOvsFQvmBrL3yj3k3uFNZv/JDyApTakRpxbKLJ3CT8UGVCrA== + dependencies: + "@jest/test-result" "^27.3.1" + graceful-fs "^4.2.4" + jest-haste-map "^27.3.1" + jest-runtime "^27.3.1" + "@jest/transform@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== dependencies: "@babel/core" "^7.1.0" @@ -565,9 +709,30 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" +"@jest/transform@^27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.3.1.tgz#ff80eafbeabe811e9025e4b6f452126718455220" + integrity sha512-3fSvQ02kuvjOI1C1ssqMVBKJpZf6nwoCiSu00zAKh5nrp3SptNtZy/8s5deayHnqxhjD9CWDJ+yqQwuQ0ZafXQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.2.5" + 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 "^27.3.1" + jest-regex-util "^27.0.6" + jest-util "^27.3.1" + micromatch "^4.0.4" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + "@jest/types@^26.6.2": version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" @@ -576,24 +741,43 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jsii/check-node@1.38.0": - version "1.38.0" - resolved "https://registry.yarnpkg.com/@jsii/check-node/-/check-node-1.38.0.tgz#4d9df1aac07d69401da240a3fff456f55c2b3e3d" - integrity sha512-VlEu2/nqxgGHVlfMlzGCPN3ivS3iPNjXSLSHLNCxHzjumcVjJnj88KzrIXrtNy3Dwuy72ulQYJp7aa/TSsCZNw== +"@jest/types@^27.2.5": + version "27.2.5" + resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132" + integrity sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jsii/check-node@1.44.2": + version "1.44.2" + resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.44.2.tgz#d7786e7ca739cc9a5cd2cd3f1b93c4375ff884e8" + integrity sha512-rVwrKXkuV4qmo0TmPbYMAu2SCC80xPDzY7cS+TDx80wfU5Dcr66lhpUW04hWcYwrVsUYXxtEYLxAbzeNYeJeoA== dependencies: chalk "^4.1.2" semver "^7.3.5" -"@jsii/spec@^1.38.0": - version "1.38.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.38.0.tgz#503ae6c0c7e4deae48857ffff1e6a11d318d533e" - integrity sha512-SvZU6QxH1HOlHuXJhzHcaoflGqx4PXlNbva9Ox73qw2P/QuNY0H8/MIB/dZniT01H1nVDGVJm7/4Se6eSWKzdQ== +"@jsii/check-node@1.45.0": + version "1.45.0" + resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.45.0.tgz#1a679806e0def800a1265bfd4b459c5ed39f36b2" + integrity sha512-YtB4EEnlVe2jSmnyD7PAh8TaY6JORhTsZm+p6/p4t997cA9tG9/dyeVMpRA5dtmfLUpBFOOkZdV1+qxa53crwQ== + dependencies: + chalk "^4.1.2" + semver "^7.3.5" + +"@jsii/spec@1.45.0", "@jsii/spec@^1.45.0": + version "1.45.0" + resolved "https://registry.npmjs.org/@jsii/spec/-/spec-1.45.0.tgz#94a1ade6f2792d3d0ffc9e8722eb3d4e0a640c2a" + integrity sha512-+hLFh08nKhUep1UIaKDHi2ywM7SipCZX5BBf/xJ3db+P9qnN4dAu/H46mbOrdQGChZx9OwSuLWm5xEYZQ52efA== dependencies: jsonschema "^1.4.0" "@lerna/add@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" + resolved "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" integrity sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng== dependencies: "@lerna/bootstrap" "4.0.0" @@ -609,7 +793,7 @@ "@lerna/bootstrap@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-4.0.0.tgz#5f5c5e2c6cfc8fcec50cb2fbe569a8c607101891" + resolved "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-4.0.0.tgz#5f5c5e2c6cfc8fcec50cb2fbe569a8c607101891" integrity sha512-RkS7UbeM2vu+kJnHzxNRCLvoOP9yGNgkzRdy4UV2hNalD7EP41bLvRVOwRYQ7fhc2QcbhnKNdOBihYRL0LcKtw== dependencies: "@lerna/command" "4.0.0" @@ -637,7 +821,7 @@ "@lerna/changed@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-4.0.0.tgz#b9fc76cea39b9292a6cd263f03eb57af85c9270b" + resolved "https://registry.npmjs.org/@lerna/changed/-/changed-4.0.0.tgz#b9fc76cea39b9292a6cd263f03eb57af85c9270b" integrity sha512-cD+KuPRp6qiPOD+BO6S6SN5cARspIaWSOqGBpGnYzLb4uWT8Vk4JzKyYtc8ym1DIwyoFXHosXt8+GDAgR8QrgQ== dependencies: "@lerna/collect-updates" "4.0.0" @@ -647,7 +831,7 @@ "@lerna/check-working-tree@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-4.0.0.tgz#257e36a602c00142e76082a19358e3e1ae8dbd58" + resolved "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-4.0.0.tgz#257e36a602c00142e76082a19358e3e1ae8dbd58" integrity sha512-/++bxM43jYJCshBiKP5cRlCTwSJdRSxVmcDAXM+1oUewlZJVSVlnks5eO0uLxokVFvLhHlC5kHMc7gbVFPHv6Q== dependencies: "@lerna/collect-uncommitted" "4.0.0" @@ -656,7 +840,7 @@ "@lerna/child-process@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-4.0.0.tgz#341b96a57dffbd9705646d316e231df6fa4df6e1" + resolved "https://registry.npmjs.org/@lerna/child-process/-/child-process-4.0.0.tgz#341b96a57dffbd9705646d316e231df6fa4df6e1" integrity sha512-XtCnmCT9eyVsUUHx6y/CTBYdV9g2Cr/VxyseTWBgfIur92/YKClfEtJTbOh94jRT62hlKLqSvux/UhxXVh613Q== dependencies: chalk "^4.1.0" @@ -665,7 +849,7 @@ "@lerna/clean@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-4.0.0.tgz#8f778b6f2617aa2a936a6b5e085ae62498e57dc5" + resolved "https://registry.npmjs.org/@lerna/clean/-/clean-4.0.0.tgz#8f778b6f2617aa2a936a6b5e085ae62498e57dc5" integrity sha512-uugG2iN9k45ITx2jtd8nEOoAtca8hNlDCUM0N3lFgU/b1mEQYAPRkqr1qs4FLRl/Y50ZJ41wUz1eazS+d/0osA== dependencies: "@lerna/command" "4.0.0" @@ -679,7 +863,7 @@ "@lerna/cli@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-4.0.0.tgz#8eabd334558836c1664df23f19acb95e98b5bbf3" + resolved "https://registry.npmjs.org/@lerna/cli/-/cli-4.0.0.tgz#8eabd334558836c1664df23f19acb95e98b5bbf3" integrity sha512-Neaw3GzFrwZiRZv2g7g6NwFjs3er1vhraIniEs0jjVLPMNC4eata0na3GfE5yibkM/9d3gZdmihhZdZ3EBdvYA== dependencies: "@lerna/global-options" "4.0.0" @@ -689,7 +873,7 @@ "@lerna/collect-uncommitted@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-4.0.0.tgz#855cd64612969371cfc2453b90593053ff1ba779" + resolved "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-4.0.0.tgz#855cd64612969371cfc2453b90593053ff1ba779" integrity sha512-ufSTfHZzbx69YNj7KXQ3o66V4RC76ffOjwLX0q/ab//61bObJ41n03SiQEhSlmpP+gmFbTJ3/7pTe04AHX9m/g== dependencies: "@lerna/child-process" "4.0.0" @@ -698,7 +882,7 @@ "@lerna/collect-updates@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-4.0.0.tgz#8e208b1bafd98a372ff1177f7a5e288f6bea8041" + resolved "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-4.0.0.tgz#8e208b1bafd98a372ff1177f7a5e288f6bea8041" integrity sha512-bnNGpaj4zuxsEkyaCZLka9s7nMs58uZoxrRIPJ+nrmrZYp1V5rrd+7/NYTuunOhY2ug1sTBvTAxj3NZQ+JKnOw== dependencies: "@lerna/child-process" "4.0.0" @@ -709,7 +893,7 @@ "@lerna/command@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-4.0.0.tgz#991c7971df8f5bf6ae6e42c808869a55361c1b98" + resolved "https://registry.npmjs.org/@lerna/command/-/command-4.0.0.tgz#991c7971df8f5bf6ae6e42c808869a55361c1b98" integrity sha512-LM9g3rt5FsPNFqIHUeRwWXLNHJ5NKzOwmVKZ8anSp4e1SPrv2HNc1V02/9QyDDZK/w+5POXH5lxZUI1CHaOK/A== dependencies: "@lerna/child-process" "4.0.0" @@ -725,7 +909,7 @@ "@lerna/conventional-commits@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-4.0.0.tgz#660fb2c7b718cb942ead70110df61f18c6f99750" + resolved "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-4.0.0.tgz#660fb2c7b718cb942ead70110df61f18c6f99750" integrity sha512-CSUQRjJHFrH8eBn7+wegZLV3OrNc0Y1FehYfYGhjLE2SIfpCL4bmfu/ViYuHh9YjwHaA+4SX6d3hR+xkeseKmw== dependencies: "@lerna/validation-error" "4.0.0" @@ -742,7 +926,7 @@ "@lerna/create-symlink@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-4.0.0.tgz#8c5317ce5ae89f67825443bd7651bf4121786228" + resolved "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-4.0.0.tgz#8c5317ce5ae89f67825443bd7651bf4121786228" integrity sha512-I0phtKJJdafUiDwm7BBlEUOtogmu8+taxq6PtIrxZbllV9hWg59qkpuIsiFp+no7nfRVuaasNYHwNUhDAVQBig== dependencies: cmd-shim "^4.1.0" @@ -751,7 +935,7 @@ "@lerna/create@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-4.0.0.tgz#b6947e9b5dfb6530321952998948c3e63d64d730" + resolved "https://registry.npmjs.org/@lerna/create/-/create-4.0.0.tgz#b6947e9b5dfb6530321952998948c3e63d64d730" integrity sha512-mVOB1niKByEUfxlbKTM1UNECWAjwUdiioIbRQZEeEabtjCL69r9rscIsjlGyhGWCfsdAG5wfq4t47nlDXdLLag== dependencies: "@lerna/child-process" "4.0.0" @@ -775,7 +959,7 @@ "@lerna/describe-ref@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-4.0.0.tgz#53c53b4ea65fdceffa072a62bfebe6772c45d9ec" + resolved "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-4.0.0.tgz#53c53b4ea65fdceffa072a62bfebe6772c45d9ec" integrity sha512-eTU5+xC4C5Gcgz+Ey4Qiw9nV2B4JJbMulsYJMW8QjGcGh8zudib7Sduj6urgZXUYNyhYpRs+teci9M2J8u+UvQ== dependencies: "@lerna/child-process" "4.0.0" @@ -783,7 +967,7 @@ "@lerna/diff@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-4.0.0.tgz#6d3071817aaa4205a07bf77cfc6e932796d48b92" + resolved "https://registry.npmjs.org/@lerna/diff/-/diff-4.0.0.tgz#6d3071817aaa4205a07bf77cfc6e932796d48b92" integrity sha512-jYPKprQVg41+MUMxx6cwtqsNm0Yxx9GDEwdiPLwcUTFx+/qKCEwifKNJ1oGIPBxyEHX2PFCOjkK39lHoj2qiag== dependencies: "@lerna/child-process" "4.0.0" @@ -793,7 +977,7 @@ "@lerna/exec@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-4.0.0.tgz#eb6cb95cb92d42590e9e2d628fcaf4719d4a8be6" + resolved "https://registry.npmjs.org/@lerna/exec/-/exec-4.0.0.tgz#eb6cb95cb92d42590e9e2d628fcaf4719d4a8be6" integrity sha512-VGXtL/b/JfY84NB98VWZpIExfhLOzy0ozm/0XaS4a2SmkAJc5CeUfrhvHxxkxiTBLkU+iVQUyYEoAT0ulQ8PCw== dependencies: "@lerna/child-process" "4.0.0" @@ -806,7 +990,7 @@ "@lerna/filter-options@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-4.0.0.tgz#ac94cc515d7fa3b47e2f7d74deddeabb1de5e9e6" + resolved "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-4.0.0.tgz#ac94cc515d7fa3b47e2f7d74deddeabb1de5e9e6" integrity sha512-vV2ANOeZhOqM0rzXnYcFFCJ/kBWy/3OA58irXih9AMTAlQLymWAK0akWybl++sUJ4HB9Hx12TOqaXbYS2NM5uw== dependencies: "@lerna/collect-updates" "4.0.0" @@ -816,7 +1000,7 @@ "@lerna/filter-packages@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-4.0.0.tgz#b1f70d70e1de9cdd36a4e50caa0ac501f8d012f2" + resolved "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-4.0.0.tgz#b1f70d70e1de9cdd36a4e50caa0ac501f8d012f2" integrity sha512-+4AJIkK7iIiOaqCiVTYJxh/I9qikk4XjNQLhE3kixaqgMuHl1NQ99qXRR0OZqAWB9mh8Z1HA9bM5K1HZLBTOqA== dependencies: "@lerna/validation-error" "4.0.0" @@ -825,14 +1009,14 @@ "@lerna/get-npm-exec-opts@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-4.0.0.tgz#dc955be94a4ae75c374ef9bce91320887d34608f" + resolved "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-4.0.0.tgz#dc955be94a4ae75c374ef9bce91320887d34608f" integrity sha512-yvmkerU31CTWS2c7DvmAWmZVeclPBqI7gPVr5VATUKNWJ/zmVcU4PqbYoLu92I9Qc4gY1TuUplMNdNuZTSL7IQ== dependencies: npmlog "^4.1.2" "@lerna/get-packed@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-4.0.0.tgz#0989d61624ac1f97e393bdad2137c49cd7a37823" + resolved "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-4.0.0.tgz#0989d61624ac1f97e393bdad2137c49cd7a37823" integrity sha512-rfWONRsEIGyPJTxFzC8ECb3ZbsDXJbfqWYyeeQQDrJRPnEJErlltRLPLgC2QWbxFgFPsoDLeQmFHJnf0iDfd8w== dependencies: fs-extra "^9.1.0" @@ -841,7 +1025,7 @@ "@lerna/github-client@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-4.0.0.tgz#2ced67721363ef70f8e12ffafce4410918f4a8a4" + resolved "https://registry.npmjs.org/@lerna/github-client/-/github-client-4.0.0.tgz#2ced67721363ef70f8e12ffafce4410918f4a8a4" integrity sha512-2jhsldZtTKXYUBnOm23Lb0Fx8G4qfSXF9y7UpyUgWUj+YZYd+cFxSuorwQIgk5P4XXrtVhsUesIsli+BYSThiw== dependencies: "@lerna/child-process" "4.0.0" @@ -852,7 +1036,7 @@ "@lerna/gitlab-client@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-4.0.0.tgz#00dad73379c7b38951d4b4ded043504c14e2b67d" + resolved "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-4.0.0.tgz#00dad73379c7b38951d4b4ded043504c14e2b67d" integrity sha512-OMUpGSkeDWFf7BxGHlkbb35T7YHqVFCwBPSIR6wRsszY8PAzCYahtH3IaJzEJyUg6vmZsNl0FSr3pdA2skhxqA== dependencies: node-fetch "^2.6.1" @@ -861,12 +1045,12 @@ "@lerna/global-options@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-4.0.0.tgz#c7d8b0de6a01d8a845e2621ea89e7f60f18c6a5f" + resolved "https://registry.npmjs.org/@lerna/global-options/-/global-options-4.0.0.tgz#c7d8b0de6a01d8a845e2621ea89e7f60f18c6a5f" integrity sha512-TRMR8afAHxuYBHK7F++Ogop2a82xQjoGna1dvPOY6ltj/pEx59pdgcJfYcynYqMkFIk8bhLJJN9/ndIfX29FTQ== "@lerna/has-npm-version@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-4.0.0.tgz#d3fc3292c545eb28bd493b36e6237cf0279f631c" + resolved "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-4.0.0.tgz#d3fc3292c545eb28bd493b36e6237cf0279f631c" integrity sha512-LQ3U6XFH8ZmLCsvsgq1zNDqka0Xzjq5ibVN+igAI5ccRWNaUsE/OcmsyMr50xAtNQMYMzmpw5GVLAivT2/YzCg== dependencies: "@lerna/child-process" "4.0.0" @@ -874,7 +1058,7 @@ "@lerna/import@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-4.0.0.tgz#bde656c4a451fa87ae41733ff8a8da60547c5465" + resolved "https://registry.npmjs.org/@lerna/import/-/import-4.0.0.tgz#bde656c4a451fa87ae41733ff8a8da60547c5465" integrity sha512-FaIhd+4aiBousKNqC7TX1Uhe97eNKf5/SC7c5WZANVWtC7aBWdmswwDt3usrzCNpj6/Wwr9EtEbYROzxKH8ffg== dependencies: "@lerna/child-process" "4.0.0" @@ -888,7 +1072,7 @@ "@lerna/info@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/info/-/info-4.0.0.tgz#b9fb0e479d60efe1623603958a831a88b1d7f1fc" + resolved "https://registry.npmjs.org/@lerna/info/-/info-4.0.0.tgz#b9fb0e479d60efe1623603958a831a88b1d7f1fc" integrity sha512-8Uboa12kaCSZEn4XRfPz5KU9XXoexSPS4oeYGj76s2UQb1O1GdnEyfjyNWoUl1KlJ2i/8nxUskpXIftoFYH0/Q== dependencies: "@lerna/command" "4.0.0" @@ -897,7 +1081,7 @@ "@lerna/init@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-4.0.0.tgz#dadff67e6dfb981e8ccbe0e6a310e837962f6c7a" + resolved "https://registry.npmjs.org/@lerna/init/-/init-4.0.0.tgz#dadff67e6dfb981e8ccbe0e6a310e837962f6c7a" integrity sha512-wY6kygop0BCXupzWj5eLvTUqdR7vIAm0OgyV9WHpMYQGfs1V22jhztt8mtjCloD/O0nEe4tJhdG62XU5aYmPNQ== dependencies: "@lerna/child-process" "4.0.0" @@ -908,7 +1092,7 @@ "@lerna/link@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-4.0.0.tgz#c3a38aabd44279d714e90f2451e31b63f0fb65ba" + resolved "https://registry.npmjs.org/@lerna/link/-/link-4.0.0.tgz#c3a38aabd44279d714e90f2451e31b63f0fb65ba" integrity sha512-KlvPi7XTAcVOByfaLlOeYOfkkDcd+bejpHMCd1KcArcFTwijOwXOVi24DYomIeHvy6HsX/IUquJ4PPUJIeB4+w== dependencies: "@lerna/command" "4.0.0" @@ -919,7 +1103,7 @@ "@lerna/list@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-4.0.0.tgz#24b4e6995bd73f81c556793fe502b847efd9d1d7" + resolved "https://registry.npmjs.org/@lerna/list/-/list-4.0.0.tgz#24b4e6995bd73f81c556793fe502b847efd9d1d7" integrity sha512-L2B5m3P+U4Bif5PultR4TI+KtW+SArwq1i75QZ78mRYxPc0U/piau1DbLOmwrdqr99wzM49t0Dlvl6twd7GHFg== dependencies: "@lerna/command" "4.0.0" @@ -929,7 +1113,7 @@ "@lerna/listable@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-4.0.0.tgz#d00d6cb4809b403f2b0374fc521a78e318b01214" + resolved "https://registry.npmjs.org/@lerna/listable/-/listable-4.0.0.tgz#d00d6cb4809b403f2b0374fc521a78e318b01214" integrity sha512-/rPOSDKsOHs5/PBLINZOkRIX1joOXUXEtyUs5DHLM8q6/RP668x/1lFhw6Dx7/U+L0+tbkpGtZ1Yt0LewCLgeQ== dependencies: "@lerna/query-graph" "4.0.0" @@ -938,7 +1122,7 @@ "@lerna/log-packed@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-4.0.0.tgz#95168fe2e26ac6a71e42f4be857519b77e57a09f" + resolved "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-4.0.0.tgz#95168fe2e26ac6a71e42f4be857519b77e57a09f" integrity sha512-+dpCiWbdzgMAtpajLToy9PO713IHoE6GV/aizXycAyA07QlqnkpaBNZ8DW84gHdM1j79TWockGJo9PybVhrrZQ== dependencies: byte-size "^7.0.0" @@ -948,7 +1132,7 @@ "@lerna/npm-conf@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-4.0.0.tgz#b259fd1e1cee2bf5402b236e770140ff9ade7fd2" + resolved "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-4.0.0.tgz#b259fd1e1cee2bf5402b236e770140ff9ade7fd2" integrity sha512-uS7H02yQNq3oejgjxAxqq/jhwGEE0W0ntr8vM3EfpCW1F/wZruwQw+7bleJQ9vUBjmdXST//tk8mXzr5+JXCfw== dependencies: config-chain "^1.1.12" @@ -956,7 +1140,7 @@ "@lerna/npm-dist-tag@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-4.0.0.tgz#d1e99b4eccd3414142f0548ad331bf2d53f3257a" + resolved "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-4.0.0.tgz#d1e99b4eccd3414142f0548ad331bf2d53f3257a" integrity sha512-F20sg28FMYTgXqEQihgoqSfwmq+Id3zT23CnOwD+XQMPSy9IzyLf1fFVH319vXIw6NF6Pgs4JZN2Qty6/CQXGw== dependencies: "@lerna/otplease" "4.0.0" @@ -966,7 +1150,7 @@ "@lerna/npm-install@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-4.0.0.tgz#31180be3ab3b7d1818a1a0c206aec156b7094c78" + resolved "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-4.0.0.tgz#31180be3ab3b7d1818a1a0c206aec156b7094c78" integrity sha512-aKNxq2j3bCH3eXl3Fmu4D54s/YLL9WSwV8W7X2O25r98wzrO38AUN6AB9EtmAx+LV/SP15et7Yueg9vSaanRWg== dependencies: "@lerna/child-process" "4.0.0" @@ -979,7 +1163,7 @@ "@lerna/npm-publish@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-4.0.0.tgz#84eb62e876fe949ae1fd62c60804423dbc2c4472" + resolved "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-4.0.0.tgz#84eb62e876fe949ae1fd62c60804423dbc2c4472" integrity sha512-vQb7yAPRo5G5r77DRjHITc9piR9gvEKWrmfCH7wkfBnGWEqu7n8/4bFQ7lhnkujvc8RXOsYpvbMQkNfkYibD/w== dependencies: "@lerna/otplease" "4.0.0" @@ -993,7 +1177,7 @@ "@lerna/npm-run-script@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-4.0.0.tgz#dfebf4f4601442e7c0b5214f9fb0d96c9350743b" + resolved "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-4.0.0.tgz#dfebf4f4601442e7c0b5214f9fb0d96c9350743b" integrity sha512-Jmyh9/IwXJjOXqKfIgtxi0bxi1pUeKe5bD3S81tkcy+kyng/GNj9WSqD5ZggoNP2NP//s4CLDAtUYLdP7CU9rA== dependencies: "@lerna/child-process" "4.0.0" @@ -1002,21 +1186,21 @@ "@lerna/otplease@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-4.0.0.tgz#84972eb43448f8a1077435ba1c5e59233b725850" + resolved "https://registry.npmjs.org/@lerna/otplease/-/otplease-4.0.0.tgz#84972eb43448f8a1077435ba1c5e59233b725850" integrity sha512-Sgzbqdk1GH4psNiT6hk+BhjOfIr/5KhGBk86CEfHNJTk9BK4aZYyJD4lpDbDdMjIV4g03G7pYoqHzH765T4fxw== dependencies: "@lerna/prompt" "4.0.0" "@lerna/output@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/output/-/output-4.0.0.tgz#b1d72215c0e35483e4f3e9994debc82c621851f2" + resolved "https://registry.npmjs.org/@lerna/output/-/output-4.0.0.tgz#b1d72215c0e35483e4f3e9994debc82c621851f2" integrity sha512-Un1sHtO1AD7buDQrpnaYTi2EG6sLF+KOPEAMxeUYG5qG3khTs2Zgzq5WE3dt2N/bKh7naESt20JjIW6tBELP0w== dependencies: npmlog "^4.1.2" "@lerna/pack-directory@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-4.0.0.tgz#8b617db95d20792f043aaaa13a9ccc0e04cb4c74" + resolved "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-4.0.0.tgz#8b617db95d20792f043aaaa13a9ccc0e04cb4c74" integrity sha512-NJrmZNmBHS+5aM+T8N6FVbaKFScVqKlQFJNY2k7nsJ/uklNKsLLl6VhTQBPwMTbf6Tf7l6bcKzpy7aePuq9UiQ== dependencies: "@lerna/get-packed" "4.0.0" @@ -1029,7 +1213,7 @@ "@lerna/package-graph@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-4.0.0.tgz#16a00253a8ac810f72041481cb46bcee8d8123dd" + resolved "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-4.0.0.tgz#16a00253a8ac810f72041481cb46bcee8d8123dd" integrity sha512-QED2ZCTkfXMKFoTGoccwUzjHtZMSf3UKX14A4/kYyBms9xfFsesCZ6SLI5YeySEgcul8iuIWfQFZqRw+Qrjraw== dependencies: "@lerna/prerelease-id-from-version" "4.0.0" @@ -1040,7 +1224,7 @@ "@lerna/package@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/package/-/package-4.0.0.tgz#1b4c259c4bcff45c876ee1d591a043aacbc0d6b7" + resolved "https://registry.npmjs.org/@lerna/package/-/package-4.0.0.tgz#1b4c259c4bcff45c876ee1d591a043aacbc0d6b7" integrity sha512-l0M/izok6FlyyitxiQKr+gZLVFnvxRQdNhzmQ6nRnN9dvBJWn+IxxpM+cLqGACatTnyo9LDzNTOj2Db3+s0s8Q== dependencies: load-json-file "^6.2.0" @@ -1049,14 +1233,14 @@ "@lerna/prerelease-id-from-version@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-4.0.0.tgz#c7e0676fcee1950d85630e108eddecdd5b48c916" + resolved "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-4.0.0.tgz#c7e0676fcee1950d85630e108eddecdd5b48c916" integrity sha512-GQqguzETdsYRxOSmdFZ6zDBXDErIETWOqomLERRY54f4p+tk4aJjoVdd9xKwehC9TBfIFvlRbL1V9uQGHh1opg== dependencies: semver "^7.3.4" "@lerna/profiler@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-4.0.0.tgz#8a53ab874522eae15d178402bff90a14071908e9" + resolved "https://registry.npmjs.org/@lerna/profiler/-/profiler-4.0.0.tgz#8a53ab874522eae15d178402bff90a14071908e9" integrity sha512-/BaEbqnVh1LgW/+qz8wCuI+obzi5/vRE8nlhjPzdEzdmWmZXuCKyWSEzAyHOJWw1ntwMiww5dZHhFQABuoFz9Q== dependencies: fs-extra "^9.1.0" @@ -1065,7 +1249,7 @@ "@lerna/project@4.0.0", "@lerna/project@^4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-4.0.0.tgz#ff84893935833533a74deff30c0e64ddb7f0ba6b" + resolved "https://registry.npmjs.org/@lerna/project/-/project-4.0.0.tgz#ff84893935833533a74deff30c0e64ddb7f0ba6b" integrity sha512-o0MlVbDkD5qRPkFKlBZsXZjoNTWPyuL58564nSfZJ6JYNmgAptnWPB2dQlAc7HWRZkmnC2fCkEdoU+jioPavbg== dependencies: "@lerna/package" "4.0.0" @@ -1083,7 +1267,7 @@ "@lerna/prompt@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-4.0.0.tgz#5ec69a803f3f0db0ad9f221dad64664d3daca41b" + resolved "https://registry.npmjs.org/@lerna/prompt/-/prompt-4.0.0.tgz#5ec69a803f3f0db0ad9f221dad64664d3daca41b" integrity sha512-4Ig46oCH1TH5M7YyTt53fT6TuaKMgqUUaqdgxvp6HP6jtdak6+amcsqB8YGz2eQnw/sdxunx84DfI9XpoLj4bQ== dependencies: inquirer "^7.3.3" @@ -1091,7 +1275,7 @@ "@lerna/publish@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-4.0.0.tgz#f67011305adeba120066a3b6d984a5bb5fceef65" + resolved "https://registry.npmjs.org/@lerna/publish/-/publish-4.0.0.tgz#f67011305adeba120066a3b6d984a5bb5fceef65" integrity sha512-K8jpqjHrChH22qtkytA5GRKIVFEtqBF6JWj1I8dWZtHs4Jywn8yB1jQ3BAMLhqmDJjWJtRck0KXhQQKzDK2UPg== dependencies: "@lerna/check-working-tree" "4.0.0" @@ -1125,21 +1309,21 @@ "@lerna/pulse-till-done@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-4.0.0.tgz#04bace7d483a8205c187b806bcd8be23d7bb80a3" + resolved "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-4.0.0.tgz#04bace7d483a8205c187b806bcd8be23d7bb80a3" integrity sha512-Frb4F7QGckaybRhbF7aosLsJ5e9WuH7h0KUkjlzSByVycxY91UZgaEIVjS2oN9wQLrheLMHl6SiFY0/Pvo0Cxg== dependencies: npmlog "^4.1.2" "@lerna/query-graph@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-4.0.0.tgz#09dd1c819ac5ee3f38db23931143701f8a6eef63" + resolved "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-4.0.0.tgz#09dd1c819ac5ee3f38db23931143701f8a6eef63" integrity sha512-YlP6yI3tM4WbBmL9GCmNDoeQyzcyg1e4W96y/PKMZa5GbyUvkS2+Jc2kwPD+5KcXou3wQZxSPzR3Te5OenaDdg== dependencies: "@lerna/package-graph" "4.0.0" "@lerna/resolve-symlink@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-4.0.0.tgz#6d006628a210c9b821964657a9e20a8c9a115e14" + resolved "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-4.0.0.tgz#6d006628a210c9b821964657a9e20a8c9a115e14" integrity sha512-RtX8VEUzqT+uLSCohx8zgmjc6zjyRlh6i/helxtZTMmc4+6O4FS9q5LJas2uGO2wKvBlhcD6siibGt7dIC3xZA== dependencies: fs-extra "^9.1.0" @@ -1148,7 +1332,7 @@ "@lerna/rimraf-dir@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-4.0.0.tgz#2edf3b62d4eb0ef4e44e430f5844667d551ec25a" + resolved "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-4.0.0.tgz#2edf3b62d4eb0ef4e44e430f5844667d551ec25a" integrity sha512-QNH9ABWk9mcMJh2/muD9iYWBk1oQd40y6oH+f3wwmVGKYU5YJD//+zMiBI13jxZRtwBx0vmBZzkBkK1dR11cBg== dependencies: "@lerna/child-process" "4.0.0" @@ -1158,7 +1342,7 @@ "@lerna/run-lifecycle@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-4.0.0.tgz#e648a46f9210a9bcd7c391df6844498cb5079334" + resolved "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-4.0.0.tgz#e648a46f9210a9bcd7c391df6844498cb5079334" integrity sha512-IwxxsajjCQQEJAeAaxF8QdEixfI7eLKNm4GHhXHrgBu185JcwScFZrj9Bs+PFKxwb+gNLR4iI5rpUdY8Y0UdGQ== dependencies: "@lerna/npm-conf" "4.0.0" @@ -1167,7 +1351,7 @@ "@lerna/run-topologically@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-4.0.0.tgz#af846eeee1a09b0c2be0d1bfb5ef0f7b04bb1827" + resolved "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-4.0.0.tgz#af846eeee1a09b0c2be0d1bfb5ef0f7b04bb1827" integrity sha512-EVZw9hGwo+5yp+VL94+NXRYisqgAlj0jWKWtAIynDCpghRxCE5GMO3xrQLmQgqkpUl9ZxQFpICgYv5DW4DksQA== dependencies: "@lerna/query-graph" "4.0.0" @@ -1175,7 +1359,7 @@ "@lerna/run@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-4.0.0.tgz#4bc7fda055a729487897c23579694f6183c91262" + resolved "https://registry.npmjs.org/@lerna/run/-/run-4.0.0.tgz#4bc7fda055a729487897c23579694f6183c91262" integrity sha512-9giulCOzlMPzcZS/6Eov6pxE9gNTyaXk0Man+iCIdGJNMrCnW7Dme0Z229WWP/UoxDKg71F2tMsVVGDiRd8fFQ== dependencies: "@lerna/command" "4.0.0" @@ -1190,7 +1374,7 @@ "@lerna/symlink-binary@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-4.0.0.tgz#21009f62d53a425f136cb4c1a32c6b2a0cc02d47" + resolved "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-4.0.0.tgz#21009f62d53a425f136cb4c1a32c6b2a0cc02d47" integrity sha512-zualodWC4q1QQc1pkz969hcFeWXOsVYZC5AWVtAPTDfLl+TwM7eG/O6oP+Rr3fFowspxo6b1TQ6sYfDV6HXNWA== dependencies: "@lerna/create-symlink" "4.0.0" @@ -1200,7 +1384,7 @@ "@lerna/symlink-dependencies@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-4.0.0.tgz#8910eca084ae062642d0490d8972cf2d98e9ebbd" + resolved "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-4.0.0.tgz#8910eca084ae062642d0490d8972cf2d98e9ebbd" integrity sha512-BABo0MjeUHNAe2FNGty1eantWp8u83BHSeIMPDxNq0MuW2K3CiQRaeWT3EGPAzXpGt0+hVzBrA6+OT0GPn7Yuw== dependencies: "@lerna/create-symlink" "4.0.0" @@ -1212,19 +1396,19 @@ "@lerna/timer@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-4.0.0.tgz#a52e51bfcd39bfd768988049ace7b15c1fd7a6da" + resolved "https://registry.npmjs.org/@lerna/timer/-/timer-4.0.0.tgz#a52e51bfcd39bfd768988049ace7b15c1fd7a6da" integrity sha512-WFsnlaE7SdOvjuyd05oKt8Leg3ENHICnvX3uYKKdByA+S3g+TCz38JsNs7OUZVt+ba63nC2nbXDlUnuT2Xbsfg== "@lerna/validation-error@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-4.0.0.tgz#af9d62fe8304eaa2eb9a6ba1394f9aa807026d35" + resolved "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-4.0.0.tgz#af9d62fe8304eaa2eb9a6ba1394f9aa807026d35" integrity sha512-1rBOM5/koiVWlRi3V6dB863E1YzJS8v41UtsHgMr6gB2ncJ2LsQtMKlJpi3voqcgh41H8UsPXR58RrrpPpufyw== dependencies: npmlog "^4.1.2" "@lerna/version@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-4.0.0.tgz#532659ec6154d8a8789c5ab53878663e244e3228" + resolved "https://registry.npmjs.org/@lerna/version/-/version-4.0.0.tgz#532659ec6154d8a8789c5ab53878663e244e3228" integrity sha512-otUgiqs5W9zGWJZSCCMRV/2Zm2A9q9JwSDS7s/tlKq4mWCYriWo7+wsHEA/nPTMDyYyBO5oyZDj+3X50KDUzeA== dependencies: "@lerna/check-working-tree" "4.0.0" @@ -1256,7 +1440,7 @@ "@lerna/write-log-file@4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-4.0.0.tgz#18221a38a6a307d6b0a5844dd592ad53fa27091e" + resolved "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-4.0.0.tgz#18221a38a6a307d6b0a5844dd592ad53fa27091e" integrity sha512-XRG5BloiArpXRakcnPHmEHJp+4AtnhRtpDIHSghmXD5EichI1uD73J7FgPp30mm2pDRq3FdqB0NbwSEsJ9xFQg== dependencies: npmlog "^4.1.2" @@ -1264,7 +1448,7 @@ "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -1272,25 +1456,25 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@npmcli/ci-detect@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.3.0.tgz#6c1d2c625fb6ef1b9dea85ad0a5afcbef85ef22a" - integrity sha512-oN3y7FAROHhrAt7Rr7PnTSwrHrZVRTS2ZbyxeQwSSYD0ifwM3YNgQqbaRmjcWoPyq77MjchusjJDspbzMmip1Q== + version "1.4.0" + resolved "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" + integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== "@npmcli/fs@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== dependencies: "@gar/promisify" "^1.0.1" @@ -1298,7 +1482,7 @@ "@npmcli/git@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" + resolved "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== dependencies: "@npmcli/promise-spawn" "^1.3.2" @@ -1312,7 +1496,7 @@ "@npmcli/installed-package-contents@^1.0.6": version "1.0.7" - resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + resolved "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== dependencies: npm-bundled "^1.1.1" @@ -1320,7 +1504,7 @@ "@npmcli/move-file@^1.0.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== dependencies: mkdirp "^1.0.4" @@ -1328,19 +1512,19 @@ "@npmcli/node-gyp@^1.0.2": version "1.0.3" - resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" + resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": version "1.3.2" - resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" + resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== dependencies: infer-owner "^1.0.4" "@npmcli/run-script@^1.8.2": version "1.8.6" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" + resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== dependencies: "@npmcli/node-gyp" "^1.0.2" @@ -1350,14 +1534,14 @@ "@octokit/auth-token@^2.4.0", "@octokit/auth-token@^2.4.4": version "2.5.0" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== dependencies: "@octokit/types" "^6.0.3" "@octokit/core@^3.5.1": version "3.5.1" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== dependencies: "@octokit/auth-token" "^2.4.4" @@ -1370,7 +1554,7 @@ "@octokit/endpoint@^6.0.1": version "6.0.12" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== dependencies: "@octokit/types" "^6.0.3" @@ -1379,61 +1563,61 @@ "@octokit/graphql@^4.3.1", "@octokit/graphql@^4.5.8": version "4.8.0" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== dependencies: "@octokit/request" "^5.6.0" "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^10.6.4": - version "10.6.4" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-10.6.4.tgz#c8b5b1f5c60ab7c62858abe2ef57bc709f426a30" - integrity sha512-JVmwWzYTIs6jACYOwD6zu5rdrqGIYsiAsLzTCxdrWIPNKNVjEF6vPTL20shmgJ4qZsq7WPBcLXLsaQD+NLChfg== +"@octokit/openapi-types@^11.2.0": + version "11.2.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6" + integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== "@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" + resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== "@octokit/plugin-paginate-rest@^1.1.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" integrity sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q== dependencies: "@octokit/types" "^2.0.1" -"@octokit/plugin-paginate-rest@^2.16.4": - version "2.16.7" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.7.tgz#d25b6e650ba5a007002986f5fda66958d44e70a4" - integrity sha512-TMlyVhMPx6La1Ud4PSY4YxqAvb9YPEMs/7R1nBSbsw4wNqG73aBqls0r0dRRCWe5Pm0ZUGS9a94N46iAxlOR8A== +"@octokit/plugin-paginate-rest@^2.16.8": + version "2.17.0" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7" + integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== dependencies: - "@octokit/types" "^6.31.3" + "@octokit/types" "^6.34.0" "@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.4": version "1.0.4" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@2.4.0": version "2.4.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e" integrity sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ== dependencies: "@octokit/types" "^2.0.1" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@5.11.4": - version "5.11.4" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.11.4.tgz#221dedcbdc45d6bfa54228d469e8c34acb4e0e34" - integrity sha512-iS+GYTijrPUiEiLoDsGJhrbXIvOPfm2+schvr+FxNMs7PeE9Nl4bAMhE8ftfNX3Z1xLxSKwEZh0O7GbWurX5HQ== +"@octokit/plugin-rest-endpoint-methods@^5.12.0": + version "5.13.0" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba" + integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== dependencies: - "@octokit/types" "^6.31.2" + "@octokit/types" "^6.34.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" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA== dependencies: "@octokit/types" "^2.0.0" @@ -1442,7 +1626,7 @@ "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== dependencies: "@octokit/types" "^6.0.3" @@ -1451,7 +1635,7 @@ "@octokit/request@^5.2.0", "@octokit/request@^5.6.0": version "5.6.2" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.2.tgz#1aa74d5da7b9e04ac60ef232edd9a7438dcf32d8" + resolved "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz#1aa74d5da7b9e04ac60ef232edd9a7438dcf32d8" integrity sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA== dependencies: "@octokit/endpoint" "^6.0.1" @@ -1463,7 +1647,7 @@ "@octokit/rest@^16.43.1": version "16.43.2" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b" integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ== dependencies: "@octokit/auth-token" "^2.4.0" @@ -1483,54 +1667,61 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^18.1.0", "@octokit/rest@^18.11.4": - version "18.11.4" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.11.4.tgz#9fb6d826244554fbf8c110b9064018d7198eec51" - integrity sha512-QplypCyYxqMK05JdMSm/bDWZO8VWWaBdzQ9tbF9rEV9rIEiICh+v6q+Vu/Y5hdze8JJaxfUC+PBC7vrnEkZvZg== +"@octokit/rest@^18.1.0", "@octokit/rest@^18.12.0": + version "18.12.0" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" + integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== dependencies: "@octokit/core" "^3.5.1" - "@octokit/plugin-paginate-rest" "^2.16.4" + "@octokit/plugin-paginate-rest" "^2.16.8" "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "5.11.4" + "@octokit/plugin-rest-endpoint-methods" "^5.12.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" + resolved "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.31.2", "@octokit/types@^6.31.3": - version "6.31.3" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.31.3.tgz#14c2961baea853b2bf148d892256357a936343f8" - integrity sha512-IUG3uMpsLHrtEL6sCVXbxCgnbKcgpkS4K7gVEytLDvYYalkK3XcuMCHK1YPD8xJglSJAOAbL4MgXp47rS9G49w== +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": + version "6.34.0" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" + integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== dependencies: - "@octokit/openapi-types" "^10.6.4" + "@octokit/openapi-types" "^11.2.0" "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1", "@sinonjs/commons@^1.8.3": version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== dependencies: "@sinonjs/commons" "^1.7.0" "@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.2": version "7.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/samsam@^5.3.1": version "5.3.1" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" + resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== dependencies: "@sinonjs/commons" "^1.6.0" @@ -1539,7 +1730,7 @@ "@sinonjs/samsam@^6.0.2": version "6.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" + resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== dependencies: "@sinonjs/commons" "^1.6.0" @@ -1548,49 +1739,49 @@ "@sinonjs/text-encoding@^0.7.1": version "0.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== "@tootallnate/once@1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@tsconfig/node10@^1.0.7": version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== "@tsconfig/node12@^1.0.7": version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== "@tsconfig/node14@^1.0.0": version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== "@tsconfig/node16@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== "@types/archiver@^5.3.0": version "5.3.0" - resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-5.3.0.tgz#2b34ba56d4d7102d256b922c7e91e09eab79db6f" + resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.0.tgz#2b34ba56d4d7102d256b922c7e91e09eab79db6f" integrity sha512-qJ79qsmq7O/k9FYwsF6O1xVA1PeLV+9Bh3TYkVCu3VzMR6vN9JQkgEOh/rrQ0R+F4Ta+R3thHGewxQtFglwVfg== dependencies: "@types/glob" "*" -"@types/aws-lambda@^8.10.83": - version "8.10.83" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.83.tgz#66db06cedb7476e860e8655e4387fd2e4385433a" - integrity sha512-7YsLv/B8rF7K7jYAGmYBxLq3QU+hQV7qNJBMcSCmJCTcXuzoTKGBX8d4v9CsVs0SOKBSAErXG7rtk8jVxiP30g== +"@types/aws-lambda@^8.10.85": + version "8.10.85" + resolved "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.85.tgz#26cd76897b1972247cbc1a34b6f21d023e987437" + integrity sha512-cMRXVxb+NMb6EekKel1fPBfz2ZqE5cGhIS14G7FVUM4Bqilx0lHKnZbsDLWLSeckDpkvlp5six2F7UWyEEJSoQ== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7": version "7.1.16" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== dependencies: "@babel/parser" "^7.1.0" @@ -1601,14 +1792,14 @@ "@types/babel__generator@*": version "7.6.3" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" @@ -1616,251 +1807,271 @@ "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== dependencies: "@babel/types" "^7.3.0" -"@types/eslint@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a" - integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A== +"@types/changelog-parser@^2.7.1": + version "2.7.1" + resolved "https://registry.npmjs.org/@types/changelog-parser/-/changelog-parser-2.7.1.tgz#da124373fc8abfb6951fef83718ea5f041fea527" + integrity sha512-OFZB7OlG6nrkcnvJhcyV2Zm/PUGk40oHyfaEBRjlm+ghrKxbFQI+xao/IzYL0G72fpLCTGGs3USrhe38/FF6QQ== + +"@types/eslint@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*": version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== "@types/fs-extra@^8.1.2": version "8.1.2" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.2.tgz#7125cc2e4bdd9bd2fc83005ffdb1d0ba00cca61f" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz#7125cc2e4bdd9bd2fc83005ffdb1d0ba00cca61f" integrity sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg== dependencies: "@types/node" "*" "@types/fs-extra@^9.0.13": version "9.0.13" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.4": - version "7.1.4" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" - integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== +"@types/glob@*", "@types/glob@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" "@types/node" "*" "@types/graceful-fs@^4.1.2": version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^26.0.24": version "26.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + resolved "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== dependencies: jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/jest@^27.0.2": + version "27.0.2" + resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" + integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.9": version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash@^4.14.175": - version "4.14.175" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45" - integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw== +"@types/lodash@^4.14.177": + version "4.14.177" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578" + integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw== "@types/md5@^2.3.1": version "2.3.1" - resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.1.tgz#010bcf3bb50a2cff3a574cb1c0b4051a9c67d6bc" + resolved "https://registry.npmjs.org/@types/md5/-/md5-2.3.1.tgz#010bcf3bb50a2cff3a574cb1c0b4051a9c67d6bc" integrity sha512-OK3oe+ALIoPSo262lnhAYwpqFNXbiwH2a+0+Z5YBnkQEwWD8fk5+PIeRhYA48PzvX9I4SGNpWy+9bLj8qz92RQ== dependencies: "@types/node" "*" "@types/mime@^2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== "@types/minimatch@*", "@types/minimatch@^3.0.3", "@types/minimatch@^3.0.5": version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/minimist@^1.2.0": version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/mock-fs@^4.13.1": version "4.13.1" - resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be" + resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be" integrity sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA== dependencies: "@types/node" "*" "@types/mockery@^1.4.30": version "1.4.30" - resolved "https://registry.yarnpkg.com/@types/mockery/-/mockery-1.4.30.tgz#25f07fa7340371c7ee0fb9239511a34e0a19d5b7" + resolved "https://registry.npmjs.org/@types/mockery/-/mockery-1.4.30.tgz#25f07fa7340371c7ee0fb9239511a34e0a19d5b7" integrity sha512-uv53RrNdhbkV/3VmVCtfImfYCWC3GTTRn3R11Whni3EJ+gb178tkZBVNj2edLY5CMrB749dQi+SJkg87jsN8UQ== "@types/node@*", "@types/node@>= 8", "@types/node@^16.9.2": - version "16.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" - integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== + version "16.11.7" + resolved "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" + integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== "@types/node@^10.17.60": version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + resolved "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== "@types/normalize-package-data@^2.4.0": version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== "@types/parse-json@^4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.0.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" - integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== +"@types/prettier@^2.0.0", "@types/prettier@^2.1.5": + version "2.4.2" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" + integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== "@types/promptly@^3.0.2": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/promptly/-/promptly-3.0.2.tgz#598674d4b78b3dffcb2d756b344f28a2cf7459f8" + resolved "https://registry.npmjs.org/@types/promptly/-/promptly-3.0.2.tgz#598674d4b78b3dffcb2d756b344f28a2cf7459f8" integrity sha512-cJFwE7d8GlraY+DJoZ0NhpoJ55slkcbNsGIKMY0H+5h0xaGqXBqXz9zeu+Ey9KfN1UiHQXiIT0GroxyPYMPP/w== dependencies: "@types/node" "*" "@types/proxyquire@^1.3.28": version "1.3.28" - resolved "https://registry.yarnpkg.com/@types/proxyquire/-/proxyquire-1.3.28.tgz#05a647bb0d8fe48fc8edcc193e43cc79310faa7d" + resolved "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.28.tgz#05a647bb0d8fe48fc8edcc193e43cc79310faa7d" integrity sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q== "@types/punycode@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" + resolved "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/semver@^7.3.8": - version "7.3.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59" - integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now== +"@types/semver@^7.3.9": + version "7.3.9" + resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== "@types/sinon@^9.0.11": version "9.0.11" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.11.tgz#7af202dda5253a847b511c929d8b6dda170562eb" + resolved "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz#7af202dda5253a847b511c929d8b6dda170562eb" integrity sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg== dependencies: "@types/sinonjs__fake-timers" "*" "@types/sinonjs__fake-timers@*": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" - integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== + version "8.1.0" + resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.0.tgz#443f49b74f1f57e20d9abac7e18b59e9ee98c5cf" + integrity sha512-TZ3vsL7wvXRNTRehor/zKtyWX9Ew3TrT20QQHPx+rieOJivRntZntWhUu1/qKnC8FK4q++RiEl/kje+PAVHhfg== "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/string-width@^4.0.1": version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/string-width/-/string-width-4.0.1.tgz#a02e22c305d0b550f8d8da12b5b13e05123dc7b8" + resolved "https://registry.npmjs.org/@types/string-width/-/string-width-4.0.1.tgz#a02e22c305d0b550f8d8da12b5b13e05123dc7b8" integrity sha512-zsZXP4RSmw3TOXf2eut1xxb7Gto7I+BrB0WxwdRaEdCBnUbAQa57yZf/OWcAfop9m7t6Zd0pw9tV2ghpJXJPZg== dependencies: string-width "*" "@types/table@^6.0.0": version "6.3.2" - resolved "https://registry.yarnpkg.com/@types/table/-/table-6.3.2.tgz#e18ad2594400d81c3da28c31b342eb5a0d87a8e7" + resolved "https://registry.npmjs.org/@types/table/-/table-6.3.2.tgz#e18ad2594400d81c3da28c31b342eb5a0d87a8e7" integrity sha512-GJ82z3vQbx2BhiUo12w2A3lyBpXPJrGHjQ7iS5aH925098w8ojqiWBhgOUy97JS2PKLmRCTLT0sI+gJI4futig== dependencies: table "*" "@types/uuid@^8.3.1": version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== "@types/wrap-ansi@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + resolved "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== "@types/yaml@1.9.6": version "1.9.6" - resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.9.6.tgz#9e30a14aecbba978ad7156d03201ecd97478a712" + resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.6.tgz#9e30a14aecbba978ad7156d03201ecd97478a712" integrity sha512-VKOCuDN57wngmyQnRqcn4vuGWCXViISHv+UCCjrKcf1yt4zyfMmOGlZDI2ucTHK72V8ki+sd7h21OZL6O5S52A== dependencies: yaml "*" "@types/yaml@1.9.7", "@types/yaml@^1.9.7": version "1.9.7" - resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" + resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" integrity sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA== dependencies: yaml "*" "@types/yargs-parser@*": version "20.2.1" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== "@types/yargs@^15.0.0", "@types/yargs@^15.0.14": version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== dependencies: "@types/yargs-parser" "*" +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + "@types/yarnpkg__lockfile@^1.1.5": version "1.1.5" - resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229" + resolved "https://registry.npmjs.org/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229" integrity sha512-8NYnGOctzsI4W0ApsP/BIHD/LnxpJ6XaGf2AZmz4EyDYJMxtprN4279dLNI1CPZcwC9H18qYcaFv4bXi0wmokg== "@typescript-eslint/eslint-plugin@^4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== dependencies: "@typescript-eslint/experimental-utils" "4.33.0" @@ -1874,7 +2085,7 @@ "@typescript-eslint/experimental-utils@4.33.0", "@typescript-eslint/experimental-utils@^4.0.1": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== dependencies: "@types/json-schema" "^7.0.7" @@ -1886,7 +2097,7 @@ "@typescript-eslint/parser@^4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== dependencies: "@typescript-eslint/scope-manager" "4.33.0" @@ -1896,7 +2107,7 @@ "@typescript-eslint/scope-manager@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== dependencies: "@typescript-eslint/types" "4.33.0" @@ -1904,12 +2115,12 @@ "@typescript-eslint/types@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== "@typescript-eslint/typescript-estree@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== dependencies: "@typescript-eslint/types" "4.33.0" @@ -1922,7 +2133,7 @@ "@typescript-eslint/visitor-keys@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== dependencies: "@typescript-eslint/types" "4.33.0" @@ -1930,17 +2141,17 @@ "@xmldom/xmldom@^0.7.5": version "0.7.5" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" + resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== "@yarnpkg/lockfile@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== JSONStream@^1.0.4: version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== dependencies: jsonparse "^1.2.0" @@ -1948,17 +2159,17 @@ JSONStream@^1.0.4: abab@^2.0.3, abab@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== abbrev@1: version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== acorn-globals@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: acorn "^7.1.1" @@ -1966,44 +2177,44 @@ acorn-globals@^6.0.0: acorn-jsx@^5.3.1: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^7.1.1: version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn-walk@^8.1.1: version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1: version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== add-stream@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" + resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" agentkeepalive@^4.1.3: version "4.1.4" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== dependencies: debug "^4.1.0" @@ -2012,7 +2223,7 @@ agentkeepalive@^4.1.3: aggregate-error@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: clean-stack "^2.0.0" @@ -2020,7 +2231,7 @@ aggregate-error@^3.0.0: ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -2029,9 +2240,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.6.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764" - integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw== + version "8.8.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.8.1.tgz#e73dd88eeb4b10bbcd82bee136e6fbe801664d18" + integrity sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2040,56 +2251,56 @@ ajv@^8.0.1: ansi-colors@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + anymatch@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== dependencies: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.3: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" @@ -2097,29 +2308,29 @@ anymatch@^3.0.3: 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" + resolved "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== append-transform@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + resolved "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== dependencies: default-require-extensions "^3.0.0" aproba@^1.0.3: version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== aproba@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== archiver-utils@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + resolved "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== dependencies: glob "^7.1.4" @@ -2135,7 +2346,7 @@ archiver-utils@^2.1.0: archiver@^5.3.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" + resolved "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== dependencies: archiver-utils "^2.1.0" @@ -2148,12 +2359,12 @@ archiver@^5.3.0: archy@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= are-we-there-yet@~1.1.2: version "1.1.7" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== dependencies: delegates "^1.0.0" @@ -2161,49 +2372,49 @@ are-we-there-yet@~1.1.2: arg@^4.1.0: version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== arr-diff@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= arr-flatten@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + resolved "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== arr-union@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= array-differ@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== array-ify@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.1.3: +array-includes@^3.1.4: version "3.1.4" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== dependencies: call-bind "^1.0.2" @@ -2214,17 +2425,17 @@ array-includes@^3.1.3: array-union@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== array-unique@^0.3.2: version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.4: +array.prototype.flat@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== dependencies: call-bind "^1.0.2" @@ -2233,81 +2444,81 @@ array.prototype.flat@^1.2.4: arrify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= arrify@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== asap@^2.0.0: version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + version "0.2.6" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assign-symbols@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= ast-types@^0.13.2: version "0.13.4" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== dependencies: tslib "^2.0.1" astral-regex@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + version "3.2.2" + resolved "https://registry.npmjs.org/async/-/async-3.2.2.tgz#2eb7671034bb2194d45d30e31e24ec7e7f9670cd" + integrity sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g== asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= at-least-node@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== atob-lite@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" + resolved "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= atob@^2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== available-typed-arrays@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== aws-sdk-mock@^5.4.0: version "5.4.0" - resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.4.0.tgz#1c7abbcb128972f0a3a475d08eff9c67b82a04a9" + resolved "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-5.4.0.tgz#1c7abbcb128972f0a3a475d08eff9c67b82a04a9" integrity sha512-lAks83rzszMBNJ91YYGZT/NEaXUlW1rzeBNPFY4i4ImoL3Xx26Ro4sBUb9TwTnU+8LDCMgVigSEELMPFfTUkmA== dependencies: aws-sdk "^2.928.0" @@ -2315,9 +2526,9 @@ aws-sdk-mock@^5.4.0: traverse "^0.6.6" aws-sdk@^2.596.0, aws-sdk@^2.848.0, aws-sdk@^2.928.0, aws-sdk@^2.979.0: - version "2.1002.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1002.0.tgz#8f2d3143109a08b19385b21433c9a7178b3f5295" - integrity sha512-duG9sJL1RETBXV0ZNx1wCVX/Y2xURXdG+jJo380nOI5xlvxyiZxSDhdYYaxNjT4Jgaz/qzGJ7mbkVFu1zQ3/1w== + version "2.1030.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1030.0.tgz#24a856af3d2b8b37c14a8f59974993661c66fd82" + integrity sha512-to0STOb8DsSGuSsUb/WCbg/UFnMGfIYavnJH5ZlRCHzvCFjTyR+vfE8ku+qIZvfFM4+5MNTQC/Oxfun2X/TuyA== dependencies: buffer "4.9.2" events "1.1.1" @@ -2331,24 +2542,24 @@ aws-sdk@^2.596.0, aws-sdk@^2.848.0, aws-sdk@^2.928.0, aws-sdk@^2.979.0: aws-sign2@~0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== axios@^0.21.1: version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" babel-jest@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== dependencies: "@jest/transform" "^26.6.2" @@ -2360,20 +2571,34 @@ babel-jest@^26.6.3: graceful-fs "^4.2.4" slash "^3.0.0" +babel-jest@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" + integrity sha512-SjIF8hh/ir0peae2D6S6ZKRhUy7q/DnpH7k/V6fT4Bgs/LXXUztOpX4G2tCgq8mLo5HA9mN6NmlFMeYtKmIsTQ== + dependencies: + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^27.2.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@istanbuljs/load-nyc-config" "^1.0.0" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" babel-plugin-jest-hoist@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== dependencies: "@babel/template" "^7.3.3" @@ -2381,9 +2606,19 @@ babel-plugin-jest-hoist@^26.6.2: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^27.2.0: + version "27.2.0" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz#79f37d43f7e5c4fdc4b2ca3e10cc6cf545626277" + integrity sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw== + 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@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -2401,25 +2636,33 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== dependencies: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" +babel-preset-jest@^27.2.0: + version "27.2.0" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz#556bbbf340608fed5670ab0ea0c8ef2449fba885" + integrity sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg== + dependencies: + babel-plugin-jest-hoist "^27.2.0" + babel-preset-current-node-syntax "^1.0.0" + balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base@^0.11.1: version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== dependencies: cache-base "^1.0.1" @@ -2432,19 +2675,24 @@ base@^0.11.1: bcrypt-pbkdf@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= dependencies: tweetnacl "^0.14.3" before-after-hook@^2.0.0, before-after-hook@^2.2.0: version "2.2.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + bl@^4.0.3: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -2453,12 +2701,12 @@ bl@^4.0.3: bluebird@^3.5.0: version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -2466,7 +2714,7 @@ brace-expansion@^1.1.7: braces@^2.3.1: version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== dependencies: arr-flatten "^1.1.0" @@ -2480,61 +2728,61 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" browser-process-hrtime@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.16.6: - version "4.17.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.3.tgz#2844cd6eebe14d12384b0122d217550160d2d624" - integrity sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ== +browserslist@^4.17.5: + version "4.18.1" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" + integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== dependencies: - caniuse-lite "^1.0.30001264" - electron-to-chromium "^1.3.857" + caniuse-lite "^1.0.30001280" + electron-to-chromium "^1.3.896" escalade "^3.1.1" - node-releases "^1.1.77" - picocolors "^0.2.1" + node-releases "^2.0.1" + picocolors "^1.0.0" bs-logger@0.x: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" btoa-lite@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" + resolved "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-from@1.x, buffer-from@^1.0.0: +buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@4.9.2: version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" @@ -2543,7 +2791,7 @@ buffer@4.9.2: buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -2551,27 +2799,27 @@ buffer@^5.5.0: builtins@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= byline@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + resolved "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= byte-size@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" + resolved "https://registry.npmjs.org/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" + integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== cacache@^15.0.5, cacache@^15.2.0: version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== dependencies: "@npmcli/fs" "^1.0.0" @@ -2595,7 +2843,7 @@ cacache@^15.0.5, cacache@^15.2.0: cache-base@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + resolved "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== dependencies: collection-visit "^1.0.0" @@ -2610,7 +2858,7 @@ cache-base@^1.0.1: caching-transform@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + resolved "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== dependencies: hasha "^5.0.0" @@ -2620,7 +2868,7 @@ caching-transform@^4.0.0: call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -2628,12 +2876,12 @@ call-bind@^1.0.0, call-bind@^1.0.2: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-keys@^6.2.2: version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== dependencies: camelcase "^5.3.1" @@ -2642,46 +2890,46 @@ camelcase-keys@^6.2.2: camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.2.1: + version "6.2.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" + integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== -caniuse-lite@^1.0.30001264: - version "1.0.30001265" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" - integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== +caniuse-lite@^1.0.30001280: + version "1.0.30001282" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" + integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== capture-exit@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== dependencies: rsvp "^4.8.4" case@1.6.3, case@^1.6.3: version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== caseless@~0.12.0: version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= cdk8s-plus@^0.33.0: version "0.33.0" - resolved "https://registry.yarnpkg.com/cdk8s-plus/-/cdk8s-plus-0.33.0.tgz#b2dc56b417ab8261cd4a796f080f04dcd4fe0f66" + resolved "https://registry.npmjs.org/cdk8s-plus/-/cdk8s-plus-0.33.0.tgz#b2dc56b417ab8261cd4a796f080f04dcd4fe0f66" integrity sha512-CnvuNTQS9DH1MYEDizUObMZ3PG7BqshJaG7kkuThmwNgC8QkVGmOkLC9YP8UBDr1mKW+zfV12HCIw93SteRr3g== dependencies: minimatch "^3.0.4" cdk8s@^0.33.0: version "0.33.0" - resolved "https://registry.yarnpkg.com/cdk8s/-/cdk8s-0.33.0.tgz#503b60b98de5fe82b22ac5dee2c351a4ba102deb" + resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-0.33.0.tgz#503b60b98de5fe82b22ac5dee2c351a4ba102deb" integrity sha512-Yoo6RZWZPk6K2JZLiJA22faNVWqTE6Nv+VNgmTFTq4AC8c+eCvq6/xHKcpn78HhTLEPBmczaja7q2MiRc9LTBQ== dependencies: follow-redirects "^1.11.0" @@ -2690,7 +2938,7 @@ cdk8s@^0.33.0: chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -2699,50 +2947,83 @@ chalk@^2.0.0, chalk@^2.4.2: chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" +changelog-parser@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/changelog-parser/-/changelog-parser-2.8.0.tgz#c14293e3e8fab797913c722de965480198650108" + integrity sha512-ZtSwN0hY7t+WpvaXqqXz98RHCNhWX9HsvCRAv1aBLlqJ7BpKtqdM6Nu6JOiUhRAWR7Gov0aN0fUnmflTz0WgZg== + dependencies: + line-reader "^0.2.4" + remove-markdown "^0.2.2" + char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== charenc@0.0.2: version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +chokidar@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== chownr@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== ci-info@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cjs-module-lexer@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + class-utils@^0.3.5: version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + resolved "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== dependencies: arr-union "^3.1.0" @@ -2752,31 +3033,31 @@ class-utils@^0.3.5: clean-stack@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== cli-color@~0.1.6: version "0.1.7" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-0.1.7.tgz#adc3200fa471cc211b0da7f566b71e98b9d67347" + resolved "https://registry.npmjs.org/cli-color/-/cli-color-0.1.7.tgz#adc3200fa471cc211b0da7f566b71e98b9d67347" integrity sha1-rcMgD6RxzCEbDaf1ZrcemLnWc0c= dependencies: es5-ext "0.8.x" cli-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" cli-width@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== cliui@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: string-width "^4.2.0" @@ -2785,7 +3066,7 @@ cliui@^6.0.0: cliui@^7.0.2: version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" @@ -2794,7 +3075,7 @@ cliui@^7.0.2: clone-deep@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: is-plain-object "^2.0.4" @@ -2803,30 +3084,39 @@ clone-deep@^4.0.1: clone@^1.0.2: version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= clone@^2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= cmd-shim@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" + resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== dependencies: mkdirp-infer-owner "^2.0.0" co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -codemaker@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.38.0.tgz#571458cc9f361f5d885e67817a652b4e6ad67470" - integrity sha512-X2CsWa6zCc8VnH84neidddw5NC+0CHh7iTzd1arUsVFc6s+vnB4i05py394nzqvY5c/ckeDCOLDix/L6NmqFEw== +codemaker@^1.44.2: + version "1.44.2" + resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.44.2.tgz#620b093f36d3fc989776abe2b8f1fed8cabcb7c4" + integrity sha512-yS9//oDu07/TXyIsuQRqfqzB8z9Z9hq9sz/kxK5+ibUDiMr4q/k9HDn9UuK+OZPo0wisffYJCxe/9UjrETiy9Q== + dependencies: + camelcase "^6.2.0" + decamelize "^5.0.1" + fs-extra "^9.1.0" + +codemaker@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.45.0.tgz#e10e356ad477aaaf7f883d54520c23f1f3691ef3" + integrity sha512-PsMKXTuIphccwZah2qPg0yw6HisDotg54ctHX/52xk9v9kSKbPuIhNSUonYyuEmfPigjYqVdkEpyoIKooah9kA== dependencies: camelcase "^6.2.0" decamelize "^5.0.1" @@ -2834,12 +3124,12 @@ codemaker@^1.38.0: collect-v8-coverage@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== collection-visit@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= dependencies: map-visit "^1.0.0" @@ -2847,36 +3137,36 @@ collection-visit@^1.0.0: color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colors@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== columnify@^1.5.4: version "1.5.4" - resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + resolved "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= dependencies: strip-ansi "^3.0.0" @@ -2884,24 +3174,24 @@ columnify@^1.5.4: combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@~8.2.0: version "8.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8" + resolved "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8" integrity sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA== commondir@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= commonmark@^0.30.0: version "0.30.0" - resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.30.0.tgz#38811dc7bbf0f59d277ae09054d4d73a332f2e45" + resolved "https://registry.npmjs.org/commonmark/-/commonmark-0.30.0.tgz#38811dc7bbf0f59d277ae09054d4d73a332f2e45" integrity sha512-j1yoUo4gxPND1JWV9xj5ELih0yMv1iCWDG6eEQIPLSWLxzCXiFoyS7kvB+WwU+tZMf4snwJMMtaubV0laFpiBA== dependencies: entities "~2.0" @@ -2911,7 +3201,7 @@ commonmark@^0.30.0: compare-func@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + resolved "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== dependencies: array-ify "^1.0.0" @@ -2919,12 +3209,12 @@ compare-func@^2.0.0: component-emitter@^1.2.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== compress-commons@^4.1.0: version "4.1.1" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== dependencies: buffer-crc32 "^0.2.13" @@ -2934,12 +3224,12 @@ compress-commons@^4.1.0: concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concat-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== dependencies: buffer-from "^1.0.0" @@ -2949,7 +3239,7 @@ concat-stream@^2.0.0: config-chain@^1.1.12: version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" @@ -2957,17 +3247,17 @@ config-chain@^1.1.12: console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constructs@^3.3.69: version "3.3.161" - resolved "https://registry.yarnpkg.com/constructs/-/constructs-3.3.161.tgz#9726b1d450f3b9aca7907230f2248e3fd4058ce4" + resolved "https://registry.npmjs.org/constructs/-/constructs-3.3.161.tgz#9726b1d450f3b9aca7907230f2248e3fd4058ce4" integrity sha512-/27vW3fo0iyb3py4vKI1BduEYmv8vv8uJgLXvI+5F0Jbnn0/E+As2wkGMa7bumhzCd0Ckv/USkAXstGYVXTYQA== conventional-changelog-angular@^5.0.12: version "5.0.13" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== dependencies: compare-func "^2.0.0" @@ -2975,14 +3265,14 @@ conventional-changelog-angular@^5.0.12: conventional-changelog-atom@^2.0.8: version "2.0.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz#a759ec61c22d1c1196925fca88fe3ae89fd7d8de" + resolved "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz#a759ec61c22d1c1196925fca88fe3ae89fd7d8de" integrity sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw== dependencies: q "^1.5.1" conventional-changelog-cli@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-2.1.1.tgz#7a11980bc399938e0509d2adf8e7a0e213eb994e" + resolved "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-2.1.1.tgz#7a11980bc399938e0509d2adf8e7a0e213eb994e" integrity sha512-xMGQdKJ+4XFDDgfX5aK7UNFduvJMbvF5BB+g0OdVhA3rYdYyhctrIE2Al+WYdZeKTdg9YzMWF2iFPT8MupIwng== dependencies: add-stream "^1.0.0" @@ -2993,28 +3283,19 @@ conventional-changelog-cli@^2.1.1: conventional-changelog-codemirror@^2.0.8: version "2.0.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz#398e9530f08ce34ec4640af98eeaf3022eb1f7dc" + resolved "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz#398e9530f08ce34ec4640af98eeaf3022eb1f7dc" integrity sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw== dependencies: q "^1.5.1" conventional-changelog-config-spec@2.1.0, conventional-changelog-config-spec@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz#874a635287ef8b581fd8558532bf655d4fb59f2d" + resolved "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz#874a635287ef8b581fd8558532bf655d4fb59f2d" integrity sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ== -conventional-changelog-conventionalcommits@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz#a02e0b06d11d342fdc0f00c91d78265ed0bc0a62" - integrity sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw== - dependencies: - compare-func "^2.0.0" - lodash "^4.17.15" - q "^1.5.1" - -conventional-changelog-conventionalcommits@^4.5.0: +conventional-changelog-conventionalcommits@4.6.1, conventional-changelog-conventionalcommits@^4.5.0: version "4.6.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.1.tgz#f4c0921937050674e578dc7875f908351ccf4014" + resolved "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.1.tgz#f4c0921937050674e578dc7875f908351ccf4014" integrity sha512-lzWJpPZhbM1R0PIzkwzGBCnAkH5RKJzJfFQZcl/D+2lsJxAwGnDKBqn/F4C1RD31GJNn8NuKWQzAZDAVXPp2Mw== dependencies: compare-func "^2.0.0" @@ -3023,7 +3304,7 @@ conventional-changelog-conventionalcommits@^4.5.0: conventional-changelog-core@^4.2.1, conventional-changelog-core@^4.2.2: version "4.2.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" + resolved "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== dependencies: add-stream "^1.0.0" @@ -3043,35 +3324,35 @@ conventional-changelog-core@^4.2.1, conventional-changelog-core@^4.2.2: conventional-changelog-ember@^2.0.9: version "2.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz#619b37ec708be9e74a220f4dcf79212ae1c92962" + resolved "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz#619b37ec708be9e74a220f4dcf79212ae1c92962" integrity sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A== dependencies: q "^1.5.1" conventional-changelog-eslint@^3.0.9: version "3.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz#689bd0a470e02f7baafe21a495880deea18b7cdb" + resolved "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz#689bd0a470e02f7baafe21a495880deea18b7cdb" integrity sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA== dependencies: q "^1.5.1" conventional-changelog-express@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz#420c9d92a347b72a91544750bffa9387665a6ee8" + resolved "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz#420c9d92a347b72a91544750bffa9387665a6ee8" integrity sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ== dependencies: q "^1.5.1" conventional-changelog-jquery@^3.0.11: version "3.0.11" - resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz#d142207400f51c9e5bb588596598e24bba8994bf" + resolved "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz#d142207400f51c9e5bb588596598e24bba8994bf" integrity sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw== dependencies: q "^1.5.1" conventional-changelog-jshint@^2.0.9: version "2.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz#f2d7f23e6acd4927a238555d92c09b50fe3852ff" + resolved "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz#f2d7f23e6acd4927a238555d92c09b50fe3852ff" integrity sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA== dependencies: compare-func "^2.0.0" @@ -3079,12 +3360,12 @@ conventional-changelog-jshint@^2.0.9: conventional-changelog-preset-loader@^2.3.4: version "2.3.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" + resolved "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== conventional-changelog-writer@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" + resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== dependencies: compare-func "^2.0.0" @@ -3100,7 +3381,7 @@ conventional-changelog-writer@^4.1.0: conventional-changelog-writer@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.0.tgz#c4042f3f1542f2f41d7d2e0d6cad23aba8df8eec" + resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.0.tgz#c4042f3f1542f2f41d7d2e0d6cad23aba8df8eec" integrity sha512-HnDh9QHLNWfL6E1uHz6krZEQOgm8hN7z/m7tT16xwd802fwgMN0Wqd7AQYVkhpsjDUx/99oo+nGgvKF657XP5g== dependencies: conventional-commits-filter "^2.0.7" @@ -3115,7 +3396,7 @@ conventional-changelog-writer@^5.0.0: conventional-changelog@3.1.24, conventional-changelog@^3.1.24: version "3.1.24" - resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-3.1.24.tgz#ebd180b0fd1b2e1f0095c4b04fd088698348a464" + resolved "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz#ebd180b0fd1b2e1f0095c4b04fd088698348a464" integrity sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg== dependencies: conventional-changelog-angular "^5.0.12" @@ -3132,16 +3413,16 @@ conventional-changelog@3.1.24, conventional-changelog@^3.1.24: conventional-commits-filter@^2.0.7: version "2.0.7" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + resolved "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== dependencies: lodash.ismatch "^4.4.0" modify-values "^1.0.0" -conventional-commits-parser@^3.2.0, conventional-commits-parser@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.2.tgz#190fb9900c6e02be0c0bca9b03d57e24982639fd" - integrity sha512-Jr9KAKgqAkwXMRHjxDwO/zOCDKod1XdAESHAGuJX38iZ7ZzVti/tvVoysO0suMsdAObp9NQ2rHSsSbnAqZ5f5g== +conventional-commits-parser@^3.2.0, conventional-commits-parser@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.3.tgz#fc43704698239451e3ef35fd1d8ed644f46bd86e" + integrity sha512-YyRDR7On9H07ICFpRm/igcdjIqebXbvf4Cff+Pf0BrBys1i1EOzx9iFXNlAbdrLAR8jf7bkUYkDAr8pEy0q4Pw== dependencies: JSONStream "^1.0.4" is-text-path "^1.0.1" @@ -3152,7 +3433,7 @@ conventional-commits-parser@^3.2.0, conventional-commits-parser@^3.2.2: conventional-recommended-bump@6.1.0, conventional-recommended-bump@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" + resolved "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== dependencies: concat-stream "^2.0.0" @@ -3166,29 +3447,29 @@ conventional-recommended-bump@6.1.0, conventional-recommended-bump@^6.1.0: convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" copy-descriptor@^0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-util-is@1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= core-util-is@~1.0.0: version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmiconfig@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" @@ -3199,7 +3480,7 @@ cosmiconfig@^7.0.0: crc-32@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" + resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== dependencies: exit-on-epipe "~1.0.1" @@ -3207,7 +3488,7 @@ crc-32@^1.2.0: crc32-stream@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + resolved "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== dependencies: crc-32 "^1.2.0" @@ -3215,12 +3496,12 @@ crc32-stream@^4.0.2: create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" @@ -3231,7 +3512,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -3240,46 +3521,46 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: crypt@0.0.2: version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= cssom@^0.4.4: version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== cssom@~0.3.6: version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== cssstyle@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" dargs@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== dashdash@^1.12.0: version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= dependencies: assert-plus "^1.0.0" data-uri-to-buffer@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== data-urls@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== dependencies: abab "^2.0.3" @@ -3288,48 +3569,48 @@ data-urls@^2.0.0: date-format@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" + resolved "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== date-format@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" + resolved "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== dateformat@^3.0.0: version "3.0.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" debug@^3.2.7: version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" debuglog@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= decamelize-keys@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= dependencies: decamelize "^1.1.0" @@ -3337,32 +3618,32 @@ decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decamelize@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== decimal.js@^10.2.1: version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decode-uri-component@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= dedent@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= deep-equal@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== dependencies: call-bind "^1.0.0" @@ -3383,57 +3664,57 @@ deep-equal@^2.0.5: deep-extend@^0.6.0, deep-extend@~0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== default-require-extensions@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + resolved "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== dependencies: strip-bom "^4.0.0" defaults@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: clone "^1.0.2" define-properties@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + resolved "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= dependencies: is-descriptor "^1.0.0" define-property@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + resolved "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== dependencies: is-descriptor "^1.0.2" @@ -3441,7 +3722,7 @@ define-property@^2.0.2: degenerator@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" + resolved "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== dependencies: ast-types "^0.13.2" @@ -3451,52 +3732,52 @@ degenerator@^3.0.1: delay@5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= delegates@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= depd@^1.1.2, depd@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== detect-indent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= detect-indent@^6.0.0, detect-indent@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== detect-newline@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= detect-newline@^3.0.0, detect-newline@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== dezalgo@^1.0.0: version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= dependencies: asap "^2.0.0" @@ -3504,81 +3785,86 @@ dezalgo@^1.0.0: diff-sequences@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff-sequences@^27.0.6: + version "27.0.6" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" + integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== + diff@^4.0.1, diff@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== diff@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== difflib@~0.2.1: version "0.2.4" - resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e" + resolved "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e" integrity sha1-teMDYabbAjF21WKJLbhZQKcY9H4= dependencies: heap ">= 0.2.0" dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" doctrine@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" domexception@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: webidl-conversions "^5.0.0" dot-prop@^5.1.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== dependencies: is-obj "^2.0.0" dot-prop@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== dependencies: is-obj "^2.0.0" dotenv-json@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" + resolved "https://registry.npmjs.org/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== dotenv@^8.0.0: version "8.6.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== dotgitignore@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" + resolved "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" integrity sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA== dependencies: find-up "^3.0.0" @@ -3586,95 +3872,100 @@ dotgitignore@^2.1.0: dreamopt@~0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.6.0.tgz#d813ccdac8d39d8ad526775514a13dda664d6b4b" + resolved "https://registry.npmjs.org/dreamopt/-/dreamopt-0.6.0.tgz#d813ccdac8d39d8ad526775514a13dda664d6b4b" integrity sha1-2BPM2sjTnYrVJndVFKE92mZNa0s= dependencies: wordwrap ">=0.0.2" duplexer@^0.1.1: version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== ecc-jsbn@~0.1.1: version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.857: - version "1.3.861" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.861.tgz#981e37a79af7a7b29bbaeed36376f4795527de13" - integrity sha512-GZyflmpMnZRdZ1e2yAyvuFwz1MPSVQelwHX4TJZyXypB8NcxdPvPNwy5lOTxnlkrK13EiQzyTPugRSnj6cBgKg== +electron-to-chromium@^1.3.896: + version "1.3.900" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" + integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ== emittery@^0.7.1: version "0.7.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== encoding@^0.1.12: version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" enquirer@^2.3.5: version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1" entities@~2.0: version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + resolved "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== entities@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== env-paths@^2.2.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@^7.7.4: version "7.8.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== err-code@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== dependencies: call-bind "^1.0.2" @@ -3700,7 +3991,7 @@ es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: es-get-iterator@^1.1.1: version "1.1.2" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== dependencies: call-bind "^1.0.2" @@ -3714,7 +4005,7 @@ es-get-iterator@^1.1.1: es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" @@ -3723,139 +4014,145 @@ es-to-primitive@^1.2.1: es5-ext@0.8.x: version "0.8.2" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab" integrity sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs= es6-error@^4.0.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild-android-arm64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.4.tgz#5178a20d2b7aba741a31c19609f9e67b346996b9" - integrity sha512-elDJt+jNyoHFId0/dKsuVYUPke3EcquIyUwzJCH17a3ERglN3A9aMBI5zbz+xNZ+FbaDNdpn0RaJHCFLbZX+fA== - -esbuild-darwin-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.4.tgz#7a3e66c8e1271b650541b25eed65c84f3564a69d" - integrity sha512-zJQGyHRAdZUXlRzbN7W+7ykmEiGC+bq3Gc4GxKYjjWTgDRSEly98ym+vRNkDjXwXYD3gGzSwvH35+MiHAtWvLA== - -esbuild-darwin-arm64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.4.tgz#793feca6032b2a57ef291eb9b2d33768d60a49d6" - integrity sha512-r8oYvAtqSGq8HNTZCAx4TdLE7jZiGhX9ooGi5AQAey37MA6XNaP8ZNlw9OCpcgpx3ryU2WctXwIqPzkHO7a8dg== - -esbuild-freebsd-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.4.tgz#294aec3c2cf4b41fb6900212fc9c33dd8fbbb4a2" - integrity sha512-u9DRGkn09EN8+lCh6z7FKle7awi17PJRBuAKdRNgSo5ZrH/3m+mYaJK2PR2URHMpAfXiwJX341z231tSdVe3Yw== - -esbuild-freebsd-arm64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.4.tgz#09fe66c751c12f9b976976b1d83f3de594cb2787" - integrity sha512-q3B2k68Uf6gfjATjcK16DqxvjqRQkHL8aPoOfj4op+lSqegdXvBacB1d8jw8PxbWJ8JHpdTLdAVUYU80kotQXA== - -esbuild-linux-32@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.4.tgz#a9f0793d7bcc9cef4f4ffa4398c525877fba5839" - integrity sha512-UUYJPHSiKAO8KoN3Ls/iZtgDLZvK5HarES96aolDPWZnq9FLx4dIHM/x2z4Rxv9IYqQ/DxlPoE2Co1UPBIYYeA== - -esbuild-linux-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.4.tgz#c0d0b4c9d62e3bbf8bdf2cece37403aa6d60fc2e" - integrity sha512-+RnohAKiiUW4UHLGRkNR1AnENW1gCuDWuygEtd4jxTNPIoeC7lbXGor7rtgjj9AdUzFgOEvAXyNNX01kJ8NueQ== - -esbuild-linux-arm64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.4.tgz#1292d97bfa64a08d12728f8a7837bf92776c779b" - integrity sha512-+A188cAdd6QuSRxMIwRrWLjgphQA0LDAQ/ECVlrPVJwnx+1i64NjDZivoqPYLOTkSPIKntiWwMhhf0U5/RrPHQ== - -esbuild-linux-arm@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.4.tgz#186cd9b8885ac132b9953a4a0afe668168debd10" - integrity sha512-BH5gKve4jglS7UPSsfwHSX79I5agC/lm4eKoRUEyo8lwQs89frQSRp2Xup+6SFQnxt3md5EsKcd2Dbkqeb3gPA== - -esbuild-linux-mips64le@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.4.tgz#42049bf72bc586817b4a51cc9e32148d13e5e807" - integrity sha512-0xkwtPaUkG5xMTFGaQPe1AadSe5QAiQuD4Gix1O9k5Xo/U8xGIkw9UFUTvfEUeu71vFb6ZgsIacfP1NLoFjWNw== - -esbuild-linux-ppc64le@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.4.tgz#adf1ce2ef2302757c4383887da6ac4dd25be9d4f" - integrity sha512-E1+oJPP7A+j23GPo3CEpBhGwG1bni4B8IbTA3/3rvzjURwUMZdcN3Fhrz24rnjzdLSHmULtOE4VsbT42h1Om4Q== - -esbuild-openbsd-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.4.tgz#1c8122101898c52a20c8786935cf3eb7a19b83b4" - integrity sha512-xEkI1o5HYxDzbv9jSox0EsDxpwraG09SRiKKv0W8pH6O3bt+zPSlnoK7+I7Q69tkvONkpIq5n2o+c55uq0X7cw== - -esbuild-sunos-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.4.tgz#4ec95faa14a60f295fe485bebffefff408739337" - integrity sha512-bjXUMcODMnB6hQicLBBmmnBl7OMDyVpFahKvHGXJfDChIi5udiIRKCmFUFIRn+AUAKVlfrofRKdyPC7kBsbvGQ== - -esbuild-windows-32@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.4.tgz#3182c380487b797b04d0ec2c80c2945666869080" - integrity sha512-z4CH07pfyVY0XF98TCsGmLxKCl0kyvshKDbdpTekW9f2d+dJqn5mmoUyWhpSVJ0SfYWJg86FoD9nMbbaMVyGdg== - -esbuild-windows-64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.4.tgz#b9e995f92d81f433a04f33611e603e82f9232e69" - integrity sha512-uVL11vORRPjocGLYam67rwFLd0LvkrHEs+JG+1oJN4UD9MQmNGZPa4gBHo6hDpF+kqRJ9kXgQSeDqUyRy0tj/Q== - -esbuild-windows-arm64@0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.4.tgz#fb239532f07b764d158f4cc787178ef4c6fadb5c" - integrity sha512-vA6GLvptgftRcDcWngD5cMlL4f4LbL8JjU2UMT9yJ0MT5ra6hdZNFWnOeOoEtY4GtJ6OjZ0i+81sTqhAB0fMkg== - -esbuild@^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.4.tgz#ce2deb56c4fb360938311cbfc67f8e467bb6841b" - integrity sha512-wMA5eUwpavTBiNl+It6j8OQuKVh69l6z4DKDLzoTIqC+gChnPpcmqdA8WNHptUHRnfyML+mKEQPlW7Mybj8gHg== +esbuild-android-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.14.tgz#c85083ece26be3d67e6c720e088968a98409e023" + integrity sha512-Q+Xhfp827r+ma8/DJgpMRUbDZfefsk13oePFEXEIJ4gxFbNv5+vyiYXYuKm43/+++EJXpnaYmEnu4hAKbAWYbA== + +esbuild-darwin-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.14.tgz#8e4e237ad847cc54a1d3a5caee26a746b9f0b81f" + integrity sha512-YmOhRns6QBNSjpVdTahi/yZ8dscx9ai7a6OY6z5ACgOuQuaQ2Qk2qgJ0/siZ6LgD0gJFMV8UINFV5oky5TFNQQ== + +esbuild-darwin-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.14.tgz#b3b5ebd40b2cb06ee0f6fb342dd4bdcca54ad273" + integrity sha512-Lp00VTli2jqZghSa68fx3fEFCPsO1hK59RMo1PRap5RUjhf55OmaZTZYnCDI0FVlCtt+gBwX5qwFt4lc6tI1xg== + +esbuild-freebsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.14.tgz#175ecb2fa8141428cf70ea2d5f4c27534bad53e0" + integrity sha512-BKosI3jtvTfnmsCW37B1TyxMUjkRWKqopR0CE9AF2ratdpkxdR24Vpe3gLKNyWiZ7BE96/SO5/YfhbPUzY8wKw== + +esbuild-freebsd-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.14.tgz#a7d64e41d1fa581f8db7775e5200f18e67d70c4d" + integrity sha512-yd2uh0yf+fWv5114+SYTl4/1oDWtr4nN5Op+PGxAkMqHfYfLjFKpcxwCo/QOS/0NWqPVE8O41IYZlFhbEN2B8Q== + +esbuild-linux-32@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.14.tgz#14bdd4f6b6cfd35c65c835894651ba335c2117da" + integrity sha512-a8rOnS1oWSfkkYWXoD2yXNV4BdbDKA7PNVQ1klqkY9SoSApL7io66w5H44mTLsfyw7G6Z2vLlaLI2nz9MMAowA== + +esbuild-linux-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.14.tgz#7fd56851b2982fdd0cd8447ee9858c2c5711708a" + integrity sha512-yPZSoMs9W2MC3Dw+6kflKt5FfQm6Dicex9dGIr1OlHRsn3Hm7yGMUTctlkW53KknnZdOdcdd5upxvbxqymczVQ== + +esbuild-linux-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.14.tgz#a55634d70679ba509adeafd68eebb9fd1ec5af6c" + integrity sha512-Lvo391ln9PzC334e+jJ2S0Rt0cxP47eoH5gFyv/E8HhOnEJTvm7A+RRnMjjHnejELacTTfYgFGQYPjLsi/jObQ== + +esbuild-linux-arm@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.14.tgz#bb96a99677e608b31ff61f37564326d38e846ca2" + integrity sha512-8chZE4pkKRvJ/M/iwsNQ1KqsRg2RyU5eT/x2flNt/f8F2TVrDreR7I0HEeCR50wLla3B1C3wTIOzQBmjuc6uWg== + +esbuild-linux-mips64le@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.14.tgz#6a55362a8fd1e593dea2ecc41877beed8b8184b9" + integrity sha512-MZhgxbmrWbpY3TOE029O6l5tokG9+Yoj2hW7vdit/d/VnmneqeGrSHADuDL6qXM8L5jaCiaivb4VhsyVCpdAbQ== + +esbuild-linux-ppc64le@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.14.tgz#9e0048587ece0a7f184ab147f20d077098045e7f" + integrity sha512-un7KMwS7fX1Un6BjfSZxTT8L5cV/8Uf4SAhM7WYy2XF8o8TI+uRxxD03svZnRNIPsN2J5cl6qV4n7Iwz+yhhVw== + +esbuild-netbsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.14.tgz#dcab16a4bbcfa16e2e8535dadc5f64fdc891c63b" + integrity sha512-5ekKx/YbOmmlTeNxBjh38Uh5TGn5C4uyqN17i67k18pS3J+U2hTVD7rCxcFcRS1AjNWumkVL3jWqYXadFwMS0Q== + +esbuild-openbsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.14.tgz#3c7453b155ebb68dc34d5aec3bd6505337bdda08" + integrity sha512-9bzvwewHjct2Cv5XcVoE1yW5YTW12Sk838EYfA46abgnhxGoFSD1mFcaztp5HHC43AsF+hQxbSFG/RilONARUA== + +esbuild-sunos-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.14.tgz#85addf5fef6b5db154a955d4f2e88953359d75ce" + integrity sha512-mjMrZB76M6FmoiTvj/RGWilrioR7gVwtFBRVugr9qLarXMIU1W/pQx+ieEOtflrW61xo8w1fcxyHsVVGRvoQ0w== + +esbuild-windows-32@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.14.tgz#f77f98f30a5c636c44db2428ecdf9bcbbaedb1a7" + integrity sha512-GZa6mrx2rgfbH/5uHg0Rdw50TuOKbdoKCpEBitzmG5tsXBdce+cOL+iFO5joZc6fDVCLW3Y6tjxmSXRk/v20Hg== + +esbuild-windows-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.14.tgz#bc778674c40d65150d12385e0f23eb3a0badbd0d" + integrity sha512-Lsgqah24bT7ClHjLp/Pj3A9wxjhIAJyWQcrOV4jqXAFikmrp2CspA8IkJgw7HFjx6QrJuhpcKVbCAe/xw0i2yw== + +esbuild-windows-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.14.tgz#91a8dad35ab2c4dd27cd83860742955b25a354d7" + integrity sha512-KP8FHVlWGhM7nzYtURsGnskXb/cBCPTfj0gOKfjKq2tHtYnhDZywsUG57nk7TKhhK0fL11LcejHG3LRW9RF/9A== + +esbuild@^0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.13.14.tgz#98a3f7f42809abdc2b57c84565d0f713382dc1a5" + integrity sha512-xu4D+1ji9x53ocuomcY+KOrwAnWzhBu/wTEjpdgZ8I1c8i5vboYIeigMdzgY1UowYBKa2vZgVgUB32bu7gkxeg== optionalDependencies: - esbuild-android-arm64 "0.13.4" - esbuild-darwin-64 "0.13.4" - esbuild-darwin-arm64 "0.13.4" - esbuild-freebsd-64 "0.13.4" - esbuild-freebsd-arm64 "0.13.4" - esbuild-linux-32 "0.13.4" - esbuild-linux-64 "0.13.4" - esbuild-linux-arm "0.13.4" - esbuild-linux-arm64 "0.13.4" - esbuild-linux-mips64le "0.13.4" - esbuild-linux-ppc64le "0.13.4" - esbuild-openbsd-64 "0.13.4" - esbuild-sunos-64 "0.13.4" - esbuild-windows-32 "0.13.4" - esbuild-windows-64 "0.13.4" - esbuild-windows-arm64 "0.13.4" + esbuild-android-arm64 "0.13.14" + esbuild-darwin-64 "0.13.14" + esbuild-darwin-arm64 "0.13.14" + esbuild-freebsd-64 "0.13.14" + esbuild-freebsd-arm64 "0.13.14" + esbuild-linux-32 "0.13.14" + esbuild-linux-64 "0.13.14" + esbuild-linux-arm "0.13.14" + esbuild-linux-arm64 "0.13.14" + esbuild-linux-mips64le "0.13.14" + esbuild-linux-ppc64le "0.13.14" + esbuild-netbsd-64 "0.13.14" + esbuild-openbsd-64 "0.13.14" + esbuild-sunos-64 "0.13.14" + esbuild-windows-32 "0.13.14" + esbuild-windows-64 "0.13.14" + esbuild-windows-arm64 "0.13.14" escalade@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^1.8.1: version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== dependencies: esprima "^4.0.1" @@ -3867,7 +4164,7 @@ escodegen@^1.8.1: escodegen@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: esprima "^4.0.1" @@ -3879,12 +4176,12 @@ escodegen@^2.0.0: 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" + resolved "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== eslint-import-resolver-node@^0.3.6: version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: debug "^3.2.7" @@ -3892,7 +4189,7 @@ eslint-import-resolver-node@^0.3.6: eslint-import-resolver-typescript@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" + resolved "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== dependencies: debug "^4.3.1" @@ -3901,53 +4198,52 @@ eslint-import-resolver-typescript@^2.5.0: resolve "^1.20.0" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534" - integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q== +eslint-module-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" + integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== dependencies: debug "^3.2.7" + find-up "^2.1.0" 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" + resolved "https://registry.npmjs.org/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.24.2: - version "2.24.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da" - integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q== +eslint-plugin-import@^2.25.3: + version "2.25.3" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" + integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== dependencies: - array-includes "^3.1.3" - array.prototype.flat "^1.2.4" + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.6.2" - find-up "^2.0.0" + eslint-module-utils "^2.7.1" has "^1.0.3" - is-core-module "^2.6.0" + is-core-module "^2.8.0" + is-glob "^4.0.3" minimatch "^3.0.4" - object.values "^1.1.4" - pkg-up "^2.0.0" - read-pkg-up "^3.0.0" + object.values "^1.1.5" resolve "^1.20.0" tsconfig-paths "^3.11.0" -eslint-plugin-jest@^24.5.2: - version "24.5.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.5.2.tgz#f71f98f27fd18b50f55246ca090f36d1730e36a6" - integrity sha512-lrI3sGAyZi513RRmP08sIW241Ti/zMnn/6wbE4ZBhb3M2pJ9ztaZMnSKSKKBUfotVdwqU8W1KtD8ao2/FR8DIg== +eslint-plugin-jest@^24.7.0: + version "24.7.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz#206ac0833841e59e375170b15f8d0955219c4889" + integrity sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA== dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" 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" + resolved "https://registry.npmjs.org/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" @@ -3959,22 +4255,22 @@ eslint-plugin-node@^11.1.0: eslint-plugin-promise@^4.3.1: version "4.3.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45" + resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45" integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ== eslint-plugin-rulesdir@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.2.0.tgz#0d729e3f11bcb1a18d9b724a29a6d1a082ac2d62" + resolved "https://registry.npmjs.org/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.2.0.tgz#0d729e3f11bcb1a18d9b724a29a6d1a082ac2d62" integrity sha512-PPQPCsPkzF3upl1862swPA1bmDAAHKHmJJ4JTHJ11JCVCU4sycB0K5LLA/Rwr6r4VbnpScvUvHV4hqfdjvFmhQ== eslint-plugin-standard@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" + resolved "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -3982,31 +4278,31 @@ eslint-scope@^5.1.1: eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" eslint-utils@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== dependencies: eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint-visitor-keys@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint@^7.32.0: version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: "@babel/code-frame" "7.12.11" @@ -4052,7 +4348,7 @@ eslint@^7.32.0: espree@^7.3.0, espree@^7.3.1: version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" @@ -4061,56 +4357,56 @@ espree@^7.3.0, espree@^7.3.1: esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== eventemitter3@^4.0.4: version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= exec-sh@^0.3.2: version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== execa@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== dependencies: cross-spawn "^6.0.0" @@ -4123,7 +4419,7 @@ execa@^1.0.0: execa@^4.0.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== dependencies: cross-spawn "^7.0.0" @@ -4138,7 +4434,7 @@ execa@^4.0.0: execa@^5.0.0: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -4153,17 +4449,17 @@ execa@^5.0.0: exit-on-epipe@~1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" + resolved "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== exit@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= expand-brackets@^2.1.4: version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + resolved "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= dependencies: debug "^2.3.3" @@ -4176,7 +4472,7 @@ expand-brackets@^2.1.4: expect@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== dependencies: "@jest/types" "^26.6.2" @@ -4186,16 +4482,28 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +expect@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/expect/-/expect-27.3.1.tgz#d0f170b1f5c8a2009bab0beffd4bb94f043e38e7" + integrity sha512-MrNXV2sL9iDRebWPGOGFdPQRl2eDQNu/uhxIMShjjx74T6kC6jFIkmQ6OqXDtevjGUkyB2IT56RzDBqXf/QPCg== + dependencies: + "@jest/types" "^27.2.5" + ansi-styles "^5.0.0" + jest-get-type "^27.3.1" + jest-matcher-utils "^27.3.1" + jest-message-util "^27.3.1" + jest-regex-util "^27.0.6" + extend-shallow@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= dependencies: assign-symbols "^1.0.0" @@ -4203,12 +4511,12 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: extend@~3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== external-editor@^3.0.3: version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: chardet "^0.7.0" @@ -4217,7 +4525,7 @@ external-editor@^3.0.3: extglob@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + resolved "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== dependencies: array-unique "^0.3.2" @@ -4231,34 +4539,34 @@ extglob@^2.0.4: extsprintf@1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + version "1.4.1" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== -fast-check@^2.17.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.17.0.tgz#9b9637684332be386219a5f73a4799874da7461c" - integrity sha512-fNNKkxNEJP+27QMcEzF6nbpOYoSZIS0p+TyB+xh/jXqRBxRhLkiZSREly4ruyV8uJi7nwH1YWAhi7OOK5TubRw== +fast-check@^2.19.0: + version "2.19.0" + resolved "https://registry.npmjs.org/fast-check/-/fast-check-2.19.0.tgz#e87666450e417cd163a564bd546ee763d27c0f19" + integrity sha512-qY4Rc0Nljl2aJx2qgbK3o6wPBjL9QvhKdD/VqJgaKd5ewn8l4ViqgDpUHJq/JkHTBnFKomYYvkFkOYGDVTT8bw== dependencies: pure-rand "^5.0.0" fast-deep-equal@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.1.1: version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -4269,62 +4577,62 @@ fast-glob@^3.1.1: fast-json-patch@^2.2.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-2.2.1.tgz#18150d36c9ab65c7209e7d4eb113f4f8eaabe6d9" + resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz#18150d36c9ab65c7209e7d4eb113f4f8eaabe6d9" integrity sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig== dependencies: fast-deep-equal "^2.0.1" fast-json-patch@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.0.tgz#ec8cd9b9c4c564250ec8b9140ef7a55f70acaee6" + resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.0.tgz#ec8cd9b9c4c564250ec8b9140ef7a55f70acaee6" integrity sha512-IhpytlsVTRndz0hU5t0/MGzS/etxLlfrpG5V5M9mVbuj9TrJLWaMfsox9REM5rkuGX0T+5qjpe8XA1o0gZ42nA== fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fastq@^1.6.0: version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== dependencies: bser "2.1.1" figures@^3.0.0, figures@^3.1.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" file-uri-to-path@2: version "2.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== fill-keys@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + resolved "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" integrity sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA= dependencies: is-object "~1.0.1" @@ -4332,7 +4640,7 @@ fill-keys@^1.0.2: fill-range@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= dependencies: extend-shallow "^2.0.1" @@ -4342,19 +4650,19 @@ fill-range@^4.0.0: fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" filter-obj@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= find-cache-dir@^3.2.0: version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== dependencies: commondir "^1.0.1" @@ -4363,21 +4671,21 @@ find-cache-dir@^3.2.0: find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: locate-path "^2.0.0" find-up@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== dependencies: locate-path "^3.0.0" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -4385,7 +4693,7 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -4393,14 +4701,14 @@ find-up@^5.0.0: find-yarn-workspace-root@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + resolved "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== dependencies: micromatch "^4.0.2" flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -4408,32 +4716,32 @@ flat-cache@^3.0.4: flatted@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + resolved "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flatted@^3.1.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" - integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + version "3.2.4" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== follow-redirects@^1.11.0, follow-redirects@^1.14.0: - version "1.14.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== + version "1.14.5" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== for-in@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= foreach@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= foreground-child@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== dependencies: cross-spawn "^7.0.0" @@ -4441,12 +4749,12 @@ foreground-child@^2.0.0: forever-agent@~0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= form-data@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: asynckit "^0.4.0" @@ -4455,7 +4763,7 @@ form-data@^3.0.0: form-data@~2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" @@ -4464,31 +4772,31 @@ form-data@~2.3.2: fragment-cache@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= dependencies: map-cache "^0.2.2" fromentries@^1.2.0: version "1.3.2" - resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== fs-access@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" + resolved "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" integrity sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o= dependencies: null-check "^1.0.0" fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== dependencies: graceful-fs "^4.1.2" @@ -4497,7 +4805,7 @@ fs-extra@^7.0.1: fs-extra@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: graceful-fs "^4.2.0" @@ -4506,7 +4814,7 @@ fs-extra@^8.1.0: fs-extra@^9.1.0: version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== dependencies: at-least-node "^1.0.0" @@ -4516,31 +4824,31 @@ fs-extra@^9.1.0: fs-minipass@^1.2.7: version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: minipass "^2.6.0" fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== dependencies: minipass "^3.0.0" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== ftp@^0.3.10: version "0.3.10" - resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= dependencies: readable-stream "1.1.x" @@ -4548,17 +4856,17 @@ ftp@^0.3.10: function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== functional-red-black-tree@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= gauge@~2.7.3: version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + resolved "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= dependencies: aproba "^1.0.3" @@ -4572,17 +4880,17 @@ gauge@~2.7.3: gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: function-bind "^1.1.1" @@ -4591,12 +4899,12 @@ get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@ get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-pkg-repo@^4.0.0: version "4.2.1" - resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" + resolved "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== dependencies: "@hutson/parse-repository-url" "^3.0.0" @@ -4606,36 +4914,36 @@ get-pkg-repo@^4.0.0: get-port@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== get-stdin@~8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== get-stream@^4.0.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" get-stream@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== get-symbol-description@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: call-bind "^1.0.2" @@ -4643,7 +4951,7 @@ get-symbol-description@^1.0.0: get-uri@3: version "3.0.2" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" + resolved "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== dependencies: "@tootallnate/once" "1" @@ -4655,19 +4963,19 @@ get-uri@3: get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= getpass@^0.1.1: version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= dependencies: assert-plus "^1.0.0" git-raw-commits@^2.0.10, git-raw-commits@^2.0.8: version "2.0.10" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" + resolved "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== dependencies: dargs "^7.0.0" @@ -4678,7 +4986,7 @@ git-raw-commits@^2.0.10, git-raw-commits@^2.0.8: git-remote-origin-url@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" + resolved "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= dependencies: gitconfiglocal "^1.0.0" @@ -4686,7 +4994,7 @@ git-remote-origin-url@^2.0.0: git-semver-tags@^4.0.0, git-semver-tags@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" + resolved "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== dependencies: meow "^8.0.0" @@ -4694,7 +5002,7 @@ git-semver-tags@^4.0.0, git-semver-tags@^4.1.1: git-up@^4.0.0: version "4.0.5" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" + resolved "https://registry.npmjs.org/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== dependencies: is-ssh "^1.3.0" @@ -4702,21 +5010,21 @@ git-up@^4.0.0: git-url-parse@^11.4.4: version "11.6.0" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" + resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== dependencies: git-up "^4.0.0" gitconfiglocal@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" + resolved "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= dependencies: ini "^1.3.2" github-api@^3.4.0: version "3.4.0" - resolved "https://registry.yarnpkg.com/github-api/-/github-api-3.4.0.tgz#5da2f56442d4839d324e9faf0ffb2cf30f7650b8" + resolved "https://registry.npmjs.org/github-api/-/github-api-3.4.0.tgz#5da2f56442d4839d324e9faf0ffb2cf30f7650b8" integrity sha512-2yYqYS6Uy4br1nw0D3VrlYWxtGTkUhIZrumBrcBwKdBOzMT8roAe8IvI6kjIOkxqxapKR5GkEsHtz3Du/voOpA== dependencies: axios "^0.21.1" @@ -4724,16 +5032,16 @@ github-api@^3.4.0: js-base64 "^2.1.9" utf8 "^2.1.1" -glob-parent@^5.1.1, glob-parent@^5.1.2: +glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0, glob@~7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" @@ -4745,19 +5053,19 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, gl globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" - integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== + version "13.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== dependencies: type-fest "^0.20.2" globby@^11.0.2, globby@^11.0.3: version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" @@ -4769,17 +5077,17 @@ globby@^11.0.2, globby@^11.0.3: graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.8: version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== growly@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= handlebars@^4.7.6: version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== dependencies: minimist "^1.2.5" @@ -4791,12 +5099,12 @@ handlebars@^4.7.6: har-schema@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.1.3: version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: ajv "^6.12.3" @@ -4804,44 +5112,44 @@ har-validator@~5.1.3: hard-rejection@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== has-bigints@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: has-symbols "^1.0.2" has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= has-value@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: get-value "^2.0.3" @@ -4850,7 +5158,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + resolved "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= dependencies: get-value "^2.0.6" @@ -4859,12 +5167,12 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + resolved "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= has-values@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + resolved "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= dependencies: is-number "^3.0.0" @@ -4872,14 +5180,14 @@ has-values@^1.0.0: has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" hasha@^5.0.0: version "5.2.2" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + resolved "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== dependencies: is-stream "^2.0.0" @@ -4887,52 +5195,52 @@ hasha@^5.0.0: "heap@>= 0.2.0": version "0.2.6" - resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" + resolved "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" integrity sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw= hosted-git-info@^2.1.4: version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== dependencies: lru-cache "^6.0.0" html-encoding-sniffer@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== dependencies: whatwg-encoding "^1.0.5" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== http-cache-semantics@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: depd "~1.1.2" inherits "2.0.4" - setprototypeof "1.1.1" + setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" + toidentifier "1.0.1" http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== dependencies: "@tootallnate/once" "1" @@ -4941,7 +5249,7 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: http-signature@~1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= dependencies: assert-plus "^1.0.0" @@ -4950,7 +5258,7 @@ http-signature@~1.2.0: https-proxy-agent@5, https-proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== dependencies: agent-base "6" @@ -4958,70 +5266,70 @@ https-proxy-agent@5, https-proxy-agent@^5.0.0: human-signals@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== humanize-ms@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= dependencies: ms "^2.0.0" iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" iconv-lite@^0.6.2: version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" ieee754@1.1.13: version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore-walk@^3.0.3: version "3.0.4" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== dependencies: minimatch "^3.0.4" ignore@^4.0.6: version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + resolved "https://registry.npmjs.org/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.8, ignore@~5.1.8: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8, ignore@^5.1.9, ignore@~5.1.8: + version "5.1.9" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" + integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== immediate@~3.0.5: version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -5029,7 +5337,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: import-local@^3.0.2: version "3.0.3" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" integrity sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA== dependencies: pkg-dir "^4.2.0" @@ -5037,22 +5345,22 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= indent-string@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== infer-owner@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" @@ -5060,22 +5368,22 @@ inflight@^1.0.4: inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@^1.3.2, ini@^1.3.4: version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ini@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== init-package-json@^2.0.2: version "2.0.5" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" + resolved "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== dependencies: npm-package-arg "^8.1.5" @@ -5088,7 +5396,7 @@ init-package-json@^2.0.2: inquirer@^7.3.3: version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== dependencies: ansi-escapes "^4.2.1" @@ -5107,7 +5415,7 @@ inquirer@^7.3.3: internal-slot@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: get-intrinsic "^1.1.0" @@ -5116,26 +5424,26 @@ internal-slot@^1.0.3: ip@^1.1.5: version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= is-accessor-descriptor@^0.1.6: version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= dependencies: kind-of "^3.0.2" is-accessor-descriptor@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== dependencies: kind-of "^6.0.0" is-arguments@^1.0.4, is-arguments@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: call-bind "^1.0.2" @@ -5143,19 +5451,26 @@ is-arguments@^1.0.4, is-arguments@^1.1.0: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-bigint@^1.0.1: version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: has-bigints "^1.0.1" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.1.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" @@ -5163,52 +5478,52 @@ is-boolean-object@^1.1.0: is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== is-ci@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= dependencies: kind-of "^3.0.2" is-data-descriptor@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== dependencies: kind-of "^6.0.0" is-date-object@^1.0.1, is-date-object@^1.0.2: version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" is-descriptor@^0.1.0: version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== dependencies: is-accessor-descriptor "^0.1.6" @@ -5217,7 +5532,7 @@ is-descriptor@^0.1.0: is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== dependencies: is-accessor-descriptor "^1.0.0" @@ -5226,117 +5541,117 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-docker@^2.0.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= is-extendable@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== dependencies: is-plain-object "^2.0.4" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-lambda@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== is-negative-zero@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-number-object@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== dependencies: has-tostringtag "^1.0.0" is-number@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= dependencies: kind-of "^3.0.2" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-obj@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== is-object@~1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" + resolved "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= is-plain-obj@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-plain-object@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== is-potential-custom-element-name@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-regex@^1.1.1, is-regex@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" @@ -5344,55 +5659,55 @@ is-regex@^1.1.1, is-regex@^1.1.4: is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-shared-array-buffer@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== is-ssh@^1.3.0: version "1.3.3" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" + resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== dependencies: protocols "^1.1.0" is-stream@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: has-tostringtag "^1.0.0" is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" is-text-path@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + resolved "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= dependencies: text-extensions "^1.0.0" is-typed-array@^1.1.7: version "1.1.8" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== dependencies: available-typed-arrays "^1.0.5" @@ -5403,90 +5718,90 @@ is-typed-array@^1.1.7: is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= is-weakmap@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== is-weakref@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ== dependencies: call-bind "^1.0.0" is-weakset@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" + resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== is-windows@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: is-docker "^2.0.0" isarray@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isarray@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= isobject@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + resolved "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= isstream@~0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-hook@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + resolved "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== dependencies: append-transform "^2.0.0" istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== dependencies: "@babel/core" "^7.7.5" @@ -5494,9 +5809,20 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" +istanbul-lib-instrument@^5.0.4: + version "5.1.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + istanbul-lib-processinfo@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" + resolved "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" integrity sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw== dependencies: archy "^1.0.0" @@ -5509,7 +5835,7 @@ istanbul-lib-processinfo@^2.0.2: istanbul-lib-report@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -5517,34 +5843,68 @@ istanbul-lib-report@^3.0.0: supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.3.tgz#974d682037f6d12b15dc55f9a2a5f8f1ea923831" - integrity sha512-0i77ZFLsb9U3DHi22WzmIngVzfoyxxbQcZRqlF3KoKmCJGq9nhFHoGi8FqBztN2rE8w6hURnZghetn0xpkVb6A== + version "3.0.5" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" jest-changed-files@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== dependencies: "@jest/types" "^26.6.2" execa "^4.0.0" throat "^5.0.0" +jest-changed-files@^27.3.0: + version "27.3.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.3.0.tgz#22a02cc2b34583fc66e443171dc271c0529d263c" + integrity sha512-9DJs9garMHv4RhylUMZgbdCJ3+jHSkpL9aaVKp13xtXAD80qLTLrqcDZL1PHA9dYA0bCI86Nv2BhkLpLhrBcPg== + dependencies: + "@jest/types" "^27.2.5" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.3.1.tgz#1679e74387cbbf0c6a8b42de963250a6469e0797" + integrity sha512-v1dsM9II6gvXokgqq6Yh2jHCpfg7ZqV4jWY66u7npz24JnhP3NHxI0sKT7+ZMQ7IrOWHYAaeEllOySbDbWsiXw== + dependencies: + "@jest/environment" "^27.3.1" + "@jest/test-result" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.3.1" + is-generator-fn "^2.0.0" + jest-each "^27.3.1" + jest-matcher-utils "^27.3.1" + jest-message-util "^27.3.1" + jest-runtime "^27.3.1" + jest-snapshot "^27.3.1" + jest-util "^27.3.1" + pretty-format "^27.3.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + jest-cli@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== dependencies: "@jest/core" "^26.6.3" @@ -5561,9 +5921,27 @@ jest-cli@^26.6.3: prompts "^2.0.1" yargs "^15.4.1" +jest-cli@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.3.1.tgz#b576f9d146ba6643ce0a162d782b40152b6b1d16" + integrity sha512-WHnCqpfK+6EvT62me6WVs8NhtbjAS4/6vZJnk7/2+oOr50cwAzG4Wxt6RXX0hu6m1169ZGMlhYYUNeKBXCph/Q== + dependencies: + "@jest/core" "^27.3.1" + "@jest/test-result" "^27.3.1" + "@jest/types" "^27.2.5" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + jest-config "^27.3.1" + jest-util "^27.3.1" + jest-validate "^27.3.1" + prompts "^2.0.1" + yargs "^16.2.0" + jest-config@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== dependencies: "@babel/core" "^7.1.0" @@ -5585,9 +5963,36 @@ jest-config@^26.6.3: micromatch "^4.0.2" pretty-format "^26.6.2" +jest-config@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.3.1.tgz#cb3b7f6aaa8c0a7daad4f2b9573899ca7e09bbad" + integrity sha512-KY8xOIbIACZ/vdYCKSopL44I0xboxC751IX+DXL2+Wx6DKNycyEfV3rryC3BPm5Uq/BBqDoMrKuqLEUNJmMKKg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^27.3.1" + "@jest/types" "^27.2.5" + babel-jest "^27.3.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-circus "^27.3.1" + jest-environment-jsdom "^27.3.1" + jest-environment-node "^27.3.1" + jest-get-type "^27.3.1" + jest-jasmine2 "^27.3.1" + jest-regex-util "^27.0.6" + jest-resolve "^27.3.1" + jest-runner "^27.3.1" + jest-util "^27.3.1" + jest-validate "^27.3.1" + micromatch "^4.0.4" + pretty-format "^27.3.1" + jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== dependencies: chalk "^4.0.0" @@ -5595,16 +6000,33 @@ jest-diff@^26.0.0, jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" +jest-diff@^27.0.0, jest-diff@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz#d2775fea15411f5f5aeda2a5e02c2f36440f6d55" + integrity sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.0.6" + jest-get-type "^27.3.1" + pretty-format "^27.3.1" + jest-docblock@^26.0.0: version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== dependencies: detect-newline "^3.0.0" +jest-docblock@^27.0.6: + version "27.0.6" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" + integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA== + dependencies: + detect-newline "^3.0.0" + jest-each@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== dependencies: "@jest/types" "^26.6.2" @@ -5613,9 +6035,20 @@ jest-each@^26.6.2: jest-util "^26.6.2" pretty-format "^26.6.2" +jest-each@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.3.1.tgz#14c56bb4f18dd18dc6bdd853919b5f16a17761ff" + integrity sha512-E4SwfzKJWYcvOYCjOxhZcxwL+AY0uFMvdCOwvzgutJiaiodFjkxQQDxHm8FQBeTqDnSmKsQWn7ldMRzTn2zJaQ== + dependencies: + "@jest/types" "^27.2.5" + chalk "^4.0.0" + jest-get-type "^27.3.1" + jest-util "^27.3.1" + pretty-format "^27.3.1" + jest-environment-jsdom@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== dependencies: "@jest/environment" "^26.6.2" @@ -5626,9 +6059,22 @@ jest-environment-jsdom@^26.6.2: jest-util "^26.6.2" jsdom "^16.4.0" +jest-environment-jsdom@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz#63ac36d68f7a9303494df783494856222b57f73e" + integrity sha512-3MOy8qMzIkQlfb3W1TfrD7uZHj+xx8Olix5vMENkj5djPmRqndMaXtpnaZkxmxM+Qc3lo+yVzJjzuXbCcZjAlg== + dependencies: + "@jest/environment" "^27.3.1" + "@jest/fake-timers" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + jest-mock "^27.3.0" + jest-util "^27.3.1" + jsdom "^16.6.0" + jest-environment-node@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== dependencies: "@jest/environment" "^26.6.2" @@ -5638,14 +6084,31 @@ jest-environment-node@^26.6.2: jest-mock "^26.6.2" jest-util "^26.6.2" +jest-environment-node@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.3.1.tgz#af7d0eed04edafb740311b303f3fe7c8c27014bb" + integrity sha512-T89F/FgkE8waqrTSA7/ydMkcc52uYPgZZ6q8OaZgyiZkJb5QNNCF6oPZjH9IfPFfcc9uBWh1574N0kY0pSvTXw== + dependencies: + "@jest/environment" "^27.3.1" + "@jest/fake-timers" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + jest-mock "^27.3.0" + jest-util "^27.3.1" + jest-get-type@^26.3.0: version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== +jest-get-type@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" + integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== + jest-haste-map@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== dependencies: "@jest/types" "^26.6.2" @@ -5664,9 +6127,29 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" +jest-haste-map@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" + integrity sha512-lYfNZIzwPccDJZIyk9Iz5iQMM/MH56NIIcGj7AFU1YyA4ewWFBl8z+YPJuSCRML/ee2cCt2y3W4K3VXPT6Nhzg== + dependencies: + "@jest/types" "^27.2.5" + "@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 "^27.0.6" + jest-serializer "^27.0.6" + jest-util "^27.3.1" + jest-worker "^27.3.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + jest-jasmine2@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== dependencies: "@babel/traverse" "^7.1.0" @@ -5688,37 +6171,59 @@ jest-jasmine2@^26.6.3: pretty-format "^26.6.2" throat "^5.0.0" -jest-junit@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-11.1.0.tgz#79cd53948e44d62b2b30fa23ea0d7a899d2c8d7a" - integrity sha512-c2LFOyKY7+ZxL5zSu+WHmHfsJ2wqbOpeYJ4Uu26yMhFxny2J2NQj6AVS7M+Eaxji9Q/oIDDK5tQy0DGzDp9xOw== +jest-jasmine2@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz#df6d3d07c7dafc344feb43a0072a6f09458d32b0" + integrity sha512-WK11ZUetDQaC09w4/j7o4FZDUIp+4iYWH/Lik34Pv7ukL+DuXFGdnmmi7dT58J2ZYKFB5r13GyE0z3NPeyJmsg== dependencies: - mkdirp "^1.0.4" - strip-ansi "^5.2.0" - uuid "^3.3.3" - xml "^1.0.1" - -jest-junit@^12.3.0: - version "12.3.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-12.3.0.tgz#ee41a74e439eecdc8965f163f83035cce5998d6d" - integrity sha512-+NmE5ogsEjFppEl90GChrk7xgz8xzvF0f+ZT5AnhW6suJC93gvQtmQjfyjDnE0Z2nXJqEkxF0WXlvjG/J+wn/g== + "@babel/traverse" "^7.1.0" + "@jest/environment" "^27.3.1" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.3.1" + is-generator-fn "^2.0.0" + jest-each "^27.3.1" + jest-matcher-utils "^27.3.1" + jest-message-util "^27.3.1" + jest-runtime "^27.3.1" + jest-snapshot "^27.3.1" + jest-util "^27.3.1" + pretty-format "^27.3.1" + throat "^6.0.1" + +jest-junit@^13.0.0: + version "13.0.0" + resolved "https://registry.npmjs.org/jest-junit/-/jest-junit-13.0.0.tgz#479be347457aad98ae8a5983a23d7c3ec526c9a3" + integrity sha512-JSHR+Dhb32FGJaiKkqsB7AR3OqWKtldLd6ZH2+FJ8D4tsweb8Id8zEVReU4+OlrRO1ZluqJLQEETm+Q6/KilBg== dependencies: mkdirp "^1.0.4" - strip-ansi "^5.2.0" + strip-ansi "^6.0.1" uuid "^8.3.2" xml "^1.0.1" jest-leak-detector@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== dependencies: jest-get-type "^26.3.0" pretty-format "^26.6.2" +jest-leak-detector@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz#7fb632c2992ef707a1e73286e1e704f9cc1772b2" + integrity sha512-78QstU9tXbaHzwlRlKmTpjP9k4Pvre5l0r8Spo4SbFFVy/4Abg9I6ZjHwjg2QyKEAMg020XcjP+UgLZIY50yEg== + dependencies: + jest-get-type "^27.3.1" + pretty-format "^27.3.1" + jest-matcher-utils@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== dependencies: chalk "^4.0.0" @@ -5726,9 +6231,19 @@ jest-matcher-utils@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" +jest-matcher-utils@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" + integrity sha512-hX8N7zXS4k+8bC1Aj0OWpGb7D3gIXxYvPNK1inP5xvE4ztbz3rc4AkI6jGVaerepBnfWB17FL5lWFJT3s7qo8w== + dependencies: + chalk "^4.0.0" + jest-diff "^27.3.1" + jest-get-type "^27.3.1" + pretty-format "^27.3.1" + jest-message-util@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== dependencies: "@babel/code-frame" "^7.0.0" @@ -5741,36 +6256,73 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" +jest-message-util@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.3.1.tgz#f7c25688ad3410ab10bcb862bcfe3152345c6436" + integrity sha512-bh3JEmxsTZ/9rTm0jQrPElbY2+y48Rw2t47uMfByNyUVR+OfPh4anuyKsGqsNkXk/TI4JbLRZx+7p7Hdt6q1yg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.2.5" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + pretty-format "^27.3.1" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== dependencies: "@jest/types" "^26.6.2" "@types/node" "*" +jest-mock@^27.3.0: + version "27.3.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.3.0.tgz#ddf0ec3cc3e68c8ccd489bef4d1f525571a1b867" + integrity sha512-ziZiLk0elZOQjD08bLkegBzv5hCABu/c8Ytx45nJKkysQwGaonvmTxwjLqEA4qGdasq9o2I8/HtdGMNnVsMTGw== + dependencies: + "@jest/types" "^27.2.5" + "@types/node" "*" + 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" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== 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" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== +jest-regex-util@^27.0.6: + version "27.0.6" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" + integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== + jest-resolve-dependencies@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== dependencies: "@jest/types" "^26.6.2" jest-regex-util "^26.0.0" jest-snapshot "^26.6.2" +jest-resolve-dependencies@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz#85b99bdbdfa46e2c81c6228fc4c91076f624f6e2" + integrity sha512-X7iLzY8pCiYOnvYo2YrK3P9oSE8/3N2f4pUZMJ8IUcZnT81vlSonya1KTO9ZfKGuC+svE6FHK/XOb8SsoRUV1A== + dependencies: + "@jest/types" "^27.2.5" + jest-regex-util "^27.0.6" + jest-snapshot "^27.3.1" + jest-resolve@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== dependencies: "@jest/types" "^26.6.2" @@ -5782,9 +6334,25 @@ jest-resolve@^26.6.2: resolve "^1.18.1" slash "^3.0.0" +jest-resolve@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.3.1.tgz#0e5542172a1aa0270be6f66a65888647bdd74a3e" + integrity sha512-Dfzt25CFSPo3Y3GCbxynRBZzxq9AdyNN+x/v2IqYx6KVT5Z6me2Z/PsSGFSv3cOSUZqJ9pHxilao/I/m9FouLw== + dependencies: + "@jest/types" "^27.2.5" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^27.3.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.3.1" + jest-validate "^27.3.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + jest-runner@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== dependencies: "@jest/console" "^26.6.2" @@ -5808,9 +6376,37 @@ jest-runner@^26.6.3: source-map-support "^0.5.6" throat "^5.0.0" +jest-runner@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.3.1.tgz#1d594dcbf3bd8600a7e839e790384559eaf96e3e" + integrity sha512-r4W6kBn6sPr3TBwQNmqE94mPlYVn7fLBseeJfo4E2uCTmAyDFm2O5DYAQAFP7Q3YfiA/bMwg8TVsciP7k0xOww== + dependencies: + "@jest/console" "^27.3.1" + "@jest/environment" "^27.3.1" + "@jest/test-result" "^27.3.1" + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-docblock "^27.0.6" + jest-environment-jsdom "^27.3.1" + jest-environment-node "^27.3.1" + jest-haste-map "^27.3.1" + jest-leak-detector "^27.3.1" + jest-message-util "^27.3.1" + jest-resolve "^27.3.1" + jest-runtime "^27.3.1" + jest-util "^27.3.1" + jest-worker "^27.3.1" + source-map-support "^0.5.6" + throat "^6.0.1" + jest-runtime@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== dependencies: "@jest/console" "^26.6.2" @@ -5841,17 +6437,57 @@ jest-runtime@^26.6.3: strip-bom "^4.0.0" yargs "^15.4.1" +jest-runtime@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.3.1.tgz#80fa32eb85fe5af575865ddf379874777ee993d7" + integrity sha512-qtO6VxPbS8umqhEDpjA4pqTkKQ1Hy4ZSi9mDVeE9Za7LKBo2LdW2jmT+Iod3XFaJqINikZQsn2wEi0j9wPRbLg== + dependencies: + "@jest/console" "^27.3.1" + "@jest/environment" "^27.3.1" + "@jest/globals" "^27.3.1" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.3.1" + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-haste-map "^27.3.1" + jest-message-util "^27.3.1" + jest-mock "^27.3.0" + jest-regex-util "^27.0.6" + jest-resolve "^27.3.1" + jest-snapshot "^27.3.1" + jest-util "^27.3.1" + jest-validate "^27.3.1" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^16.2.0" + jest-serializer@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== dependencies: "@types/node" "*" graceful-fs "^4.2.4" +jest-serializer@^27.0.6: + version "27.0.6" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" + integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + jest-snapshot@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== dependencies: "@babel/types" "^7.0.0" @@ -5871,9 +6507,39 @@ jest-snapshot@^26.6.2: pretty-format "^26.6.2" semver "^7.3.2" -jest-util@^26.1.0, jest-util@^26.6.2: +jest-snapshot@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.3.1.tgz#1da5c0712a252d70917d46c037054f5918c49ee4" + integrity sha512-APZyBvSgQgOT0XumwfFu7X3G5elj6TGhCBLbBdn3R1IzYustPGPE38F51dBWMQ8hRXa9je0vAdeVDtqHLvB6lg== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/parser" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.3.1" + graceful-fs "^4.2.4" + jest-diff "^27.3.1" + jest-get-type "^27.3.1" + jest-haste-map "^27.3.1" + jest-matcher-utils "^27.3.1" + jest-message-util "^27.3.1" + jest-resolve "^27.3.1" + jest-util "^27.3.1" + natural-compare "^1.4.0" + pretty-format "^27.3.1" + semver "^7.3.2" + +jest-util@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== dependencies: "@jest/types" "^26.6.2" @@ -5883,9 +6549,21 @@ jest-util@^26.1.0, jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" +jest-util@^27.0.0, jest-util@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.3.1.tgz#a58cdc7b6c8a560caac9ed6bdfc4e4ff23f80429" + integrity sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw== + dependencies: + "@jest/types" "^27.2.5" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.4" + picomatch "^2.2.3" + jest-validate@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== dependencies: "@jest/types" "^26.6.2" @@ -5895,9 +6573,21 @@ jest-validate@^26.6.2: leven "^3.1.0" pretty-format "^26.6.2" +jest-validate@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.3.1.tgz#3a395d61a19cd13ae9054af8cdaf299116ef8a24" + integrity sha512-3H0XCHDFLA9uDII67Bwi1Vy7AqwA5HqEEjyy934lgVhtJ3eisw6ShOF1MDmRPspyikef5MyExvIm0/TuLzZ86Q== + dependencies: + "@jest/types" "^27.2.5" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.3.1" + leven "^3.1.0" + pretty-format "^27.3.1" + jest-watcher@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== dependencies: "@jest/test-result" "^26.6.2" @@ -5908,42 +6598,73 @@ jest-watcher@^26.6.2: jest-util "^26.6.2" string-length "^4.0.1" +jest-watcher@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.3.1.tgz#ba5e0bc6aa843612b54ddb7f009d1cbff7e05f3e" + integrity sha512-9/xbV6chABsGHWh9yPaAGYVVKurWoP3ZMCv6h+O1v9/+pkOroigs6WzZ0e9gLP/njokUwM7yQhr01LKJVMkaZA== + dependencies: + "@jest/test-result" "^27.3.1" + "@jest/types" "^27.2.5" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.3.1" + string-length "^4.0.1" + jest-worker@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" +jest-worker@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz#0def7feae5b8042be38479799aeb7b5facac24b2" + integrity sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jest@^26.6.3: version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + resolved "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== dependencies: "@jest/core" "^26.6.3" import-local "^3.0.2" jest-cli "^26.6.3" +jest@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/jest/-/jest-27.3.1.tgz#b5bab64e8f56b6f7e275ba1836898b0d9f1e5c8a" + integrity sha512-U2AX0AgQGd5EzMsiZpYt8HyZ+nSVIh5ujQ9CPp9EQZJMjXIiSZpJNweZl0swatKRoqHWgGKM3zaSwm4Zaz87ng== + dependencies: + "@jest/core" "^27.3.1" + import-local "^3.0.2" + jest-cli "^27.3.1" + jmespath@0.15.0: version "0.15.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= js-base64@^2.1.9: version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -5951,19 +6672,19 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsbn@~0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.4.0: +jsdom@^16.4.0, jsdom@^16.6.0: version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: abab "^2.0.5" @@ -5996,73 +6717,76 @@ jsdom@^16.4.0: jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.38.0.tgz#a7fcc2e3eab9d5eec57fca2f41d4380c45e1920b" - integrity sha512-aBrqOCYZ5PvjCZ29tabYaELh9OtsAKYpiYP3enzPMo7yLuGJUODtJuZhtJCeMAaz9cbadj121f6EinwDyYhIgA== +jsii-diff@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-diff/-/jsii-diff-1.45.0.tgz#af1ef5fa79b3e2ac393c29f9f3dbd19a2d543b8f" + integrity sha512-XZh+oaToQJX5/y9VvIrFtEb4rXaV69pp/ennmkaSpLCFEkBXGNJoysREXQeVnmLvZUJfpUxvtMhL6HlPVpmSuQ== dependencies: - "@jsii/check-node" "1.38.0" - "@jsii/spec" "^1.38.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" fs-extra "^9.1.0" - jsii-reflect "^1.38.0" + jsii-reflect "^1.45.0" log4js "^6.3.0" typescript "~3.9.10" yargs "^16.2.0" -jsii-pacmak@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.38.0.tgz#e43c6c29d3589b3991dc21b5194a3290c736356c" - integrity sha512-QhaOto/7F/5cPaZPaupxXfD5PW10QOnVJjBhTcfIg3Pziqvs7tYuGh6I1EBs9dbZ1kCBq15ATYXqoljNIP78aw== +jsii-pacmak@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-1.45.0.tgz#341509f88c03b2104d8d6f56557cf787ff379fa5" + integrity sha512-w952TgVg/kRQyoF8zHp6G6y8On3LM5RJXBJ7doWvicWG9v9HXgHA5zfFgkSFCNtM3IcN7htDtg2kVpE7fv+IBw== dependencies: - "@jsii/check-node" "1.38.0" - "@jsii/spec" "^1.38.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" clone "^2.1.2" - codemaker "^1.38.0" + codemaker "^1.45.0" commonmark "^0.30.0" escape-string-regexp "^4.0.0" fs-extra "^9.1.0" - jsii-reflect "^1.38.0" - jsii-rosetta "^1.38.0" + jsii-reflect "^1.45.0" + jsii-rosetta "^1.45.0" semver "^7.3.5" spdx-license-list "^6.4.0" xmlbuilder "^15.1.1" yargs "^16.2.0" -jsii-reflect@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.38.0.tgz#4d9d52a148922dddd8d2647937747455f92e02bb" - integrity sha512-KH5Gi5gqcLVdPqCbC1fZf4hhOrGhNnYRjgAS5jyVpBfydsTaQFPqtu6Lyz/BntQ495u4rax1Teqk3ITafdFCoA== +jsii-reflect@1.45.0, jsii-reflect@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-1.45.0.tgz#8e4bfd062e2a6c9d2571996405069cd392e7668c" + integrity sha512-2oaDz3AKA/0p631TQJCCLUmZ11PXNP+e2GJ01mjUEpwNfEvdLGbfsQ+ThOhVlqP9yP8pmWAOjsQGiXhX4I27lA== dependencies: - "@jsii/check-node" "1.38.0" - "@jsii/spec" "^1.38.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" colors "^1.4.0" fs-extra "^9.1.0" - oo-ascii-tree "^1.38.0" + oo-ascii-tree "^1.45.0" yargs "^16.2.0" -jsii-rosetta@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.38.0.tgz#5da6bb478f0c68a7b06db7b52bd6bec355c1537f" - integrity sha512-UwYFMXyoHZMV/NX4FcTZ4vj22giVBO1+Bj2xYDqI4PVm1Nmad+8XwfnVFRYYJjQ4y1RWOWDkOOe1B/NH8kKWAg== +jsii-rosetta@1.45.0, jsii-rosetta@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-rosetta/-/jsii-rosetta-1.45.0.tgz#34c1eb252e5f16078c48f9b805fceb59d9204834" + integrity sha512-pwuqPmvAfXhU1LMZrcxbvVzeJyrMHEoJSH3P+qCP67TR5X8SGs5efH3HBIbIboGFXclg3c/WSHg+fEgzFs/tKg== dependencies: - "@jsii/check-node" "1.38.0" - "@jsii/spec" "^1.38.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "1.45.0" "@xmldom/xmldom" "^0.7.5" commonmark "^0.30.0" fs-extra "^9.1.0" + jsii "1.45.0" + sort-json "^2.0.0" typescript "~3.9.10" + workerpool "^6.1.5" yargs "^16.2.0" -jsii@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.38.0.tgz#2de03bba8adefe469e0412b41d5c8f249a347df3" - integrity sha512-FSqjFhMTAedl7voMgCz2cbEoSoXmwog6wYUQ91zUD5u2gDBIPY3K9LJhS18aUr7KMn+An8SecgOcMia5p6haAQ== +jsii@1.45.0, jsii@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii/-/jsii-1.45.0.tgz#d37c3dc46594f9c5dc2acb988d8e56179464943c" + integrity sha512-QaCV8d1pFMcFM+cVRRMXI/tfhgylH2T0+vrn51pIMkmA1ixqrBN4gQwTAskrNcjIKVHVndW5eR8IT4MFDMMOqw== dependencies: - "@jsii/check-node" "1.38.0" - "@jsii/spec" "^1.38.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.5" @@ -6077,7 +6801,7 @@ jsii@^1.38.0: json-diff@^0.5.4: version "0.5.4" - resolved "https://registry.yarnpkg.com/json-diff/-/json-diff-0.5.4.tgz#7bc8198c441756632aab66c7d9189d365a7a035a" + resolved "https://registry.npmjs.org/json-diff/-/json-diff-0.5.4.tgz#7bc8198c441756632aab66c7d9189d365a7a035a" integrity sha512-q5Xmx9QXNOzOzIlMoYtLrLiu4Jl/Ce2bn0CNcv54PhyH89CI4GWlGVDye8ei2Ijt9R3U+vsWPsXpLUNob8bs8Q== dependencies: cli-color "~0.1.6" @@ -6086,75 +6810,75 @@ json-diff@^0.5.4: json-parse-better-errors@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema-traverse@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== json-schema@0.2.3: version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= json-stable-stringify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= dependencies: jsonify "~0.0.0" json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= json5@2.x, json5@^2.1.2: version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" json5@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: minimist "^1.2.0" jsonc-parser@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== jsonfile@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= optionalDependencies: graceful-fs "^4.1.6" jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" @@ -6163,22 +6887,22 @@ jsonfile@^6.0.1: jsonify@~0.0.0: version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= jsonschema@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" + resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== jsprim@^1.2.2: version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= dependencies: assert-plus "1.0.0" @@ -6188,7 +6912,7 @@ jsprim@^1.2.2: jszip@^3.7.1: version "3.7.1" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + resolved "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" @@ -6198,53 +6922,53 @@ jszip@^3.7.1: just-extend@^4.0.2: version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= dependencies: is-buffer "^1.1.5" kind-of@^5.0.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== klaw-sync@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + resolved "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== dependencies: graceful-fs "^4.1.11" kleur@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/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" + resolved "https://registry.npmjs.org/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" + resolved "https://registry.npmjs.org/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" @@ -6256,15 +6980,15 @@ lambda-tester@^3.6.0: 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" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + version "1.0.1" + resolved "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== dependencies: readable-stream "^2.0.5" lerna@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-4.0.0.tgz#b139d685d50ea0ca1be87713a7c2f44a5b678e9e" + resolved "https://registry.npmjs.org/lerna/-/lerna-4.0.0.tgz#b139d685d50ea0ca1be87713a7c2f44a5b678e9e" integrity sha512-DD/i1znurfOmNJb0OBw66NmNqiM8kF6uIrzrJ0wGE3VNdzeOhz9ziWLYiRaZDGGwgbcjOo6eIfcx9O5Qynz+kg== dependencies: "@lerna/add" "4.0.0" @@ -6288,12 +7012,12 @@ lerna@^4.0.0: leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -6301,7 +7025,7 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= dependencies: prelude-ls "~1.1.2" @@ -6309,7 +7033,7 @@ levn@~0.3.0: libnpmaccess@^4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" + resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== dependencies: aproba "^2.0.0" @@ -6319,7 +7043,7 @@ libnpmaccess@^4.0.1: libnpmpublish@^4.0.0: version "4.0.2" - resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" + resolved "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== dependencies: normalize-package-data "^3.0.2" @@ -6330,26 +7054,31 @@ libnpmpublish@^4.0.0: lie@~3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + resolved "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== dependencies: immediate "~3.0.5" +line-reader@^0.2.4: + version "0.2.4" + resolved "https://registry.npmjs.org/line-reader/-/line-reader-0.2.4.tgz#c4392b587dea38580c9678570e6e8e49fce52622" + integrity sha1-xDkrWH3qOFgMlnhXDm6OSfzlJiI= + 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" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= linkify-it@^3.0.1: version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: uc.micro "^1.0.1" load-json-file@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= dependencies: graceful-fs "^4.1.2" @@ -6359,7 +7088,7 @@ load-json-file@^4.0.0: load-json-file@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== dependencies: graceful-fs "^4.1.15" @@ -6369,7 +7098,7 @@ load-json-file@^6.2.0: locate-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: p-locate "^2.0.0" @@ -6377,7 +7106,7 @@ locate-path@^2.0.0: locate-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== dependencies: p-locate "^3.0.0" @@ -6385,81 +7114,81 @@ locate-path@^3.0.0: locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash._reinterpolate@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.defaults@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= lodash.differencewith@~4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7" + resolved "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7" integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc= lodash.flatten@^4.4.0, lodash.flatten@~4.4.0: version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= lodash.flattendeep@^4.4.0: version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= lodash.get@^4.4.2: version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= lodash.ismatch@^4.4.0: version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" + resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= lodash.isplainobject@^4.0.6: version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.set@^4.3.2: version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + resolved "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= lodash.template@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== dependencies: lodash._reinterpolate "^3.0.0" @@ -6467,34 +7196,34 @@ lodash.template@^4.5.0: lodash.templatesettings@^4.0.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + resolved "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== dependencies: lodash._reinterpolate "^3.0.0" lodash.truncate@^4.4.2: version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= lodash.union@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash.uniq@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.x, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log4js@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" + resolved "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw== dependencies: date-format "^3.0.0" @@ -6505,26 +7234,26 @@ log4js@^6.3.0: lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" macos-release@^2.2.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" + resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== make-dir@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: pify "^4.0.1" @@ -6532,19 +7261,19 @@ make-dir@^2.1.0: make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" make-error@1.x, make-error@^1.1.1: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== make-fetch-happen@^8.0.9: version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== dependencies: agentkeepalive "^4.1.3" @@ -6565,7 +7294,7 @@ make-fetch-happen@^8.0.9: make-fetch-happen@^9.0.1: version "9.1.0" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== dependencies: agentkeepalive "^4.1.3" @@ -6587,44 +7316,44 @@ make-fetch-happen@^9.0.1: make-runnable@^1.3.10: version "1.3.10" - resolved "https://registry.yarnpkg.com/make-runnable/-/make-runnable-1.3.10.tgz#c89f89e35ffd2dd88fd0ec1b06650e8357577c87" + resolved "https://registry.npmjs.org/make-runnable/-/make-runnable-1.3.10.tgz#c89f89e35ffd2dd88fd0ec1b06650e8357577c87" integrity sha512-ec9hxTJip4ncG3TqZrkoR69oKdxFyJDq40A4sGNwGYVtl4Q10V4BhqnTGLUyJxQIxobhTqwxkgEFbGh77RmV7A== dependencies: bluebird "^3.5.0" yargs "^16.2.0" -makeerror@1.0.x: - version "1.0.11" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: - tmpl "1.0.x" + tmpl "1.0.5" map-cache@^0.2.2: version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= map-obj@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= map-obj@^4.0.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== map-visit@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= dependencies: object-visit "^1.0.0" markdown-it@12.2.0: version "12.2.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db" integrity sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg== dependencies: argparse "^2.0.1" @@ -6635,7 +7364,7 @@ markdown-it@12.2.0: markdownlint-cli@^0.29.0: version "0.29.0" - resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.29.0.tgz#3c56686fd00ace4b68c9cfa3a34a7a9dfdef1417" + resolved "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.29.0.tgz#3c56686fd00ace4b68c9cfa3a34a7a9dfdef1417" integrity sha512-SEXRUT1ri9sXV8xQK88vjGAgmz2X9rxEG2tXdDZMljzW8e++LNTO9zzBBStx3JQWrTDoGTPHNrcurbuiyF97gw== dependencies: commander "~8.2.0" @@ -6655,19 +7384,19 @@ markdownlint-cli@^0.29.0: markdownlint-rule-helpers@~0.15.0: version "0.15.0" - resolved "https://registry.yarnpkg.com/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.15.0.tgz#11434c573649b9235ae70b967314f5711f7d8fa8" + resolved "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.15.0.tgz#11434c573649b9235ae70b967314f5711f7d8fa8" integrity sha512-A+9mswc3m/kkqpJCqntmte/1VKhDJ+tjZsERLz5L4h/Qr7ht2/BkGkgY5E7/wsxIhcpl+ctIfz+oS3PQrMOB2w== markdownlint@~0.24.0: version "0.24.0" - resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.24.0.tgz#224b53f671367a237d40c8be1745c7be9a322671" + resolved "https://registry.npmjs.org/markdownlint/-/markdownlint-0.24.0.tgz#224b53f671367a237d40c8be1745c7be9a322671" integrity sha512-OJIGsGFV/rC9irI5E1FMy6v9hdACSwaa+EN3224Y5KG8zj2EYzdHOw0pOJovIYmjNfEZ9BtxUY4P7uYHTSNnbQ== dependencies: markdown-it "12.2.0" md5@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== dependencies: charenc "0.0.2" @@ -6676,12 +7405,12 @@ md5@^2.3.0: mdurl@^1.0.1, mdurl@~1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= meow@^8.0.0: version "8.1.2" - resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== dependencies: "@types/minimist" "^1.2.0" @@ -6698,22 +7427,22 @@ meow@^8.0.0: merge-descriptors@~1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^3.1.4: version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== dependencies: arr-diff "^4.0.0" @@ -6732,49 +7461,49 @@ micromatch@^3.1.4: micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" - integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.33" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" - integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + version "2.1.34" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: - mime-db "1.50.0" + mime-db "1.51.0" -mime@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== +mime@^2.6.0: + version "2.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== min-indent@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== minimatch@>=3.0, minimatch@^3.0.4, minimatch@~3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist-options@4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== dependencies: arrify "^1.0.1" @@ -6783,19 +7512,19 @@ minimist-options@4.1.0: minimist@>=1.2.2, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== minipass-collect@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== dependencies: minipass "^3.0.0" minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: version "1.4.1" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== dependencies: minipass "^3.1.0" @@ -6806,14 +7535,14 @@ minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: minipass-flush@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== dependencies: minipass "^3.0.0" minipass-json-stream@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + resolved "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== dependencies: jsonparse "^1.3.1" @@ -6821,21 +7550,21 @@ minipass-json-stream@^1.0.1: minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" minipass-sized@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== dependencies: minipass "^3.0.0" minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" @@ -6843,21 +7572,21 @@ minipass@^2.6.0, minipass@^2.9.0: minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.1.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== dependencies: yallist "^4.0.0" minizlib@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: minipass "^2.9.0" minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: minipass "^3.0.0" @@ -6865,7 +7594,7 @@ minizlib@^2.0.0, minizlib@^2.1.1: mixin-deep@^1.2.0: version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + resolved "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== dependencies: for-in "^1.0.2" @@ -6873,63 +7602,63 @@ mixin-deep@^1.2.0: mkdirp-infer-owner@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== dependencies: chownr "^2.0.0" infer-owner "^1.0.4" mkdirp "^1.0.3" -mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mock-fs@^4.14.0: version "4.14.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" + resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== mockery@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mockery/-/mockery-2.1.0.tgz#5b0aef1ff564f0f8139445e165536c7909713470" + resolved "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz#5b0aef1ff564f0f8139445e165536c7909713470" integrity sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA== modify-values@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== module-not-found-error@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + resolved "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA= ms@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== ms@^2.0.0, ms@^2.1.1: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multimatch@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + resolved "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== dependencies: "@types/minimatch" "^3.0.3" @@ -6940,12 +7669,12 @@ multimatch@^5.0.0: mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nanomatch@^1.2.9: version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== dependencies: arr-diff "^4.0.0" @@ -6962,32 +7691,32 @@ nanomatch@^1.2.9: natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= negotiator@^0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.6.0: version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== netmask@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== nice-try@^1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== nise@^4.0.4: version "4.1.0" - resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" + resolved "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== dependencies: "@sinonjs/commons" "^1.7.0" @@ -6998,7 +7727,7 @@ nise@^4.0.4: nise@^5.1.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" + resolved "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== dependencies: "@sinonjs/commons" "^1.7.0" @@ -7007,10 +7736,10 @@ nise@^5.1.0: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^13.1.3: - version "13.1.3" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.1.3.tgz#110b005965654a8ffb798e87bad18b467bff15f9" - integrity sha512-YKj0rKQWMGiiIO+Y65Ut8OEgYM3PplLU2+GAhnPmqZdBd6z5IskgdBqWmjzA6lH3RF0S2a3wiAlrMOF5Iv2Jeg== +nock@^13.2.1: + version "13.2.1" + resolved "https://registry.npmjs.org/nock/-/nock-13.2.1.tgz#fcf5bdb9bb9f0554a84c25d3333166c0ffd80858" + integrity sha512-CoHAabbqq/xZEknubuyQMjq6Lfi5b7RtK6SoNK6m40lebGp3yiMagWtIoYaw2s9sISD7wPuCfwFpivVHX/35RA== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -7018,15 +7747,15 @@ nock@^13.1.3: propagate "^2.0.0" node-fetch@^2.6.1: - version "2.6.5" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" - integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== + version "2.6.6" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== dependencies: whatwg-url "^5.0.0" node-gyp@^5.0.2: version "5.1.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw== dependencies: env-paths "^2.2.0" @@ -7043,7 +7772,7 @@ node-gyp@^5.0.2: node-gyp@^7.1.0: version "7.1.2" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== dependencies: env-paths "^2.2.0" @@ -7059,17 +7788,17 @@ node-gyp@^7.1.0: node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= node-modules-regexp@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= node-notifier@^8.0.0: version "8.0.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== dependencies: growly "^1.3.0" @@ -7081,19 +7810,19 @@ node-notifier@^8.0.0: node-preload@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + resolved "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== dependencies: process-on-spawn "^1.0.0" -node-releases@^1.1.77: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== nopt@^4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== dependencies: abbrev "1" @@ -7101,14 +7830,14 @@ nopt@^4.0.1: nopt@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== dependencies: abbrev "1" normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" @@ -7118,7 +7847,7 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package- normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== dependencies: hosted-git-info "^4.0.1" @@ -7128,38 +7857,38 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: normalize-path@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== npm-bundled@^1.1.1, npm-bundled@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" npm-install-checks@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" + resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== dependencies: semver "^7.1.1" npm-lifecycle@^3.1.5: version "3.1.5" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" + resolved "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g== dependencies: byline "^5.0.0" @@ -7173,12 +7902,12 @@ npm-lifecycle@^3.1.5: npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: version "8.1.5" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" + resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== dependencies: hosted-git-info "^4.0.1" @@ -7187,7 +7916,7 @@ npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-pack npm-packlist@^2.1.4: version "2.2.2" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" + resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== dependencies: glob "^7.1.6" @@ -7197,7 +7926,7 @@ npm-packlist@^2.1.4: npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" + resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== dependencies: npm-install-checks "^4.0.0" @@ -7207,7 +7936,7 @@ npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: npm-registry-fetch@^11.0.0: version "11.0.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" + resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== dependencies: make-fetch-happen "^9.0.1" @@ -7219,7 +7948,7 @@ npm-registry-fetch@^11.0.0: npm-registry-fetch@^9.0.0: version "9.0.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" + resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== dependencies: "@npmcli/ci-detect" "^1.0.0" @@ -7233,21 +7962,21 @@ npm-registry-fetch@^9.0.0: npm-run-path@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" npmlog@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" @@ -7257,17 +7986,17 @@ npmlog@^4.1.2: null-check@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" + resolved "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= nwsapi@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== nyc@^15.1.0: version "15.1.0" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + resolved "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== dependencies: "@istanbuljs/load-nyc-config" "^1.0.0" @@ -7300,17 +8029,17 @@ nyc@^15.1.0: oauth-sign@~0.9.0: version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== object-assign@^4.1.0: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-copy@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= dependencies: copy-descriptor "^0.1.0" @@ -7319,12 +8048,12 @@ object-copy@^0.1.0: object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-is@^1.1.4: version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: call-bind "^1.0.2" @@ -7332,19 +8061,19 @@ object-is@^1.1.4: object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object-visit@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= dependencies: isobject "^3.0.0" object.assign@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: call-bind "^1.0.0" @@ -7354,7 +8083,7 @@ object.assign@^4.1.2: object.getownpropertydescriptors@^2.0.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" + resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== dependencies: call-bind "^1.0.2" @@ -7363,14 +8092,14 @@ object.getownpropertydescriptors@^2.0.3: object.pick@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= dependencies: isobject "^3.0.1" -object.values@^1.1.4: +object.values@^1.1.5: version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== dependencies: call-bind "^1.0.2" @@ -7379,31 +8108,31 @@ object.values@^1.1.4: octokit-pagination-methods@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" + resolved "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.38.0.tgz#749c258bf2e3d74c5cc210c6bc98e05c567706b4" - integrity sha512-TyOg2fULmdzeVi/mbHwLHFdi/jcuhXN2HIhxPrSHqgL+Fu2WYtz9PIakFkeCuEdBWjUlYiJx89Xn0iOIhv17gA== +oo-ascii-tree@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.45.0.tgz#10480ea817ae0593404a1def81c7806544473fdd" + integrity sha512-f/2vTaoy+tUKv0VeQv5EYPHw8Yf/87aWn9iztzgnWHWqV3A8SGabWMbpDswHMZZTXM+Qnwdj+TjyhAMjt3vMhw== open@^7.4.2: version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: is-docker "^2.0.0" @@ -7411,7 +8140,7 @@ open@^7.4.2: optionator@^0.8.1: version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" @@ -7423,7 +8152,7 @@ optionator@^0.8.1: optionator@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -7435,12 +8164,12 @@ optionator@^0.9.1: os-homedir@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= os-name@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" + resolved "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== dependencies: macos-release "^2.2.0" @@ -7448,12 +8177,12 @@ os-name@^3.1.0: os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= osenv@^0.1.4: version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" @@ -7461,90 +8190,90 @@ osenv@^0.1.4: p-each-series@^2.1.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== p-finally@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^1.1.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: p-limit "^1.1.0" p-locate@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== dependencies: p-limit "^2.0.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-map-series@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" + resolved "https://registry.npmjs.org/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== p-map@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + resolved "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== dependencies: aggregate-error "^3.0.0" p-map@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" p-pipe@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" + resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== p-queue@^6.6.2: version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== dependencies: eventemitter3 "^4.0.4" @@ -7552,36 +8281,36 @@ p-queue@^6.6.2: p-reduce@^2.0.0, p-reduce@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== p-timeout@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== dependencies: p-finally "^1.0.0" p-try@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== p-waterfall@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" + resolved "https://registry.npmjs.org/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== dependencies: p-reduce "^2.0.0" pac-proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz#b718f76475a6a5415c2efbe256c1c971c84f635e" + resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz#b718f76475a6a5415c2efbe256c1c971c84f635e" integrity sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ== dependencies: "@tootallnate/once" "1" @@ -7596,7 +8325,7 @@ pac-proxy-agent@^5.0.0: pac-resolver@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" + resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== dependencies: degenerator "^3.0.1" @@ -7605,7 +8334,7 @@ pac-resolver@^5.0.0: package-hash@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + resolved "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== dependencies: graceful-fs "^4.1.15" @@ -7615,7 +8344,7 @@ package-hash@^4.0.0: pacote@^11.2.6: version "11.3.5" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" + resolved "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== dependencies: "@npmcli/git" "^2.1.0" @@ -7640,19 +8369,19 @@ pacote@^11.2.6: pako@~1.0.2: version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= dependencies: error-ex "^1.3.1" @@ -7660,7 +8389,7 @@ parse-json@^4.0.0: parse-json@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -7670,7 +8399,7 @@ parse-json@^5.0.0: parse-path@^4.0.0: version "4.0.3" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" + resolved "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA== dependencies: is-ssh "^1.3.0" @@ -7680,7 +8409,7 @@ parse-path@^4.0.0: parse-url@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d" + resolved "https://registry.npmjs.org/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d" integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw== dependencies: is-ssh "^1.3.0" @@ -7690,17 +8419,17 @@ parse-url@^6.0.0: parse5@6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== pascalcase@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= patch-package@^6.4.7: version "6.4.7" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + resolved "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== dependencies: "@yarnpkg/lockfile" "^1.1.0" @@ -7719,134 +8448,127 @@ patch-package@^6.4.7: path-exists@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 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" + resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 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" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== dependencies: isarray "0.0.1" path-type@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== dependencies: pify "^3.0.0" path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== performance-now@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= pify@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= pify@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== pify@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + resolved "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== pirates@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== dependencies: node-modules-regexp "^1.0.0" pkg-dir@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= dependencies: find-up "^2.1.0" pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= - dependencies: - find-up "^2.1.0" - posix-character-classes@^0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prelude-ls@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= pretty-format@^26.0.0, pretty-format@^26.6.2: version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== dependencies: "@jest/types" "^26.6.2" @@ -7854,36 +8576,46 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" +pretty-format@^27.0.0, pretty-format@^27.3.1: + version "27.3.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5" + integrity sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA== + dependencies: + "@jest/types" "^27.2.5" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + printj@~1.1.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" + resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== process-nextick-args@~2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process-on-spawn@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + resolved "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== dependencies: fromentries "^1.2.0" progress@^2.0.0: version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== promise-inflight@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= promise-retry@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== dependencies: err-code "^2.0.2" @@ -7891,44 +8623,44 @@ promise-retry@^2.0.1: promptly@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/promptly/-/promptly-3.2.0.tgz#a5517fbbf59bd31c1751d4e1d9bef1714f42b9d8" + resolved "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz#a5517fbbf59bd31c1751d4e1d9bef1714f42b9d8" integrity sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug== dependencies: read "^1.0.4" prompts@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" - integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" sisteransi "^1.0.5" promzard@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + resolved "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4= dependencies: read "1" propagate@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== proto-list@~1.2.1: version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= protocols@^1.1.0, protocols@^1.4.0: version "1.4.8" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" + resolved "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-5.0.0.tgz#d31405c10d6e8431fde96cba7a0c027ce01d633b" + resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz#d31405c10d6e8431fde96cba7a0c027ce01d633b" integrity sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g== dependencies: agent-base "^6.0.0" @@ -7942,12 +8674,12 @@ proxy-agent@^5.0.0: proxy-from-env@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== proxyquire@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" + resolved "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== dependencies: fill-keys "^1.0.2" @@ -7956,12 +8688,12 @@ proxyquire@^2.1.3: psl@^1.1.28, psl@^1.1.33: version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== pump@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" @@ -7969,39 +8701,39 @@ pump@^3.0.0: punycode@1.3.2: version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== pure-rand@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-5.0.0.tgz#87f5bdabeadbd8904e316913a5c0b8caac517b37" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.0.tgz#87f5bdabeadbd8904e316913a5c0b8caac517b37" integrity sha512-lD2/y78q+7HqBx2SaT6OT4UcwtvXNRfEpzYEzl0EQ+9gZq2Qi3fa0HDnYPeqQwhlHJFBUhT7AO3mLU3+8bynHA== q@^1.5.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= qs@^6.9.4: version "6.10.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== dependencies: side-channel "^1.0.4" qs@~6.5.2: version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== query-string@^6.13.8: version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" + resolved "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== dependencies: decode-uri-component "^0.2.0" @@ -8011,42 +8743,42 @@ query-string@^6.13.8: querystring@0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== quick-lru@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== raw-body@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" - integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== + version "2.4.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" + integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== dependencies: - bytes "3.1.0" - http-errors "1.7.3" + bytes "3.1.1" + http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" react-is@^17.0.1: version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== read-cmd-shim@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" + resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== read-package-json-fast@^2.0.1: version "2.0.3" - resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + resolved "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== dependencies: json-parse-even-better-errors "^2.3.0" @@ -8054,7 +8786,7 @@ read-package-json-fast@^2.0.1: read-package-json@^2.0.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" + resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== dependencies: glob "^7.1.1" @@ -8064,7 +8796,7 @@ read-package-json@^2.0.0: read-package-json@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-3.0.1.tgz#c7108f0b9390257b08c21e3004d2404c806744b9" + resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-3.0.1.tgz#c7108f0b9390257b08c21e3004d2404c806744b9" integrity sha512-aLcPqxovhJTVJcsnROuuzQvv6oziQx4zd3JvG0vGCL5MjTONUc4uJ90zCBC6R7W7oUKBNoR/F8pkyfVwlbxqng== dependencies: glob "^7.1.1" @@ -8074,7 +8806,7 @@ read-package-json@^3.0.0: read-package-json@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-4.1.1.tgz#153be72fce801578c1c86b8ef2b21188df1b9eea" + resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-4.1.1.tgz#153be72fce801578c1c86b8ef2b21188df1b9eea" integrity sha512-P82sbZJ3ldDrWCOSKxJT0r/CXMWR0OR3KRh55SgKo3p91GSIEEC32v3lSHAvO/UcH3/IoL7uqhOFBduAnwdldw== dependencies: glob "^7.1.1" @@ -8084,7 +8816,7 @@ read-package-json@^4.1.1: read-package-tree@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" + resolved "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== dependencies: read-package-json "^2.0.0" @@ -8093,7 +8825,7 @@ read-package-tree@^5.3.1: read-pkg-up@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= dependencies: find-up "^2.0.0" @@ -8101,7 +8833,7 @@ read-pkg-up@^3.0.0: read-pkg-up@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== dependencies: find-up "^4.1.0" @@ -8110,7 +8842,7 @@ read-pkg-up@^7.0.1: read-pkg@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= dependencies: load-json-file "^4.0.0" @@ -8119,7 +8851,7 @@ read-pkg@^3.0.0: read-pkg@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" @@ -8129,14 +8861,14 @@ read-pkg@^5.2.0: read@1, read@^1.0.4, read@~1.0.1: version "1.0.7" - resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + resolved "https://registry.npmjs.org/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= dependencies: mute-stream "~0.0.4" readable-stream@1.1.x: version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= dependencies: core-util-is "~1.0.0" @@ -8146,7 +8878,7 @@ readable-stream@1.1.x: readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: inherits "^2.0.3" @@ -8155,7 +8887,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@~2.3.6: version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" @@ -8168,14 +8900,14 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable readdir-glob@^1.0.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" + resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== dependencies: minimatch "^3.0.4" readdir-scoped-modules@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== dependencies: debuglog "^1.0.1" @@ -8183,9 +8915,16 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + redent@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: indent-string "^4.0.0" @@ -8193,7 +8932,7 @@ redent@^3.0.0: regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + resolved "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== dependencies: extend-shallow "^3.0.2" @@ -8201,7 +8940,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: regexp.prototype.flags@^1.3.0: version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: call-bind "^1.0.2" @@ -8209,34 +8948,39 @@ regexp.prototype.flags@^1.3.0: regexpp@^3.0.0, regexpp@^3.1.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== release-zalgo@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + resolved "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= dependencies: es6-error "^4.0.1" +remove-markdown@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.2.2.tgz#66b0ceeba9fb77ca9636bb1b0307ce21a32a12a6" + integrity sha1-ZrDO66n7d8qWNrsbAwfOIaMqEqY= + remove-trailing-separator@^1.0.1: version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== repeat-string@^1.6.1: version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= request@^2.88.0, request@^2.88.2: version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" @@ -8262,44 +9006,49 @@ request@^2.88.0, request@^2.88.2: require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-from-string@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== require-main-filename@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve-url@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.18.1, resolve@^1.20.0: version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: is-core-module "^2.2.0" @@ -8307,7 +9056,7 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.18.1, resolve@^1.2 restore-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: onetime "^5.1.0" @@ -8315,51 +9064,51 @@ restore-cursor@^3.1.0: ret@~0.1.10: version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== retry@^0.12.0: version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.1.4: version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rimraf@^2.6.3: version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" rsvp@^4.8.4: version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.4.0: version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-con@~1.2.10: version "1.2.10" - resolved "https://registry.yarnpkg.com/run-con/-/run-con-1.2.10.tgz#90de9d43d20274d00478f4c000495bd72f417d22" + resolved "https://registry.npmjs.org/run-con/-/run-con-1.2.10.tgz#90de9d43d20274d00478f4c000495bd72f417d22" integrity sha512-n7PZpYmMM26ZO21dd8y3Yw1TRtGABjRtgPSgFS/nhzfvbJMXFtJhJVyEgayMiP+w/23craJjsnfDvx4W4ue/HQ== dependencies: deep-extend "^0.6.0" @@ -8369,43 +9118,43 @@ run-con@~1.2.10: run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" rxjs@^6.6.0: version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-regex@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= dependencies: ret "~0.1.10" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sane@^4.0.3: version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== dependencies: "@cnakazawa/watch" "^1.0.3" @@ -8420,58 +9169,58 @@ sane@^4.0.3: sax@1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= sax@>=0.6.0, sax@^1.2.4: version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== saxes@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: xmlchars "^2.2.0" semver-intersect@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" + resolved "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" integrity sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ== dependencies: semver "^5.0.0" "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-immediate-shim@~1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + resolved "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== dependencies: extend-shallow "^2.0.1" @@ -8479,50 +9228,50 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== shallow-clone@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= dependencies: shebang-regex "^1.0.0" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shellwords@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== side-channel@^1.0.3, side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" @@ -8531,12 +9280,12 @@ side-channel@^1.0.3, side-channel@^1.0.4: signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== sinon@^11.1.1: version "11.1.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-11.1.2.tgz#9e78850c747241d5c59d1614d8f9cbe8840e8674" + resolved "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz#9e78850c747241d5c59d1614d8f9cbe8840e8674" integrity sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw== dependencies: "@sinonjs/commons" "^1.8.3" @@ -8548,7 +9297,7 @@ sinon@^11.1.1: sinon@^9.2.4: version "9.2.4" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" + resolved "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== dependencies: "@sinonjs/commons" "^1.8.1" @@ -8560,22 +9309,22 @@ sinon@^9.2.4: sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== slice-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: ansi-styles "^4.0.0" @@ -8584,17 +9333,17 @@ slice-ansi@^4.0.0: slide@^1.1.6: version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= smart-buffer@^4.1.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== snapdragon-node@^2.0.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== dependencies: define-property "^1.0.0" @@ -8603,14 +9352,14 @@ snapdragon-node@^2.0.1: snapdragon-util@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + resolved "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== dependencies: kind-of "^3.2.0" snapdragon@^0.8.1: version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + resolved "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== dependencies: base "^0.11.1" @@ -8624,7 +9373,7 @@ snapdragon@^0.8.1: socks-proxy-agent@5, socks-proxy-agent@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== dependencies: agent-base "^6.0.2" @@ -8633,7 +9382,7 @@ socks-proxy-agent@5, socks-proxy-agent@^5.0.0: socks-proxy-agent@^6.0.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg== dependencies: agent-base "^6.0.2" @@ -8642,7 +9391,7 @@ socks-proxy-agent@^6.0.0: socks@^2.3.3, socks@^2.6.1: version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + resolved "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== dependencies: ip "^1.1.5" @@ -8650,7 +9399,7 @@ socks@^2.3.3, socks@^2.6.1: sort-json@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-json/-/sort-json-2.0.0.tgz#a7030d8875adbd4a5ea39a000567ed94c1aa3c50" + resolved "https://registry.npmjs.org/sort-json/-/sort-json-2.0.0.tgz#a7030d8875adbd4a5ea39a000567ed94c1aa3c50" integrity sha512-OgXPErPJM/rBK5OhzIJ+etib/BmLQ1JY55Nb/ElhoWUec62pXNF/X6DrecHq3NW5OAGX0KxYD7m0HtgB9dvGeA== dependencies: detect-indent "^5.0.0" @@ -8659,21 +9408,21 @@ sort-json@^2.0.0: sort-keys@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= dependencies: is-plain-obj "^1.0.0" sort-keys@^4.0.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" + resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== dependencies: is-plain-obj "^2.0.0" source-map-resolve@^0.5.0: version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: atob "^2.1.2" @@ -8684,7 +9433,7 @@ source-map-resolve@^0.5.0: source-map-support@^0.5.17, source-map-support@^0.5.20, source-map-support@^0.5.6: version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== dependencies: buffer-from "^1.0.0" @@ -8692,27 +9441,27 @@ source-map-support@^0.5.17, source-map-support@^0.5.20, source-map-support@^0.5. source-map-url@^0.4.0: version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== spawn-wrap@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + resolved "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== dependencies: foreground-child "^2.0.0" @@ -8724,7 +9473,7 @@ spawn-wrap@^2.0.0: spdx-correct@^3.0.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" @@ -8732,61 +9481,61 @@ spdx-correct@^3.0.0: spdx-exceptions@^2.1.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.10" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" - integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== + version "3.0.11" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== spdx-license-list@^6.4.0: version "6.4.0" - resolved "https://registry.yarnpkg.com/spdx-license-list/-/spdx-license-list-6.4.0.tgz#9850c3699c1d35745285607d064d2a5145049d12" + resolved "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.4.0.tgz#9850c3699c1d35745285607d064d2a5145049d12" integrity sha512-4BxgJ1IZxTJuX1YxMGu2cRYK46Bk9zJNTK2/R0wNZR0cm+6SVl26/uG7FQmQtxoJQX1uZ0EpTi2L7zvMLboaBA== split-on-first@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== dependencies: extend-shallow "^3.0.0" split2@^3.0.0: version "3.2.2" - resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== dependencies: readable-stream "^3.0.0" split@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== dependencies: through "2" sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" @@ -8801,27 +9550,27 @@ sshpk@^1.7.0: ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" -stack-utils@^2.0.2: +stack-utils@^2.0.2, stack-utils@^2.0.3: version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" -standard-version@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/standard-version/-/standard-version-9.3.1.tgz#786c16c318847f58a31a2434f97e8db33a635853" - integrity sha512-5qMxXw/FxLouC5nANyx/5RY1kiorJx9BppUso8gN07MG64q2uLRmrPb4KfXp3Ql4s/gxjZwZ89e0FwxeLubGww== +standard-version@^9.3.2: + version "9.3.2" + resolved "https://registry.npmjs.org/standard-version/-/standard-version-9.3.2.tgz#28db8c1be66fd2d736f28f7c5de7619e64cd6dab" + integrity sha512-u1rfKP4o4ew7Yjbfycv80aNMN2feTiqseAhUhrrx2XtdQGmu7gucpziXe68Z4YfHVqlxVEzo4aUA0Iu3VQOTgQ== dependencies: chalk "^2.4.2" conventional-changelog "3.1.24" conventional-changelog-config-spec "2.1.0" - conventional-changelog-conventionalcommits "4.5.0" + conventional-changelog-conventionalcommits "4.6.1" conventional-recommended-bump "6.1.0" detect-indent "^6.0.0" detect-newline "^3.1.0" @@ -8836,7 +9585,7 @@ standard-version@^9.3.1: static-extend@^0.1.1: version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= dependencies: define-property "^0.2.5" @@ -8844,12 +9593,12 @@ static-extend@^0.1.1: "statuses@>= 1.5.0 < 2": version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= streamroller@^2.2.4: version "2.2.4" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" + resolved "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ== dependencies: date-format "^2.1.0" @@ -8858,20 +9607,20 @@ streamroller@^2.2.4: strict-uri-encode@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= string-length@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -8880,12 +9629,12 @@ string-width@*, string-width@^1.0.1, "string-width@^1.0.2 || 2", string-width@^4 string.prototype.repeat@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + resolved "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" integrity sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8= string.prototype.trimend@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: call-bind "^1.0.2" @@ -8893,7 +9642,7 @@ string.prototype.trimend@^1.0.4: string.prototype.trimstart@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: call-bind "^1.0.2" @@ -8901,84 +9650,77 @@ string.prototype.trimstart@^1.0.4: string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" string_decoder@~0.10.x: version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= string_decoder@~1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" stringify-package@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" + resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" -strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-eof@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-indent@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== dependencies: min-indent "^1.0.0" strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strong-log-transformer@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" + resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== dependencies: duplexer "^0.1.1" @@ -8987,21 +9729,28 @@ strong-log-transformer@^2.1.0: supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== dependencies: has-flag "^4.0.0" @@ -9009,16 +9758,15 @@ supports-hyperlinks@^2.0.0: symbol-tree@^3.2.4: version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@*, table@^6.0.9, table@^6.7.2: - version "6.7.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0" - integrity sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g== +table@*, table@^6.0.9, table@^6.7.3: + version "6.7.3" + resolved "https://registry.npmjs.org/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" + integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== dependencies: ajv "^8.0.1" - lodash.clonedeep "^4.5.0" lodash.truncate "^4.4.2" slice-ansi "^4.0.0" string-width "^4.2.3" @@ -9026,7 +9774,7 @@ table@*, table@^6.0.9, table@^6.7.2: tar-stream@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -9037,7 +9785,7 @@ tar-stream@^2.2.0: tar@^4.4.12: version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + resolved "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== dependencies: chownr "^1.1.4" @@ -9050,7 +9798,7 @@ tar@^4.4.12: tar@^6.0.2, tar@^6.1.0: version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" @@ -9062,17 +9810,17 @@ tar@^6.0.2, tar@^6.1.0: temp-dir@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= temp-dir@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== temp-write@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" + resolved "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== dependencies: graceful-fs "^4.1.15" @@ -9083,7 +9831,7 @@ temp-write@^4.0.0: tempfile@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-3.0.0.tgz#5376a3492de7c54150d0cc0612c3f00e2cdaf76c" + resolved "https://registry.npmjs.org/tempfile/-/tempfile-3.0.0.tgz#5376a3492de7c54150d0cc0612c3f00e2cdaf76c" integrity sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw== dependencies: temp-dir "^2.0.0" @@ -9091,7 +9839,7 @@ tempfile@^3.0.0: terminal-link@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== dependencies: ansi-escapes "^4.2.1" @@ -9099,7 +9847,7 @@ terminal-link@^2.0.0: test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -9108,22 +9856,27 @@ test-exclude@^6.0.0: text-extensions@^1.0.0: version "1.9.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= throat@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + through2@^2.0.0: version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: readable-stream "~2.3.6" @@ -9131,43 +9884,43 @@ through2@^2.0.0: through2@^4.0.0: version "4.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + resolved "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== dependencies: readable-stream "3" through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= tmp@^0.0.33: version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: os-tmpdir "~1.0.2" -tmpl@1.0.x: +tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= to-object-path@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + resolved "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= dependencies: kind-of "^3.0.2" to-regex-range@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= dependencies: is-number "^3.0.0" @@ -9175,14 +9928,14 @@ to-regex-range@^2.1.0: to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + resolved "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== dependencies: define-property "^2.0.2" @@ -9190,14 +9943,14 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== tough-cookie@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: psl "^1.1.33" @@ -9206,7 +9959,7 @@ tough-cookie@^4.0.0: tough-cookie@~2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: psl "^1.1.28" @@ -9214,53 +9967,51 @@ tough-cookie@~2.5.0: tr46@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== dependencies: punycode "^2.1.1" tr46@~0.0.3: version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= traverse@^0.6.6: version "0.6.6" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= trim-newlines@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-jest@^26.5.6: - version "26.5.6" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" - integrity sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA== +ts-jest@^27.0.7: + version "27.0.7" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.7.tgz#fb7c8c8cb5526ab371bc1b23d06e745652cca2d0" + integrity sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q== dependencies: bs-logger "0.x" - buffer-from "1.x" fast-json-stable-stringify "2.x" - jest-util "^26.1.0" + jest-util "^27.0.0" json5 "2.x" - lodash "4.x" + lodash.memoize "4.x" make-error "1.x" - mkdirp "1.x" semver "7.x" yargs-parser "20.x" -ts-mock-imports@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.3.7.tgz#8c3210a641f40fd5cadbd1c9c88574b51df59bde" - integrity sha512-zy4B3QSGaOhjaX9j0h9YKwM1oHG4Kd1KIUJBeXlXIQrFnATNLgh4+NyRcaAHsPeqwe3TWeRtHXkNXPxySEKk3w== +ts-mock-imports@^1.3.8: + version "1.3.8" + resolved "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.8.tgz#6b26887c651effe947ea91f928338d1899146fb9" + integrity sha512-A5n0iEg4zh2/qToo54XOa/wT31fAI0B8DHYU4RDcA6HIddZQPRkTsYri3Hl69+OSLjOKWjyP3/vYOIp3SAIZXg== ts-node@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5" - integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw== + version "10.4.0" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== dependencies: - "@cspotcode/source-map-support" "0.6.1" + "@cspotcode/source-map-support" "0.7.0" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" @@ -9275,7 +10026,7 @@ ts-node@^10.2.1: ts-node@^9.1.1: version "9.1.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== dependencies: arg "^4.1.0" @@ -9287,7 +10038,7 @@ ts-node@^9.1.1: tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== dependencies: "@types/json5" "^0.0.29" @@ -9297,102 +10048,102 @@ tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" tunnel-agent@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= dependencies: safe-buffer "^5.0.1" tunnel@0.0.6: version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-check@~0.3.2: version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= dependencies: prelude-ls "~1.1.2" type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.18.0: version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== type-fest@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== typedarray-to-buffer@^3.1.5: version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: is-typedarray "^1.0.0" typedarray@^0.0.6: version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript-json-schema@^0.51.0: version "0.51.0" - resolved "https://registry.yarnpkg.com/typescript-json-schema/-/typescript-json-schema-0.51.0.tgz#e2abff69b8564c98c0edef2c13d55ef10fd71427" + resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.51.0.tgz#e2abff69b8564c98c0edef2c13d55ef10fd71427" integrity sha512-POhWbUNs2oaBti1W9k/JwS+uDsaZD9J/KQiZ/iXRQEOD0lTn9VmshIls9tn+A9X6O+smPjeEz5NEy6WTkCCzrQ== dependencies: "@types/json-schema" "^7.0.9" @@ -9405,42 +10156,42 @@ typescript-json-schema@^0.51.0: typescript@~3.8.3: version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== typescript@~3.9.10: version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== typescript@~4.2.3: version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.14.2" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99" - integrity sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A== + version "3.14.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.3.tgz#c0f25dfea1e8e5323eccf59610be08b6043c15cf" + integrity sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g== uid-number@0.0.6: version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + resolved "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= umask@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" + resolved "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= unbox-primitive@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== dependencies: function-bind "^1.1.1" @@ -9450,7 +10201,7 @@ unbox-primitive@^1.0.1: union-value@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== dependencies: arr-union "^3.1.0" @@ -9460,48 +10211,48 @@ union-value@^1.0.0: unique-filename@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== dependencies: unique-slug "^2.0.0" unique-slug@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" universal-user-agent@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557" + resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557" integrity sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg== dependencies: os-name "^3.1.0" universal-user-agent@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== unpipe@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= unset-value@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + resolved "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= dependencies: has-value "^0.3.1" @@ -9509,24 +10260,24 @@ unset-value@^1.0.0: upath@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" urix@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url@0.10.3: version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + resolved "https://registry.npmjs.org/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= dependencies: punycode "1.3.2" @@ -9534,58 +10285,67 @@ url@0.10.3: use@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== utf8@^2.1.1: version "2.1.2" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" + resolved "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" integrity sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY= util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= util-promisify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" + resolved "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM= dependencies: object.getownpropertydescriptors "^2.0.3" uuid@3.3.2: version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== uuid@^3.3.2, uuid@^3.3.3: version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8-to-istanbul@^7.0.0: version "7.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" source-map "^0.7.3" +v8-to-istanbul@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" @@ -9593,19 +10353,19 @@ validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: validate-npm-package-name@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= 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" + resolved "https://registry.npmjs.org/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= verror@1.10.0: version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= dependencies: assert-plus "^1.0.0" @@ -9613,68 +10373,68 @@ verror@1.10.0: extsprintf "^1.2.0" vm2@^3.9.3: - version "3.9.3" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" - integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + version "3.9.5" + resolved "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz#5288044860b4bbace443101fcd3bddb2a0aa2496" + integrity sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng== w3c-hr-time@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" w3c-xmlserializer@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: xml-name-validator "^3.0.0" walker@^1.0.7, walker@~1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" - integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: - makeerror "1.0.x" + makeerror "1.0.12" wcwidth@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= dependencies: defaults "^1.0.3" webidl-conversions@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= webidl-conversions@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== webidl-conversions@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== whatwg-encoding@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" whatwg-mimetype@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== whatwg-url@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= dependencies: tr46 "~0.0.3" @@ -9682,7 +10442,7 @@ whatwg-url@^5.0.0: whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== dependencies: lodash "^4.7.0" @@ -9691,7 +10451,7 @@ whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" @@ -9702,7 +10462,7 @@ which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: which-collection@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== dependencies: is-map "^2.0.1" @@ -9712,12 +10472,12 @@ which-collection@^1.0.1: which-module@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which-typed-array@^1.1.2: version "1.1.7" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== dependencies: available-typed-arrays "^1.0.5" @@ -9729,45 +10489,50 @@ which-typed-array@^1.1.2: which@^1.2.9, which@^1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" which@^2.0.1, which@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + version "1.1.5" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== dependencies: - string-width "^1.0.2 || 2" + string-width "^1.0.2 || 2 || 3 || 4" windows-release@^3.1.0: version "3.3.3" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" + resolved "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== dependencies: execa "^1.0.0" word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wordwrap@>=0.0.2, wordwrap@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +workerpool@^6.1.5: + version "6.1.5" + resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" + integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== + wrap-ansi@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" @@ -9776,7 +10541,7 @@ wrap-ansi@^6.2.0: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -9785,12 +10550,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= write-file-atomic@^2.4.2: version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== dependencies: graceful-fs "^4.1.11" @@ -9799,7 +10564,7 @@ write-file-atomic@^2.4.2: write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: imurmurhash "^0.1.4" @@ -9809,7 +10574,7 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: write-json-file@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" + resolved "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== dependencies: detect-indent "^5.0.0" @@ -9821,7 +10586,7 @@ write-json-file@^3.2.0: write-json-file@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" + resolved "https://registry.npmjs.org/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== dependencies: detect-indent "^6.0.0" @@ -9833,7 +10598,7 @@ write-json-file@^4.3.0: write-pkg@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" + resolved "https://registry.npmjs.org/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== dependencies: sort-keys "^2.0.0" @@ -9842,24 +10607,24 @@ write-pkg@^4.0.0: ws@^7.4.6: version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== xml-js@^1.6.11: version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + resolved "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== dependencies: sax "^1.2.4" xml-name-validator@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xml2js@0.4.19: version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== dependencies: sax ">=0.6.0" @@ -9867,77 +10632,77 @@ xml2js@0.4.19: xml@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + resolved "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= xmlbuilder@^15.1.1: version "15.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== xmlbuilder@~9.0.1: version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= xmlchars@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xregexp@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= xtend@~4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@*, yaml@1.10.2, yaml@^1.10.0: version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@2.0.0-1: version "2.0.0-1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== yargs-parser@20.2.4: version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^18.1.2: version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" @@ -9945,7 +10710,7 @@ yargs-parser@^18.1.2: yargs@^15.0.2, yargs@^15.4.1: version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0" @@ -9962,7 +10727,7 @@ yargs@^15.0.2, yargs@^15.4.1: yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" @@ -9975,7 +10740,7 @@ yargs@^16.0.0, yargs@^16.2.0: yargs@^17.1.1: version "17.2.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== dependencies: cliui "^7.0.2" @@ -9988,17 +10753,17 @@ yargs@^17.1.1: yn@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zip-stream@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + resolved "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== dependencies: archiver-utils "^2.1.0"