diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index 08d852b209784..fe09e74b25e2e 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -87,14 +87,14 @@ jobs: {"keywords":["(@aws-cdk/aws-docdb)","(aws-docdb)","(docdb)","(doc db)","(doc-db)"],"labels":["@aws-cdk/aws-docdb"],"assignees":["skinny85"]}, {"keywords":["(@aws-cdk/aws-dynamodb)","(aws-dynamodb)","(dynamodb)","(dynamo db)","(dynamo-db)"],"labels":["@aws-cdk/aws-dynamodb"],"assignees":["skinny85"]}, {"keywords":["(@aws-cdk/aws-dynamodb-global)","(aws-dynamodb-global)","(dynamodb-global)","(dynamodb global)"],"labels":["@aws-cdk/aws-dynamodb-global"],"assignees":["skinny85"]}, - {"keywords":["(@aws-cdk/aws-ec2)","(aws-ec2)","(ec2)","(vpc)"],"labels":["@aws-cdk/aws-ec2"],"assignees":["rix0rrr"]}, + {"keywords":["(@aws-cdk/aws-ec2)","(aws-ec2)","(ec2)","(vpc)"],"labels":["@aws-cdk/aws-ec2"],"assignees":["njlynch"]}, {"keywords":["(@aws-cdk/aws-ecr)","(aws-ecr)","(ecr)"],"labels":["@aws-cdk/aws-ecr"],"assignees":["madeline-k"]}, {"keywords":["(@aws-cdk/aws-ecr-assets)","(aws-ecr-assets)","(ecr-assets)","(ecr assets)","(ecrassets)"],"labels":["@aws-cdk/aws-ecr-assets"],"assignees":["eladb"]}, {"keywords":["(@aws-cdk/aws-ecs)","(aws-ecs)","(ecs)"],"labels":["@aws-cdk/aws-ecs"],"assignees":["madeline-k"]}, {"keywords":["(@aws-cdk/aws-ecs-patterns)","(aws-ecs-patterns)","(ecs-patterns)"],"labels":["@aws-cdk/aws-ecs-patterns"],"assignees":["madeline-k"]}, {"keywords":["(@aws-cdk/aws-efs)","(aws-efs)","(efs)"],"labels":["@aws-cdk/aws-efs"],"assignees":["nija-at"]}, - {"keywords":["(@aws-cdk/aws-eks)","(aws-eks)","(eks)"],"labels":["@aws-cdk/aws-eks"],"assignees":["iliapolo"]}, - {"keywords":["(@aws-cdk/aws-eks-legacy)","(aws-eks-legacy)","(eks-legacy)"],"labels":["@aws-cdk/aws-eks-legacy"],"assignees":["iliapolo"]}, + {"keywords":["(@aws-cdk/aws-eks)","(aws-eks)","(eks)"],"labels":["@aws-cdk/aws-eks"],"assignees":["otaviomacedo"]}, + {"keywords":["(@aws-cdk/aws-eks-legacy)","(aws-eks-legacy)","(eks-legacy)"],"labels":["@aws-cdk/aws-eks-legacy"],"assignees":["otaviomacedo"]}, {"keywords":["(@aws-cdk/aws-elasticache)","(aws-elasticache)","(elasticache)","(elastic cache)","(elastic-cache)"],"labels":["@aws-cdk/aws-elasticache"],"assignees":["otaviomacedo"]}, {"keywords":["(@aws-cdk/aws-elasticbeanstalk)","(aws-elasticbeanstalk)","(elasticbeanstalk)","(elastic beanstalk)","(elastic-beanstalk)"],"labels":["@aws-cdk/aws-elasticbeanstalk"],"assignees":["skinny85"]}, {"keywords":["(@aws-cdk/aws-elasticloadbalancing)","(aws-elasticloadbalancing)","(elasticloadbalancing)","(elastic loadbalancing)","(elastic-loadbalancing)","(elb)"],"labels":["@aws-cdk/aws-elasticloadbalancing"],"assignees":["njlynch"]}, @@ -201,8 +201,8 @@ jobs: {"keywords":["(@aws-cdk/aws-sqs)","(aws-sqs)","(sqs)"],"labels":["@aws-cdk/aws-sqs"],"assignees":["njlynch"]}, {"keywords":["(@aws-cdk/aws-ssm)","(aws-ssm)","(ssm)"],"labels":["@aws-cdk/aws-ssm"],"assignees":["njlynch"]}, {"keywords":["(@aws-cdk/aws-sso)","(aws-sso)","(sso)"],"labels":["@aws-cdk/aws-sso"],"assignees":["skinny85"]}, - {"keywords":["(@aws-cdk/aws-stepfunctions)","(aws-stepfunctions)","(stepfunctions)","(step functions)","(step-functions)"],"labels":["@aws-cdk/aws-stepfunctions"],"assignees":["shivlaks"]}, - {"keywords":["(@aws-cdk/aws-stepfunctions-tasks)","(aws-stepfunctions-tasks)","(stepfunctions-tasks)","(stepfunctions tasks)"],"labels":["@aws-cdk/aws-stepfunctions-tasks"],"assignees":["shivlaks"]}, + {"keywords":["(@aws-cdk/aws-stepfunctions)","(aws-stepfunctions)","(stepfunctions)","(step functions)","(step-functions)"],"labels":["@aws-cdk/aws-stepfunctions"],"assignees":["BenChaimberg"]}, + {"keywords":["(@aws-cdk/aws-stepfunctions-tasks)","(aws-stepfunctions-tasks)","(stepfunctions-tasks)","(stepfunctions tasks)"],"labels":["@aws-cdk/aws-stepfunctions-tasks"],"assignees":["BenChaimberg"]}, {"keywords":["(@aws-cdk/aws-synthetics)","(aws-synthetics)","(synthetics)"],"labels":["@aws-cdk/aws-synthetics"],"assignees":["BenChaimberg"]}, {"keywords":["(@aws-cdk/aws-timestream)","(aws-timestream)","(timestream)"],"labels":["@aws-cdk/aws-timestream"],"assignees":["skinny85"]}, {"keywords":["(@aws-cdk/aws-transfer)","(aws-transfer)","(transfer)"],"labels":["@aws-cdk/aws-transfer"],"assignees":["otaviomacedo"]}, diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index 20d9073b02b3d..1b0d2e66c0594 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -63,12 +63,7 @@ jobs: run: yarn install - name: Run "yarn upgrade" - # jsdom breaks us starting from 16.5.1. Caused by https://github.com/feross/queue-microtask/issues/17 - # in combination with https://github.com/jsdom/jsdom/commit/31eb938fdaa5d446e194c9ec4f0d6b46b4354954 - # pinning this for now since its only used in tests (by jest-enviroment-jsdom). - # we are not even using this because our environment is 'node' - just the mere fact this module is loaded is what breaks. - # also - jest-enviroment-jsdom doesnt actually require 16.5.1 (https://github.com/facebook/jest/blob/master/packages/jest-environment-jsdom/package.json#L23) - run: yarn upgrade --pattern '!(jsdom)' + run: yarn upgrade # Next, create and upload the changes as a patch file. This will later be downloaded to create a pull request # Creating a pull request requires write permissions and it's best to keep write privileges isolated. diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c085f2b3080..4b8b3d4e4c906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ 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.108.0](https://github.com/aws/aws-cdk/compare/v1.107.0...v1.108.0) (2021-06-09) + + +### ⚠ BREAKING CHANGES + +* **cfnspec:** `imageScanningConfiguration` property of `ecr.CfnRepository` now accepts `scanOnPush` instead of `ScanOnPush` (notice the casing change). + +### Features + +* **cfnspec:** cloudformation spec v37.1.0 ([#14951](https://github.com/aws/aws-cdk/issues/14951)) ([aee0f58](https://github.com/aws/aws-cdk/commit/aee0f58b3c36b2bf8441b1f02c3cc936b55ab6f6)) +* Parameterize bootstrap stack version ([#14626](https://github.com/aws/aws-cdk/issues/14626)) ([a37108c](https://github.com/aws/aws-cdk/commit/a37108cef1132d21443561cc36771a30a7a53598)) +* **cli:** new bootstrap supports cross-account lookups ([#14874](https://github.com/aws/aws-cdk/issues/14874)) ([f66f4b8](https://github.com/aws/aws-cdk/commit/f66f4b80da22b4d24d4419acc3984b56d5690b2e)), closes [#8905](https://github.com/aws/aws-cdk/issues/8905) +* **cognito:** user pool - customize mfa message ([#14241](https://github.com/aws/aws-cdk/issues/14241)) ([a12db62](https://github.com/aws/aws-cdk/commit/a12db624ce394f5b9e786a5eea35be6716265673)) +* **custom-resources:** support custom lambda role in provider framework ([#12131](https://github.com/aws/aws-cdk/issues/12131)) ([bc01207](https://github.com/aws/aws-cdk/commit/bc0120719b8e16737b484c6b504b99d99656d1e1)), closes [#12126](https://github.com/aws/aws-cdk/issues/12126) +* **ec2:** Implement UserData methods in MultipartUserData ([#14347](https://github.com/aws/aws-cdk/issues/14347)) ([d1b6ce4](https://github.com/aws/aws-cdk/commit/d1b6ce44f6058c8ae037696a4e0d0557f9375062)) +* **ecs:** Adding support for ECS Exec ([#14670](https://github.com/aws/aws-cdk/issues/14670)) ([b35328c](https://github.com/aws/aws-cdk/commit/b35328c1197dfed572532e114d1ded89ddb523ac)) +* **ecs-patterns:** Add Load Balancer name to ApplicationLoadBalancedFargateService props ([#14831](https://github.com/aws/aws-cdk/issues/14831)) ([c432fb4](https://github.com/aws/aws-cdk/commit/c432fb40e793bac27fdf9197bb2ef7b0765c5daa)) +* **ecs-patterns:** Add support for Docker labels to ECS Patterns ([#14783](https://github.com/aws/aws-cdk/issues/14783)) ([00c11b5](https://github.com/aws/aws-cdk/commit/00c11b512b45a65c632c24893ccd576e076a98d3)) +* **elb:** set accessLoggingPolicy property with L2 LoadBalancer ([#14983](https://github.com/aws/aws-cdk/issues/14983)) ([252dfa2](https://github.com/aws/aws-cdk/commit/252dfa2f84f24ef57ab632e8ee5092544c850a5f)), closes [#14972](https://github.com/aws/aws-cdk/issues/14972) +* **events:** support embedded string variables ([#13487](https://github.com/aws/aws-cdk/issues/13487)) ([a5d27aa](https://github.com/aws/aws-cdk/commit/a5d27aabc7cab223f4000946506aa0c06c5f34b5)), closes [#9191](https://github.com/aws/aws-cdk/issues/9191) [#9191](https://github.com/aws/aws-cdk/issues/9191) +* **kms:** introduce `fromCfnKey()` method ([#14859](https://github.com/aws/aws-cdk/issues/14859)) ([1ff5b9e](https://github.com/aws/aws-cdk/commit/1ff5b9e5b728116171cb1922a861c1ecd4105292)), closes [#9719](https://github.com/aws/aws-cdk/issues/9719) [#14795](https://github.com/aws/aws-cdk/issues/14795) [#14809](https://github.com/aws/aws-cdk/issues/14809) +* **route-53:** add ability to create DS Records ([#14726](https://github.com/aws/aws-cdk/issues/14726)) ([f0c9726](https://github.com/aws/aws-cdk/commit/f0c9726487f9a46a4637f093725b7e0eb5dd4791)) +* **route53-targets:** route53 record target ([#14820](https://github.com/aws/aws-cdk/issues/14820)) ([b22da80](https://github.com/aws/aws-cdk/commit/b22da808ff124fddc643adc3b66dbd6e435cf175)), closes [#14800](https://github.com/aws/aws-cdk/issues/14800) +* **s3:** support ExpiredObjectDeleteMarker ([#14970](https://github.com/aws/aws-cdk/issues/14970)) ([f932e0f](https://github.com/aws/aws-cdk/commit/f932e0fbcf95f755d11bd322e6ac9c350b38c149)), closes [#14752](https://github.com/aws/aws-cdk/issues/14752) + + +### Bug Fixes + +* **apigatewayv2:** http api - default route does not use the default authorizer ([#14904](https://github.com/aws/aws-cdk/issues/14904)) ([25412a6](https://github.com/aws/aws-cdk/commit/25412a60971d3e332fa22fad4c44122eef9dfd2c)) +* **cli:** cross account docker image assets upload no longer works ([#14816](https://github.com/aws/aws-cdk/issues/14816)) ([14fbb11](https://github.com/aws/aws-cdk/commit/14fbb11af407a5834dedb6aeb095285dd44695ba)), closes [#14815](https://github.com/aws/aws-cdk/issues/14815) +* **cli:** image publishing role doesn't have docker pull permissions ([#14662](https://github.com/aws/aws-cdk/issues/14662)) ([beaffa9](https://github.com/aws/aws-cdk/commit/beaffa9aec25875649ad4ef02d0885d8de0f5eac)), closes [#14656](https://github.com/aws/aws-cdk/issues/14656) +* **core:** property overrides fail for references ([#15018](https://github.com/aws/aws-cdk/issues/15018)) ([ebac8bc](https://github.com/aws/aws-cdk/commit/ebac8bc08885d6862f75b1133752b639dcf54b1c)) +* **docs:** fixed typos in documentation ([#14760](https://github.com/aws/aws-cdk/issues/14760)) ([ced9b38](https://github.com/aws/aws-cdk/commit/ced9b38e0e30613befd48a9e198086412d19c175)) +* **ec2:** add missing entry for XLARGE3 ([#14750](https://github.com/aws/aws-cdk/issues/14750)) ([af6d49f](https://github.com/aws/aws-cdk/commit/af6d49f2e245b60ae3bbea3bb2c5d283beedba3f)) +* **ecs:** Can't enable both Fargate and ASG capacity providers on ECS Cluster ([#15012](https://github.com/aws/aws-cdk/issues/15012)) ([6b2d0e0](https://github.com/aws/aws-cdk/commit/6b2d0e0c867651cd632be9ca99c6e342fb3c1067)), closes [#14730](https://github.com/aws/aws-cdk/issues/14730) +* **events:** AwsApi warns if service does not exist ([#13352](https://github.com/aws/aws-cdk/issues/13352)) ([3bad98f](https://github.com/aws/aws-cdk/commit/3bad98f9cafa88c4c8a26502798afea3c3f0e146)), closes [#13090](https://github.com/aws/aws-cdk/issues/13090) +* **lambda-nodejs:** pnpm exec command ([#14954](https://github.com/aws/aws-cdk/issues/14954)) ([df16d40](https://github.com/aws/aws-cdk/commit/df16d40352e56c2d4b33b2066f3fe030792d32d6)), closes [#14757](https://github.com/aws/aws-cdk/issues/14757) [#14772](https://github.com/aws/aws-cdk/issues/14772) +* **s3:** `autoDeleteObjects` had redundant `GetObject*` permissions ([#14573](https://github.com/aws/aws-cdk/issues/14573)) ([f9be15d](https://github.com/aws/aws-cdk/commit/f9be15d9bd130519735077cda079c2e6e9e43a02)), closes [#14572](https://github.com/aws/aws-cdk/issues/14572) +* **stepfunctions:** repeated object references not allowed even if not a circular reference ([#14628](https://github.com/aws/aws-cdk/issues/14628)) ([486990f](https://github.com/aws/aws-cdk/commit/486990f9d771779cacb008dfe347a65705146818)), closes [#14596](https://github.com/aws/aws-cdk/issues/14596) + ## [1.107.0](https://github.com/aws/aws-cdk/compare/v1.106.1...v1.107.0) (2021-06-02) diff --git a/design/construct-tree.md b/design/construct-tree.md index 9c9c44c327f74..9771ee6f9cd21 100644 --- a/design/construct-tree.md +++ b/design/construct-tree.md @@ -16,7 +16,7 @@ ALL CDK applications are composed of constructs, which are the basic building bl A construct can represent a single resource, such as an Amazon Simple Storage Service (Amazon S3) bucket, or it can represent a higher-level component consisting of multiple AWS CDK resources. Examples of such components include a worker queue with its associated compute capacity, a cron job with monitoring resources and a dashboard, or even an entire app spanning multiple AWS accounts and regions. -The CDK CLI is the primary mechanism through which developers currently interact with their AWS CDK applications. It supports various operations throughout the lifecycle of application development from from the initialization of a CDK app from a template to the deployment and destruction of the AWS CloudFormation stacks. +The CDK CLI is the primary mechanism through which developers currently interact with their AWS CDK applications. It supports various operations throughout the lifecycle of application development from the initialization of a CDK app from a template to the deployment and destruction of the AWS CloudFormation stacks. ## Motivation diff --git a/package.json b/package.json index a465b3f433d86..718660cf8df49 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "fs-extra": "^9.1.0", "graceful-fs": "^4.2.6", "jest-junit": "^12.1.0", - "jsii-diff": "^1.29.0", - "jsii-pacmak": "^1.29.0", - "jsii-reflect": "^1.29.0", - "jsii-rosetta": "^1.29.0", + "jsii-diff": "^1.30.0", + "jsii-pacmak": "^1.30.0", + "jsii-reflect": "^1.30.0", + "jsii-rosetta": "^1.30.0", "lerna": "^4.0.0", "patch-package": "^6.4.7", "standard-version": "^9.3.0", @@ -97,6 +97,8 @@ "@aws-cdk/core/minimatch/**", "@aws-cdk/cx-api/semver", "@aws-cdk/cx-api/semver/**", + "@aws-cdk/aws-events-targets/aws-sdk", + "@aws-cdk/aws-events-targets/aws-sdk/**", "@aws-cdk/yaml-cfn/yaml", "@aws-cdk/yaml-cfn/yaml/**", "aws-cdk-lib/@balena/dockerignore", diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index 990a4dd376c50..1912230b41879 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -62,7 +62,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^2.14.0", + "fast-check": "^2.16.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index 7aebeb33359e4..441dea3e8075d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -72,7 +72,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/jest": "^26.0.23", - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 73abc83c16111..f650d62bd289b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -422,6 +422,8 @@ export class HttpApi extends HttpApiBase { httpApi: this, routeKey: HttpRouteKey.DEFAULT, integration: props.defaultIntegration, + authorizer: props.defaultAuthorizer, + authorizationScopes: props.defaultAuthorizationScopes, }); } 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 3b07593676c11..c2324412d3396 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -400,6 +400,24 @@ describe('HttpApi', () => { }); }); + test('can add default authorizer when using default integration', () => { + const stack = new Stack(); + + const authorizer = new DummyAuthorizer(); + + new HttpApi(stack, 'api', { + defaultIntegration: new DummyRouteIntegration(), + defaultAuthorizer: authorizer, + defaultAuthorizationScopes: ['read:pets'], + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Route', { + AuthorizerId: 'auth-1234', + AuthorizationType: 'JWT', + AuthorizationScopes: ['read:pets'], + }); + }); + test('can add default authorizer, but remove it for a route', () => { const stack = new Stack(); const authorizer = new DummyAuthorizer(); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index ed475aefa0b72..8c50e08a716f7 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -73,7 +73,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^2.14.0", + "fast-check": "^2.16.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 12709e6ccaa55..90587ef811f35 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -65,7 +65,7 @@ "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "fast-check": "^2.14.0", + "fast-check": "^2.16.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" 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 13c4f54cacc2e..dc62ee1c01e12 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 @@ -27,21 +27,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/sinon": "^9.0.11", "cdk-build-tools": "0.0.0", "aws-sdk": "^2.596.0", "aws-sdk-mock": "^5.1.0", - "eslint": "^7.27.0", + "eslint": "^7.28.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.23.3", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "jest": "^26.6.3", "lambda-tester": "^3.6.0", "sinon": "^9.2.4", - "nock": "^13.0.11", + "nock": "^13.1.0", "ts-jest": "^26.5.6" } } diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 9433981c2133a..148f2a422f59f 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -72,7 +72,7 @@ "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 28e37670b05a2..9f598761ee843 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -527,6 +527,13 @@ export interface UserPoolProps { */ readonly mfa?: Mfa; + /** + * The SMS message template sent during MFA verification. + * Use '{####}' in the template where Cognito should insert the verification code. + * @default 'Your authentication code is {####}.' + */ + readonly mfaMessage?: string; + /** * Configure the MFA types that users can use in this user pool. Ignored if `mfa` is set to `OFF`. * @@ -761,6 +768,7 @@ export class UserPool extends UserPoolBase { aliasAttributes: signIn.aliasAttrs, autoVerifiedAttributes: signIn.autoVerifyAttrs, lambdaConfig: Lazy.any({ produce: () => undefinedIfNoKeys(this.triggers) }), + smsAuthenticationMessage: this.mfaMessage(props), smsConfiguration: this.smsConfiguration(props), adminCreateUserConfig, emailVerificationMessage, @@ -810,6 +818,24 @@ export class UserPool extends UserPoolBase { }); } + private mfaMessage(props: UserPoolProps): string | undefined { + const CODE_TEMPLATE = '{####}'; + const MAX_LENGTH = 140; + const message = props.mfaMessage; + + if (message && !Token.isUnresolved(message)) { + if (!message.includes(CODE_TEMPLATE)) { + throw new Error(`MFA message must contain the template string '${CODE_TEMPLATE}'`); + } + + if (message.length > MAX_LENGTH) { + throw new Error(`MFA message must be between ${CODE_TEMPLATE.length} and ${MAX_LENGTH} characters`); + } + } + + return message; + } + private verificationMessageConfiguration(props: UserPoolProps): CfnUserPool.VerificationMessageTemplateProperty { const CODE_TEMPLATE = '{####}'; const VERIFY_EMAIL_TEMPLATE = '{##Verify Email##}'; 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 7f1c02c786a4e..ccd5959750a58 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -29,6 +29,7 @@ describe('User Pool', () => { EmailSubject: 'Verify your new account', SmsMessage: 'The verification code to your new account is {####}', }, + SmsAuthenticationMessage: ABSENT, SmsConfiguration: ABSENT, lambdaTriggers: ABSENT, }); @@ -80,6 +81,49 @@ describe('User Pool', () => { }); }), + test('mfa authentication message is configured correctly', () => { + // GIVEN + const stack = new Stack(); + const message = 'The authentication code to your account is {####}'; + + // WHEN + new UserPool(stack, 'Pool', { + mfaMessage: message, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPool', { + SmsAuthenticationMessage: message, + }); + }), + + test('mfa authentication message is validated', () => { + const stack = new Stack(); + + expect(() => new UserPool(stack, 'Pool1', { + mfaMessage: '{####', + })).toThrow(/MFA message must contain the template string/); + + expect(() => new UserPool(stack, 'Pool2', { + mfaMessage: '{####}', + })).not.toThrow(); + + expect(() => new UserPool(stack, 'Pool3', { + mfaMessage: `{####}${'x'.repeat(135)}`, + })).toThrow(/MFA message must be between 6 and 140 characters/); + + expect(() => new UserPool(stack, 'Pool4', { + mfaMessage: `{####}${'x'.repeat(134)}`, + })).not.toThrow(); + + // Validation is skipped for tokens. + const parameter = new CfnParameter(stack, 'Parameter'); + + expect(() => new UserPool(stack, 'Pool5', { + mfaMessage: parameter.valueAsString, + })).not.toThrow(); + }); + test('email and sms verification messages are validated', () => { const stack = new Stack(); @@ -1355,4 +1399,4 @@ function fooFunction(scope: Construct, name: string): lambda.IFunction { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', }); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cur/.eslintrc.js b/packages/@aws-cdk/aws-cur/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-cur/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cur/.gitignore b/packages/@aws-cdk/aws-cur/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-cur/.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-cur/.npmignore b/packages/@aws-cdk/aws-cur/.npmignore new file mode 100644 index 0000000000000..e4486030fcb17 --- /dev/null +++ b/packages/@aws-cdk/aws-cur/.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/ diff --git a/packages/@aws-cdk/aws-cur/LICENSE b/packages/@aws-cdk/aws-cur/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-cur/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-cur/NOTICE b/packages/@aws-cdk/aws-cur/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-cur/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-cur/README.md b/packages/@aws-cdk/aws-cur/README.md new file mode 100644 index 0000000000000..b889cab0377cc --- /dev/null +++ b/packages/@aws-cdk/aws-cur/README.md @@ -0,0 +1,20 @@ +# AWS::CUR 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 cur = require('@aws-cdk/aws-cur'); +``` diff --git a/packages/@aws-cdk/aws-cur/jest.config.js b/packages/@aws-cdk/aws-cur/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-cur/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-cur/lib/index.ts b/packages/@aws-cdk/aws-cur/lib/index.ts new file mode 100644 index 0000000000000..9ccbf7b4f2ab0 --- /dev/null +++ b/packages/@aws-cdk/aws-cur/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::CUR CloudFormation Resources: +export * from './cur.generated'; diff --git a/packages/@aws-cdk/aws-cur/package.json b/packages/@aws-cdk/aws-cur/package.json new file mode 100644 index 0000000000000..a0263254c1c4f --- /dev/null +++ b/packages/@aws-cdk/aws-cur/package.json @@ -0,0 +1,101 @@ +{ + "name": "@aws-cdk/aws-cur", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::CUR", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.CUR", + "packageId": "Amazon.CDK.AWS.CUR", + "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.cur", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cur" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-cur", + "module": "aws_cdk.aws_cur" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cur" + }, + "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" + }, + "cdk-build": { + "cloudformation": "AWS::CUR", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::CUR", + "aws-cur" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.22", + "@aws-cdk/assert-internal": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + }, + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-cur/test/cur.test.ts b/packages/@aws-cdk/aws-cur/test/cur.test.ts new file mode 100644 index 0000000000000..c4505ad966984 --- /dev/null +++ b/packages/@aws-cdk/aws-cur/test/cur.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert-internal/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); 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 cb343eb6c866b..d5e2951e0a07c 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 @@ -29,14 +29,14 @@ "devDependencies": { "aws-sdk": "^2.596.0", "aws-sdk-mock": "^5.1.0", - "eslint": "^7.27.0", + "eslint": "^7.28.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.23.3", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "jest": "^26.6.3", "lambda-tester": "^3.6.0", - "nock": "^13.0.11" + "nock": "^13.1.0" } } diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index db9b6b51ea3f5..93b8bb0c9cb47 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -71,7 +71,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/jest": "^26.0.23", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 434729361bfa9..fc48c31121bfe 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -1115,6 +1115,23 @@ new ec2.LaunchTemplate(stack, '', { For more information see [Specifying Multiple User Data Blocks Using a MIME Multi Part Archive](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/bootstrap_container_instance.html#multi-part_user_data) +#### Using add*Command on MultipartUserData + +To use the `add*Command` methods, that are inherited from the `UserData` interface, on `MultipartUserData` you must add a part +to the `MultipartUserData` and designate it as the reciever for these methods. This is accomplished by using the `addUserDataPart()` +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); + +// Adding commands to the multipartUserData adds them to commandsUserData, and vice-versa. +multipartUserData.addCommands('touch /root/multi.txt'); +commandsUserData.addCommands('touch /root/userdata.txt'); +``` + +When used on an EC2 instance, the above `multipartUserData` will create both `multi.txt` and `userdata.txt` in `/root`. ## Importing existing subnet diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index fc3b3b411d7b3..a12dfb92061c6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -527,6 +527,11 @@ export enum InstanceSize { */ XLARGE2 = '2xlarge', + /** + * Instance size XLARGE3 (3xlarge) + */ + XLARGE3 = '3xlarge', + /** * Instance size XLARGE4 (4xlarge) */ diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index c56a6b9dd34d1..90613e3904475 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -463,7 +463,9 @@ export class SecurityGroup extends SecurityGroupBase { // In the case of "allowAllOutbound", we don't add any more rules. There // is only one rule which allows all traffic and that subsumes any other // rule. - Annotations.of(this).addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customize rules, set allowAllOutbound=false on the SecurityGroup'); + if (!remoteRule) { // Warn only if addEgressRule() was explicitely called + Annotations.of(this).addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customize rules, set allowAllOutbound=false on the SecurityGroup'); + } return; } else { // Otherwise, if the bogus rule exists we can now remove it because the diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts index 418b6d671846d..9b835744b3a6e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/user-data.ts +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -436,13 +436,15 @@ export interface MultipartUserDataOptions { * */ export class MultipartUserData extends UserData { - private static readonly USE_PART_ERROR = 'MultipartUserData does not support this operation. Please add part using addPart.'; + private static readonly USE_PART_ERROR = 'MultipartUserData only supports this operation if it has a default UserData. Call addUserDataPart with makeDefault=true.'; private static readonly BOUNDRY_PATTERN = '[^a-zA-Z0-9()+,-./:=?]'; private parts: MultipartBody[] = []; private opts: MultipartUserDataOptions; + private defaultUserData?: UserData; + constructor(opts?: MultipartUserDataOptions) { super(); @@ -472,16 +474,28 @@ export class MultipartUserData extends UserData { } /** - * Adds a multipart part based on a UserData object + * Adds a multipart part based on a UserData object. + * + * If `makeDefault` is true, then the UserData added by this method + * will also be the target of calls to the `add*Command` methods on + * this MultipartUserData object. * - * This is the same as calling: + * If `makeDefault` is false, then this is the same as calling: * * ```ts * multiPart.addPart(MultipartBody.fromUserData(userData, contentType)); * ``` + * + * An undefined `makeDefault` defaults to either: + * - `true` if no default UserData has been set yet; or + * - `false` if there is no default UserData set. */ - public addUserDataPart(userData: UserData, contentType?: string) { + public addUserDataPart(userData: UserData, contentType?: string, makeDefault?: boolean) { this.addPart(MultipartBody.fromUserData(userData, contentType)); + makeDefault = makeDefault ?? (this.defaultUserData === undefined ? true : false); + if (makeDefault) { + this.defaultUserData = userData; + } } public render(): string { @@ -510,23 +524,43 @@ export class MultipartUserData extends UserData { return resultArchive.join('\n'); } - public addS3DownloadCommand(_params: S3DownloadOptions): string { - throw new Error(MultipartUserData.USE_PART_ERROR); + public addS3DownloadCommand(params: S3DownloadOptions): string { + if (this.defaultUserData) { + return this.defaultUserData.addS3DownloadCommand(params); + } else { + throw new Error(MultipartUserData.USE_PART_ERROR); + } } - public addExecuteFileCommand(_params: ExecuteFileOptions): void { - throw new Error(MultipartUserData.USE_PART_ERROR); + public addExecuteFileCommand(params: ExecuteFileOptions): void { + if (this.defaultUserData) { + this.defaultUserData.addExecuteFileCommand(params); + } else { + throw new Error(MultipartUserData.USE_PART_ERROR); + } } - public addSignalOnExitCommand(_resource: Resource): void { - throw new Error(MultipartUserData.USE_PART_ERROR); + public addSignalOnExitCommand(resource: Resource): void { + if (this.defaultUserData) { + this.defaultUserData.addSignalOnExitCommand(resource); + } else { + throw new Error(MultipartUserData.USE_PART_ERROR); + } } - public addCommands(..._commands: string[]): void { - throw new Error(MultipartUserData.USE_PART_ERROR); + public addCommands(...commands: string[]): void { + if (this.defaultUserData) { + this.defaultUserData.addCommands(...commands); + } else { + throw new Error(MultipartUserData.USE_PART_ERROR); + } } - public addOnExitCommands(..._commands: string[]): void { - throw new Error(MultipartUserData.USE_PART_ERROR); + public addOnExitCommands(...commands: string[]): void { + if (this.defaultUserData) { + this.defaultUserData.addOnExitCommands(...commands); + } else { + throw new Error(MultipartUserData.USE_PART_ERROR); + } } } diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 5b6e630634d10..30b8064dc19bc 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -71,7 +71,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/jest": "^26.0.23", "@aws-cdk/cx-api": "0.0.0", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index d5b77e01ad0ea..392aeb8ec22d9 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -21,17 +21,30 @@ beforeEach(() => { nodeunitShim({ 'instance is created correctly'(test: Test) { - // WHEN - new Instance(stack, 'Instance', { - vpc, - machineImage: new AmazonLinuxImage(), - instanceType: InstanceType.of(InstanceClass.BURSTABLE4_GRAVITON, InstanceSize.LARGE), - }); + // GIVEN + const sampleInstances = [{ + instanceClass: InstanceClass.BURSTABLE4_GRAVITON, + instanceSize: InstanceSize.LARGE, + instanceType: 't4g.large', + }, { + instanceClass: InstanceClass.HIGH_COMPUTE_MEMORY1, + instanceSize: InstanceSize.XLARGE3, + instanceType: 'z1d.3xlarge', + }]; - // THEN - cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { - InstanceType: 't4g.large', - })); + for (const [i, sampleInstance] of sampleInstances.entries()) { + // WHEN + new Instance(stack, `Instance${i}`, { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: InstanceType.of(sampleInstance.instanceClass, sampleInstance.instanceSize), + }); + + // THEN + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { + InstanceType: sampleInstance.instanceType, + })); + } test.done(); }, diff --git a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts index 26493962cbbb8..c3a7538c07f66 100644 --- a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts @@ -370,4 +370,284 @@ nodeunitShim({ test.done(); }, + 'Multipart user data throws when adding on exit commands'(test: Test) { + // GIVEN + // WHEN + const userData = new ec2.MultipartUserData(); + + // THEN + test.throws(() => userData.addOnExitCommands( 'a command goes here' )); + test.done(); + }, + 'Multipart user data throws when adding signal command'(test: Test) { + // GIVEN + const stack = new Stack(); + const resource = new ec2.Vpc(stack, 'RESOURCE'); + + // WHEN + const userData = new ec2.MultipartUserData(); + + // THEN + test.throws(() => userData.addSignalOnExitCommand( resource )); + test.done(); + }, + 'Multipart user data throws when downloading file'(test: Test) { + // GIVEN + const stack = new Stack(); + const userData = new ec2.MultipartUserData(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + // WHEN + // THEN + test.throws(() => userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.sh', + } )); + test.done(); + }, + 'Multipart user data throws when executing file'(test: Test) { + // GIVEN + const userData = new ec2.MultipartUserData(); + + // WHEN + // THEN + test.throws(() => + userData.addExecuteFileCommand({ + filePath: '/tmp/filename.sh', + } )); + test.done(); + }, + + 'can add commands to Multipart user data'(test: Test) { + // GIVEN + const stack = new Stack(); + const innerUserData = ec2.UserData.forLinux(); + const userData = new ec2.MultipartUserData(); + + // WHEN + userData.addUserDataPart(innerUserData, ec2.MultipartBody.SHELL_SCRIPT, true); + userData.addCommands('command1', 'command2'); + + // THEN + const expectedInner = '#!/bin/bash\ncommand1\ncommand2'; + const rendered = innerUserData.render(); + test.equals(rendered, expectedInner); + const out = stack.resolve(userData.render()); + test.equals(out, { + 'Fn::Join': [ + '', + [ + [ + 'Content-Type: multipart/mixed; boundary="+AWS+CDK+User+Data+Separator=="', + 'MIME-Version: 1.0', + '', + '--+AWS+CDK+User+Data+Separator==', + 'Content-Type: text/x-shellscript; charset="utf-8"', + 'Content-Transfer-Encoding: base64', + '', + '', + ].join('\n'), + { + 'Fn::Base64': expectedInner, + }, + '\n--+AWS+CDK+User+Data+Separator==--\n', + ], + ], + }); + test.done(); + }, + 'can add commands on exit to Multipart user data'(test: Test) { + // GIVEN + const stack = new Stack(); + const innerUserData = ec2.UserData.forLinux(); + const userData = new ec2.MultipartUserData(); + + // WHEN + userData.addUserDataPart(innerUserData, ec2.MultipartBody.SHELL_SCRIPT, true); + userData.addCommands('command1', 'command2'); + userData.addOnExitCommands('onexit1', 'onexit2'); + + // THEN + const expectedInner = '#!/bin/bash\n' + + 'function exitTrap(){\n' + + 'exitCode=$?\n' + + 'onexit1\n' + + 'onexit2\n' + + '}\n' + + 'trap exitTrap EXIT\n' + + 'command1\n' + + 'command2'; + const rendered = stack.resolve(innerUserData.render()); + test.equals(rendered, expectedInner); + const out = stack.resolve(userData.render()); + test.equals(out, { + 'Fn::Join': [ + '', + [ + [ + 'Content-Type: multipart/mixed; boundary="+AWS+CDK+User+Data+Separator=="', + 'MIME-Version: 1.0', + '', + '--+AWS+CDK+User+Data+Separator==', + 'Content-Type: text/x-shellscript; charset="utf-8"', + 'Content-Transfer-Encoding: base64', + '', + '', + ].join('\n'), + { + 'Fn::Base64': expectedInner, + }, + '\n--+AWS+CDK+User+Data+Separator==--\n', + ], + ], + }); + test.done(); + }, + 'can add Signal Command to Multipart user data'(test: Test) { + // GIVEN + const stack = new Stack(); + const resource = new ec2.Vpc(stack, 'RESOURCE'); + const innerUserData = ec2.UserData.forLinux(); + const userData = new ec2.MultipartUserData(); + + // WHEN + userData.addUserDataPart(innerUserData, ec2.MultipartBody.SHELL_SCRIPT, true); + userData.addCommands('command1'); + userData.addSignalOnExitCommand( resource ); + + // THEN + const expectedInner = stack.resolve('#!/bin/bash\n' + + 'function exitTrap(){\n' + + 'exitCode=$?\n' + + `/opt/aws/bin/cfn-signal --stack Default --resource RESOURCE1989552F --region ${Aws.REGION} -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n` + + '}\n' + + 'trap exitTrap EXIT\n' + + 'command1'); + const rendered = stack.resolve(innerUserData.render()); + test.equals(rendered, expectedInner); + const out = stack.resolve(userData.render()); + test.equals(out, { + 'Fn::Join': [ + '', + [ + [ + 'Content-Type: multipart/mixed; boundary="+AWS+CDK+User+Data+Separator=="', + 'MIME-Version: 1.0', + '', + '--+AWS+CDK+User+Data+Separator==', + 'Content-Type: text/x-shellscript; charset="utf-8"', + 'Content-Transfer-Encoding: base64', + '', + '', + ].join('\n'), + { + 'Fn::Base64': expectedInner, + }, + '\n--+AWS+CDK+User+Data+Separator==--\n', + ], + ], + }); + test.done(); + }, + 'can add download S3 files to Multipart user data'(test: Test) { + // GIVEN + const stack = new Stack(); + const innerUserData = ec2.UserData.forLinux(); + const userData = new ec2.MultipartUserData(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + const bucket2 = Bucket.fromBucketName( stack, 'testBucket2', 'test2' ); + + // WHEN + userData.addUserDataPart(innerUserData, ec2.MultipartBody.SHELL_SCRIPT, true); + userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.sh', + } ); + userData.addS3DownloadCommand({ + bucket: bucket2, + bucketKey: 'filename2.sh', + localFile: 'c:\\test\\location\\otherScript.sh', + } ); + + // THEN + const expectedInner = '#!/bin/bash\n' + + 'mkdir -p $(dirname \'/tmp/filename.sh\')\n' + + 'aws s3 cp \'s3://test/filename.sh\' \'/tmp/filename.sh\'\n' + + 'mkdir -p $(dirname \'c:\\test\\location\\otherScript.sh\')\n' + + 'aws s3 cp \'s3://test2/filename2.sh\' \'c:\\test\\location\\otherScript.sh\''; + const rendered = stack.resolve(innerUserData.render()); + test.equals(rendered, expectedInner); + const out = stack.resolve(userData.render()); + test.equals(out, { + 'Fn::Join': [ + '', + [ + [ + 'Content-Type: multipart/mixed; boundary="+AWS+CDK+User+Data+Separator=="', + 'MIME-Version: 1.0', + '', + '--+AWS+CDK+User+Data+Separator==', + 'Content-Type: text/x-shellscript; charset="utf-8"', + 'Content-Transfer-Encoding: base64', + '', + '', + ].join('\n'), + { + 'Fn::Base64': expectedInner, + }, + '\n--+AWS+CDK+User+Data+Separator==--\n', + ], + ], + }); + test.done(); + }, + 'can add execute files to Multipart user data'(test: Test) { + // GIVEN + const stack = new Stack(); + const innerUserData = ec2.UserData.forLinux(); + const userData = new ec2.MultipartUserData(); + + // WHEN + userData.addUserDataPart(innerUserData, ec2.MultipartBody.SHELL_SCRIPT, true); + userData.addExecuteFileCommand({ + filePath: '/tmp/filename.sh', + } ); + userData.addExecuteFileCommand({ + filePath: '/test/filename2.sh', + arguments: 'arg1 arg2 -arg $variable', + } ); + + // THEN + const expectedInner = '#!/bin/bash\n' + + 'set -e\n' + + 'chmod +x \'/tmp/filename.sh\'\n' + + '\'/tmp/filename.sh\'\n' + + 'set -e\n' + + 'chmod +x \'/test/filename2.sh\'\n' + + '\'/test/filename2.sh\' arg1 arg2 -arg $variable'; + const rendered = stack.resolve(innerUserData.render()); + test.equals(rendered, expectedInner); + const out = stack.resolve(userData.render()); + test.equals(out, { + 'Fn::Join': [ + '', + [ + [ + 'Content-Type: multipart/mixed; boundary="+AWS+CDK+User+Data+Separator=="', + 'MIME-Version: 1.0', + '', + '--+AWS+CDK+User+Data+Separator==', + 'Content-Type: text/x-shellscript; charset="utf-8"', + 'Content-Transfer-Encoding: base64', + '', + '', + ].join('\n'), + { + 'Fn::Base64': expectedInner, + }, + '\n--+AWS+CDK+User+Data+Separator==--\n', + ], + ], + }); + test.done(); + }, }); diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 16331864e1e23..af8547ba5edaf 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -485,7 +485,7 @@ export class Repository extends RepositoryBase { repositoryPolicyText: Lazy.any({ produce: () => this.policyDocument }), lifecyclePolicy: Lazy.any({ produce: () => this.renderLifecyclePolicy() }), imageScanningConfiguration: !props.imageScanOnPush ? undefined : { - ScanOnPush: true, + scanOnPush: true, }, imageTagMutability: props.imageTagMutability || undefined, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index 2293c129ee245..5edbd314482fb 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -523,3 +523,44 @@ new QueueProcessingFargateService(stack, 'QueueProcessingService', { image: new ecs.AssetImage(path.join(__dirname, '..', 'sqs-reader')), }); ``` + +### Deploy application and metrics sidecar + +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', { + cluster, + vpc, + desiredCount: 1, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + }, + dockerLabels: { + 'application.label.one': 'first_label' + 'application.label.two': 'second_label' + } +}); + +service.taskDefinition.addContainer('Sidecar', { + image: ContainerImage.fromRegistry('example/metrics-sidecar') +} +``` + +### Select specific load balancer name ApplicationLoadBalancedFargateService + +```ts +const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + desiredCount: 1, + cpu: 512, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + }, + vpcSubnets: { + subnets: [ec2.Subnet.fromSubnetId(stack, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')], + }, + loadBalancerName: 'application-lb-name', +}); +``` diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index cc0bbfc3427de..3c9005af19daa 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -244,6 +244,13 @@ export interface ApplicationLoadBalancedServiceBaseProps { */ readonly circuitBreaker?: DeploymentCircuitBreaker; + /** + * Name of the load balancer + * + * @default - Automatically generated name. + */ + readonly loadBalancerName?: string; + } export interface ApplicationLoadBalancedTaskImageOptions { @@ -325,6 +332,13 @@ export interface ApplicationLoadBalancedTaskImageOptions { * @default - Automatically generated name. */ readonly family?: string; + + /** + * A key/value map of labels to add to the container. + * + * @default - No labels. + */ + readonly dockerLabels?: { [key: string]: string }; } /** @@ -403,6 +417,7 @@ export abstract class ApplicationLoadBalancedServiceBase extends CoreConstruct { const lbProps = { vpc: this.cluster.vpc, + loadBalancerName: props.loadBalancerName, internetFacing, }; 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 ff3fc675fcd69..9d9c21c9e7b5e 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 @@ -188,6 +188,13 @@ export interface ApplicationLoadBalancedTaskImageProps { * @default - Automatically generated name. */ readonly family?: string; + + /** + * A key/value map of labels to add to the container. + * + * @default - No labels. + */ + readonly dockerLabels?: { [key: string]: string }; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 5872217a43f22..0857b99ee8cfd 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -267,6 +267,13 @@ export interface NetworkLoadBalancedTaskImageOptions { * @default - Automatically generated name. */ readonly family?: string; + + /** + * A key/value map of labels to add to the container. + * + * @default - No labels. + */ + readonly dockerLabels?: { [key: string]: string }; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts index 60fd9904b8078..8eb751b07d13e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts @@ -186,6 +186,13 @@ export interface NetworkLoadBalancedTaskImageProps { * @default - Automatically generated name. */ readonly family?: string; + + /** + * A key/value map of labels to add to the container. + * + * @default - No labels. + */ + readonly dockerLabels?: { [key: string]: string }; } /** @@ -452,4 +459,4 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends CoreConstru }); } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index ed606bb72c907..6d1809a68d18b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -110,6 +110,7 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, logging: logDriver, + dockerLabels: taskImageOptions.dockerLabels, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts index 90f4afdd5fa8f..f77e7e4dccdc3 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts @@ -104,6 +104,7 @@ export class ApplicationMultipleTargetGroupsEc2Service extends ApplicationMultip environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, logging: this.logDriver, + dockerLabels: taskImageOptions.dockerLabels, }); if (taskImageOptions.containerPorts) { for (const containerPort of taskImageOptions.containerPorts) { @@ -151,4 +152,4 @@ export class ApplicationMultipleTargetGroupsEc2Service extends ApplicationMultip cloudMapOptions: props.cloudMapOptions, }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 4bae918ca67fc..b8862dfbed338 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -108,6 +108,7 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, logging: logDriver, + dockerLabels: taskImageOptions.dockerLabels, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts index f0d3b0a1571ce..12d5b25ce67fd 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts @@ -103,6 +103,7 @@ export class NetworkMultipleTargetGroupsEc2Service extends NetworkMultipleTarget environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, logging: this.logDriver, + dockerLabels: taskImageOptions.dockerLabels, }); if (taskImageOptions.containerPorts) { for (const containerPort of taskImageOptions.containerPorts) { @@ -151,4 +152,4 @@ export class NetworkMultipleTargetGroupsEc2Service extends NetworkMultipleTarget cloudMapOptions: props.cloudMapOptions, }); } -} \ No newline at end of file +} 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 326a68529272b..19a2501355223 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 @@ -146,6 +146,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc logging: logDriver, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, + dockerLabels: taskImageOptions.dockerLabels, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts index 6759e8e001376..8052e0483b16a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts @@ -136,6 +136,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu logging: this.logDriver, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, + dockerLabels: taskImageOptions.dockerLabels, }); if (taskImageOptions.containerPorts) { for (const containerPort of taskImageOptions.containerPorts) { @@ -184,4 +185,4 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu platformVersion: props.platformVersion, }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 1f2618bbca314..9095be5cf2cd1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -133,6 +133,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic logging: logDriver, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, + dockerLabels: taskImageOptions.dockerLabels, }); container.addPortMappings({ containerPort: taskImageOptions.containerPort || 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts index 4a4974af7cce3..1a185f642a558 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts @@ -136,6 +136,7 @@ export class NetworkMultipleTargetGroupsFargateService extends NetworkMultipleTa logging: this.logDriver, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, + dockerLabels: taskImageOptions.dockerLabels, }); if (taskImageOptions.containerPorts) { for (const containerPort of taskImageOptions.containerPorts) { @@ -184,4 +185,4 @@ export class NetworkMultipleTargetGroupsFargateService extends NetworkMultipleTa platformVersion: props.platformVersion, }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts index 77f171425c88a..0946123907fe2 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts @@ -107,6 +107,7 @@ export = { taskRole: new Role(stack, 'TaskRole', { assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, cpu: 256, desiredCount: 3, @@ -214,6 +215,10 @@ export = { Protocol: 'tcp', }, ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], ExecutionRoleArn: { @@ -967,6 +972,7 @@ export = { taskRole: new Role(stack, 'TaskRole', { assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, cpu: 256, desiredCount: 3, @@ -1079,6 +1085,10 @@ export = { Protocol: 'tcp', }, ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], ExecutionRoleArn: { @@ -1532,4 +1542,4 @@ export = { test.done(); }, }, -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index a028d34c22157..5ef253d71bdb1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -28,6 +28,7 @@ export = { TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', }, + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, desiredCount: 2, }); @@ -54,6 +55,10 @@ export = { }, ], Memory: 1024, + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], })); @@ -389,6 +394,7 @@ export = { TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', }, + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, desiredCount: 2, }); @@ -416,6 +422,10 @@ export = { 'awslogs-region': { Ref: 'AWS::Region' }, }, }, + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], })); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service-v2.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service-v2.ts index 81f1711c961c0..cad68a5a270ae 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service-v2.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service-v2.ts @@ -4,7 +4,7 @@ import * as ecs from '@aws-cdk/aws-ecs'; import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { Duration, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { ApplicationMultipleTargetGroupsFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; +import { ApplicationMultipleTargetGroupsFargateService, NetworkMultipleTargetGroupsFargateService, ApplicationLoadBalancedFargateService } from '../../lib'; export = { 'When Application Load Balancer': { @@ -114,6 +114,7 @@ export = { taskRole: new Role(stack, 'TaskRole', { assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, cpu: 256, assignPublicIp: true, @@ -223,6 +224,10 @@ export = { Protocol: 'tcp', }, ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], Cpu: '256', @@ -304,6 +309,83 @@ export = { test.done(); }, + + 'test Fargate loadbalancer construct with application load balancer name set'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + loadBalancerName: 'alb-test-load-balancer', + }); + + // THEN - stack contains a load balancer and a service + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Name: 'alb-test-load-balancer', + })); + + expect(stack).to(haveResource('AWS::ECS::Service', { + DesiredCount: 1, + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', + }, + }, + ], + })); + + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefwebLogGroup2A898F61', + }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, + }, + Name: 'web', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + }, + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ServiceTaskDefExecutionRole919F7BE3', + 'Arn', + ], + }, + Family: 'ServiceTaskDef79D79521', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', + ], + })); + + test.done(); + }, }, 'When Network Load Balancer': { @@ -413,6 +495,7 @@ export = { taskRole: new Role(stack, 'TaskRole', { assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, cpu: 256, assignPublicIp: true, @@ -517,6 +600,10 @@ export = { Protocol: 'tcp', }, ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, }, ], Cpu: '256', @@ -599,4 +686,4 @@ export = { test.done(); }, }, -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index a8c588d1187ef..b0f8d0d573d7f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -1012,4 +1012,65 @@ export = { }, + 'test ALB load balanced service with docker labels defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + }, + ], + })); + + test.done(); + }, + + 'test Network load balanced service with docker labels defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + }, + ], + })); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 5e63747de137f..6aa894f458b94 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -850,3 +850,60 @@ taskDefinition.addContainer('cont', { inferenceAcceleratorResources, }); ``` + +## ECS Exec command + +Please note, ECS Exec leverages AWS Systems Manager (SSM). So as a prerequisite for the exec command +to work, you need to have the SSM plugin for the AWS CLI installed locally. For more information, see +[Install Session Manager plugin for AWS CLI] (https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html). + +To enable the ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in +your `Ec2Service` or `FargateService`. + +```ts +const service = new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); +``` + +### Enabling logging + +You can enable sending logs of your execute session commands to a CloudWatch log group or S3 bucket by configuring +the `executeCommandConfiguration` property for your cluster. The default configuration will send the +logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, +when using your own `logConfiguration` the log group or S3 Bucket specified must already be created. + +To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKey` field +of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key +to these resources on creation. + +```ts +const kmsKey = new kms.Key(stack, 'KmsKey'); + +// Pass the KMS key in the `encryptionKey` field to associate the key to the log group +const logGroup = new logs.LogGroup(stack, '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', { + encryptionKey: kmsKey, +}); + +const cluster = new ecs.Cluster(stack, 'Cluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-command-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); +``` 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 fa33efd2fe0ab..d4023a4df6f9e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -8,7 +8,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ICluster, CapacityProviderStrategy } from '../cluster'; +import { ICluster, CapacityProviderStrategy, ExecuteCommandLogging } from '../cluster'; import { ContainerDefinition, Protocol } from '../container-definition'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; @@ -189,6 +189,13 @@ export interface BaseServiceOptions { * */ readonly capacityProviderStrategies?: CapacityProviderStrategy[]; + + /** + * Whether to enable the ability to execute into a container + * + * @default - undefined + */ + readonly enableExecuteCommand?: boolean; } /** @@ -391,6 +398,7 @@ export abstract class BaseService extends Resource type: DeploymentControllerType.ECS, } : props.deploymentController, launchType: launchType, + enableExecuteCommand: props.enableExecuteCommand, capacityProviderStrategy: props.capacityProviderStrategies, healthCheckGracePeriodSeconds: this.evaluateHealthGracePeriod(props.healthCheckGracePeriod), /* role: never specified, supplanted by Service Linked Role */ @@ -416,6 +424,18 @@ export abstract class BaseService extends Resource this.enableCloudMap(props.cloudMapOptions); } + if (props.enableExecuteCommand) { + this.enableExecuteCommand(); + + const logging = this.cluster.executeCommandConfiguration?.logging ?? ExecuteCommandLogging.DEFAULT; + + if (this.cluster.executeCommandConfiguration?.kmsKey) { + this.enableExecuteCommandEncryption(logging); + } + if (logging !== ExecuteCommandLogging.NONE) { + this.executeCommandLogConfiguration(); + } + } this.node.defaultChild = this.resource; } @@ -426,6 +446,84 @@ export abstract class BaseService extends Resource return this.cloudmapService; } + private executeCommandLogConfiguration() { + const logConfiguration = this.cluster.executeCommandConfiguration?.logConfiguration; + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:DescribeLogGroups', + ], + resources: ['*'], + })); + + const logGroupArn = logConfiguration?.cloudWatchLogGroup ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroup.logGroupName}:*` : '*'; + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + resources: [logGroupArn], + })); + + if (logConfiguration?.s3Bucket?.bucketName) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetBucketLocation', + ], + resources: ['*'], + })); + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:PutObject', + ], + resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}/*`], + })); + if (logConfiguration.s3EncryptionEnabled) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetEncryptionConfiguration', + ], + resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}`], + })); + } + } + } + + private enableExecuteCommandEncryption(logging: ExecuteCommandLogging) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + resources: [`${this.cluster.executeCommandConfiguration?.kmsKey?.keyArn}`], + })); + + this.cluster.executeCommandConfiguration?.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:*', + ], + resources: ['*'], + principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], + })); + + if (logging === ExecuteCommandLogging.DEFAULT || this.cluster.executeCommandConfiguration?.logConfiguration?.cloudWatchEncryptionEnabled) { + this.cluster.executeCommandConfiguration?.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + resources: ['*'], + principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], + conditions: { + ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, + }, + })); + } + } + /** * This method is called to attach this service to an Application Load Balancer. * @@ -790,6 +888,18 @@ export abstract class BaseService extends Resource produce: () => providedHealthCheckGracePeriod?.toSeconds() ?? (this.loadBalancers.length > 0 ? 60 : undefined), }); } + + private enableExecuteCommand() { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + resources: ['*'], + })); + } } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 294a88fcb1858..5b9da054a9fe2 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -3,6 +3,8 @@ 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 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 * as ssm from '@aws-cdk/aws-ssm'; import { Duration, Lazy, IResource, Resource, Stack, Aspects, IAspect, IConstruct } from '@aws-cdk/core'; @@ -69,6 +71,13 @@ export interface ClusterProps { * @default - Container Insights will be disabled for this cluser. */ readonly containerInsights?: boolean; + + /** + * The execute command configuration for the cluster + * + * @default - no configuration will be provided. + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -117,14 +126,9 @@ export class Cluster extends Resource implements ICluster { public readonly clusterName: string; /** - * The cluster-level (FARGATE, FARGATE_SPOT) capacity providers. - */ - private _fargateCapacityProviders: string[] = []; - - /** - * The EC2 Auto Scaling Group capacity providers associated with the cluster. + * The names of both ASG and Fargate capacity providers associated with the cluster. */ - private _asgCapacityProviders: AsgCapacityProvider[] = []; + private _capacityProviderNames: string[] = []; /** * The AWS Cloud Map namespace to associate with the cluster. @@ -141,6 +145,11 @@ export class Cluster extends Resource implements ICluster { */ private _autoscalingGroup?: autoscaling.IAutoScalingGroup; + /** + * The execute command configuration for the cluster + */ + private _executeCommandConfiguration?: ExecuteCommandConfiguration; + /** * Constructs a new instance of the Cluster class. */ @@ -159,15 +168,23 @@ export class Cluster extends Resource implements ICluster { clusterSettings = [{ name: 'containerInsights', value: props.containerInsights ? ContainerInsights.ENABLED : ContainerInsights.DISABLED }]; } - this._fargateCapacityProviders = props.capacityProviders ?? []; + this._capacityProviderNames = props.capacityProviders ?? []; if (props.enableFargateCapacityProviders) { this.enableFargateCapacityProviders(); } + if (props.executeCommandConfiguration) { + if ((props.executeCommandConfiguration.logging === ExecuteCommandLogging.OVERRIDE) !== + (props.executeCommandConfiguration.logConfiguration !== undefined)) { + throw new Error('Execute command log configuration must only be specified when logging is OVERRIDE.'); + } + this._executeCommandConfiguration = props.executeCommandConfiguration; + } + const cluster = new CfnCluster(this, 'Resource', { clusterName: this.physicalName, clusterSettings, - capacityProviders: Lazy.list({ produce: () => this._fargateCapacityProviders }, { omitEmpty: true }), + configuration: this._executeCommandConfiguration && this.renderExecuteCommandConfiguration(), }); this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, { @@ -193,7 +210,7 @@ export class Cluster extends Resource implements ICluster { // since it's harmless, but we'd prefer not to add unexpected new // resources to the stack which could surprise users working with // brown-field CDK apps and stacks. - Aspects.of(this).add(new MaybeCreateCapacityProviderAssociations(this, id, this._asgCapacityProviders)); + Aspects.of(this).add(new MaybeCreateCapacityProviderAssociations(this, id, this._capacityProviderNames)); } /** @@ -201,12 +218,39 @@ export class Cluster extends Resource implements ICluster { */ public enableFargateCapacityProviders() { for (const provider of ['FARGATE', 'FARGATE_SPOT']) { - if (!this._fargateCapacityProviders.includes(provider)) { - this._fargateCapacityProviders.push(provider); + if (!this._capacityProviderNames.includes(provider)) { + this._capacityProviderNames.push(provider); } } } + private renderExecuteCommandConfiguration() : CfnCluster.ClusterConfigurationProperty { + return { + executeCommandConfiguration: { + kmsKeyId: this._executeCommandConfiguration?.kmsKey?.keyArn, + logConfiguration: this._executeCommandConfiguration?.logConfiguration && this.renderExecuteCommandLogConfiguration(), + logging: this._executeCommandConfiguration?.logging, + }, + }; + } + + private renderExecuteCommandLogConfiguration(): CfnCluster.ExecuteCommandLogConfigurationProperty { + const logConfiguration = this._executeCommandConfiguration?.logConfiguration; + if (logConfiguration?.s3EncryptionEnabled && !logConfiguration?.s3Bucket) { + throw new Error('You must specify an S3 bucket name in the execute command log configuration to enable S3 encryption.'); + } + if (logConfiguration?.cloudWatchEncryptionEnabled && !logConfiguration?.cloudWatchLogGroup) { + throw new Error('You must specify a CloudWatch log group in the execute command log configuration to enable CloudWatch encryption.'); + } + return { + cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, + cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroup?.logGroupName, + s3BucketName: logConfiguration?.s3Bucket?.bucketName, + s3EncryptionEnabled: logConfiguration?.s3EncryptionEnabled, + s3KeyPrefix: logConfiguration?.s3KeyPrefix, + }; + } + /** * Add an AWS Cloud Map DNS namespace for this cluster. * NOTE: HttpNamespaces are not supported, as ECS always requires a DNSConfig when registering an instance to a Cloud @@ -279,7 +323,7 @@ export class Cluster extends Resource implements ICluster { */ public addAsgCapacityProvider(provider: AsgCapacityProvider, options: AddAutoScalingGroupCapacityOptions = {}) { // Don't add the same capacity provider more than once. - if (this._asgCapacityProviders.includes(provider)) { + if (this._capacityProviderNames.includes(provider.capacityProviderName)) { return; } @@ -290,7 +334,7 @@ export class Cluster extends Resource implements ICluster { taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime, }); - this._asgCapacityProviders.push(provider); + this._capacityProviderNames.push(provider.capacityProviderName); } /** @@ -412,8 +456,8 @@ export class Cluster extends Resource implements ICluster { throw new Error('CapacityProvider not supported'); } - if (!this._fargateCapacityProviders.includes(provider)) { - this._fargateCapacityProviders.push(provider); + if (!this._capacityProviderNames.includes(provider)) { + this._capacityProviderNames.push(provider); } } @@ -458,6 +502,13 @@ export class Cluster extends Resource implements ICluster { return this._hasEc2Capacity; } + /** + * Getter for execute command configuration associated with the cluster. + */ + public get executeCommandConfiguration(): ExecuteCommandConfiguration | undefined { + return this._executeCommandConfiguration; + } + /** * This method returns the CloudWatch metric for this clusters CPU reservation. * @@ -790,6 +841,11 @@ export interface ICluster extends IResource { * The autoscaling group added to the cluster if capacity is associated to the cluster */ readonly autoscalingGroup?: autoscaling.IAutoScalingGroup; + + /** + * The execute command configuration for the cluster + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -838,6 +894,13 @@ export interface ClusterAttributes { * @default - No default autoscaling group */ readonly autoscalingGroup?: autoscaling.IAutoScalingGroup; + + /** + * The execute command configuration for the cluster + * + * @default - none. + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -874,6 +937,11 @@ class ImportedCluster extends Resource implements ICluster { */ private _defaultCloudMapNamespace?: cloudmap.INamespace; + /** + * The execute command configuration for the cluster + */ + private _executeCommandConfiguration?: ExecuteCommandConfiguration; + /** * Constructs a new instance of the ImportedCluster class. */ @@ -883,6 +951,7 @@ class ImportedCluster extends Resource implements ICluster { this.vpc = props.vpc; this.hasEc2Capacity = props.hasEc2Capacity !== false; this._defaultCloudMapNamespace = props.defaultCloudMapNamespace; + this._executeCommandConfiguration = props.executeCommandConfiguration; this.clusterArn = props.clusterArn ?? Stack.of(this).formatArn({ service: 'ecs', @@ -898,6 +967,10 @@ class ImportedCluster extends Resource implements ICluster { public get defaultCloudMapNamespace(): cloudmap.INamespace | undefined { return this._defaultCloudMapNamespace; } + + public get executeCommandConfiguration(): ExecuteCommandConfiguration | undefined { + return this._executeCommandConfiguration; + } } /** @@ -1060,6 +1133,94 @@ capacity provider. The weight value is taken into consideration after the base v readonly weight?: number; } +/** + * The details of the execute command configuration. For more information, see + * [ExecuteCommandConfiguration] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html + */ +export interface ExecuteCommandConfiguration { + /** + * The AWS Key Management Service key ID to encrypt the data between the local client and the container. + * + * @default - none + */ + readonly kmsKey?: kms.IKey, + + /** + * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs or an Amazon S3 bucket. + * + * @default - none + */ + readonly logConfiguration?: ExecuteCommandLogConfiguration, + + /** + * The log settings to use for logging the execute command session. + * + * @default - none + */ + readonly logging?: ExecuteCommandLogging, +} + +/** + * The log settings to use to for logging the execute command session. For more information, see + * [Logging] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-logging + */ +export enum ExecuteCommandLogging { + /** + * The execute command session is not logged. + */ + NONE = 'NONE', + + /** + * The awslogs configuration in the task definition is used. If no logging parameter is specified, it defaults to this value. If no awslogs log driver is configured in the task definition, the output won't be logged. + */ + DEFAULT = 'DEFAULT', + + /** + * Specify the logging details as a part of logConfiguration. + */ + OVERRIDE = 'OVERRIDE', +} + +/** + * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs and/ or an Amazon S3 bucket. + * For more information, see [ExecuteCommandLogConfiguration] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html + */ +export interface ExecuteCommandLogConfiguration { + /** + * Whether or not to enable encryption on the CloudWatch logs. + * + * @default - encryption will be disabled. + */ + readonly cloudWatchEncryptionEnabled?: boolean, + + /** + * The name of the CloudWatch log group to send logs to. The CloudWatch log group must already be created. + * @default - none + */ + readonly cloudWatchLogGroup?: logs.ILogGroup, + + /** + * The name of the S3 bucket to send logs to. The S3 bucket must already be created. + * + * @default - none + */ + readonly s3Bucket?: s3.IBucket, + + /** + * Whether or not to enable encryption on the CloudWatch logs. + * + * @default - encryption will be disabled. + */ + readonly s3EncryptionEnabled?: boolean, + + /** + * An optional folder in the S3 bucket to place logs in. + * + * @default - none + */ + readonly s3KeyPrefix?: string +} + /** * The options for creating an Auto Scaling Group Capacity Provider. */ @@ -1173,22 +1334,24 @@ export class AsgCapacityProvider extends CoreConstruct { class MaybeCreateCapacityProviderAssociations implements IAspect { private scope: CoreConstruct; private id: string; - private capacityProviders: AsgCapacityProvider[] + private capacityProviders: string[] + private resource?: CfnClusterCapacityProviderAssociations - constructor(scope: CoreConstruct, id: string, capacityProviders: AsgCapacityProvider[]) { + constructor(scope: CoreConstruct, id: string, capacityProviders: string[] ) { this.scope = scope; this.id = id; this.capacityProviders = capacityProviders; } + public visit(node: IConstruct): void { if (node instanceof Cluster) { - const providers = this.capacityProviders.map(p => p.capacityProviderName).filter(p => p !== 'FARGATE' && p !== 'FARGATE_SPOT'); - if (providers.length > 0) { - new CfnClusterCapacityProviderAssociations(this.scope, this.id, { + if (this.capacityProviders.length > 0 && !this.resource) { + const resource = new CfnClusterCapacityProviderAssociations(this.scope, this.id, { cluster: node.clusterName, defaultCapacityProviderStrategy: [], - capacityProviders: Lazy.list({ produce: () => providers }), + capacityProviders: Lazy.list({ produce: () => this.capacityProviders }), }); + this.resource = resource; } } } diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index a76c98377c1db..cd144f87bfb12 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -9,6 +9,8 @@ import { import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; 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 * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -1707,6 +1709,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE_SPOT'], })); @@ -1725,6 +1731,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE', 'FARGATE_SPOT'], })); @@ -1742,6 +1752,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE', 'FARGATE_SPOT'], })); @@ -1760,6 +1774,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE'], })); @@ -1778,6 +1796,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE'], })); @@ -1926,7 +1948,6 @@ nodeunitShim({ enableManagedTerminationProtection: false, }); - // These should not be added at the association level cluster.enableFargateCapacityProviders(); // Ensure not added twice @@ -1939,6 +1960,8 @@ nodeunitShim({ Ref: 'EcsCluster97242B84', }, CapacityProviders: [ + 'FARGATE', + 'FARGATE_SPOT', { Ref: 'providerD3FF4D3A', }, @@ -1947,4 +1970,142 @@ nodeunitShim({ })); test.done(); }, + + 'correctly sets log configuration for execute command'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + kmsKey: kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Cluster', { + Configuration: { + ExecuteCommandConfiguration: { + KmsKeyId: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + LogConfiguration: { + CloudWatchEncryptionEnabled: true, + CloudWatchLogGroupName: { + Ref: 'LogGroupF5B46931', + }, + S3BucketName: { + Ref: 'EcsExecBucket4F468651', + }, + S3EncryptionEnabled: true, + S3KeyPrefix: 'exec-output', + }, + Logging: 'OVERRIDE', + }, + }, + })); + + test.done(); + }, + + 'throws when no log configuration is provided when logging is set to OVERRIDE'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /Execute command log configuration must only be specified when logging is OVERRIDE./); + + test.done(); + }, + + 'throws when log configuration provided but logging is set to DEFAULT'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroup: logGroup, + }, + logging: ecs.ExecuteCommandLogging.DEFAULT, + }, + }); + }, /Execute command log configuration must only be specified when logging is OVERRIDE./); + + test.done(); + }, + + 'throws when CloudWatchEncryptionEnabled without providing CloudWatch Logs log group name'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + cloudWatchEncryptionEnabled: true, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /You must specify a CloudWatch log group in the execute command log configuration to enable CloudWatch encryption./); + + test.done(); + }, + + 'throws when S3EncryptionEnabled without providing S3 Bucket name'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + s3EncryptionEnabled: true, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /You must specify an S3 bucket name in the execute command log configuration to enable S3 encryption./); + + test.done(); + }, }); 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 036604d079c71..f53f66d8755ad 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 @@ -3,6 +3,9 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elb from '@aws-cdk/aws-elasticloadbalancing'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +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 * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -52,6 +55,726 @@ nodeunitShim({ test.done(); }, + 'allows setting enable execute command'(test: Test) { + // 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') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Service', { + TaskDefinition: { + Ref: 'Ec2TaskDef0226F28C', + }, + Cluster: { + Ref: 'EcsCluster97242B84', + }, + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 50, + }, + LaunchType: LaunchType.EC2, + SchedulingStrategy: 'REPLICA', + EnableECSManagedTags: false, + EnableExecuteCommand: true, + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + test.done(); + }, + + 'no logging enabled when logging field is set to NONE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.NONE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + logging: ecs.LogDrivers.awsLogs({ + logGroup, + streamPrefix: 'log-group', + }), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + test.done(); + }, + + 'enables execute command logging when logging field is set to OVERRIDE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'ExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'ExecBucket29559356', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + test.done(); + }, + + 'enables only execute command session encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + + 'enables encryption for execute command logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: 's3:GetEncryptionConfiguration', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + Condition: { + ArnLike: { + 'kms:EncryptionContext:aws:logs:arn': { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':*', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: { + 'Fn::Join': [ + '', + [ + 'logs.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + 'with custom cloudmap namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.expected.json index d4e008750fe93..555d9acc46764 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.expected.json @@ -362,6 +362,8 @@ "Type": "AWS::ECS::ClusterCapacityProviderAssociations", "Properties": { "CapacityProviders": [ + "FARGATE", + "FARGATE_SPOT", { "Ref": "EC2CapacityProvider5A2E35CD" } @@ -582,7 +584,6 @@ "LaunchConfigurationName": { "Ref": "ASGLaunchConfigC00AF12B" }, - "NewInstancesProtectedFromScaleIn": true, "Tags": [ { "Key": "Name", @@ -605,6 +606,292 @@ } } }, + "ASGDrainECSHookFunctionServiceRoleC12963BB": { + "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" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-ec2-capacity-provider/ASG" + } + ] + } + }, + "ASGDrainECSHookFunctionServiceRoleDefaultPolicy16848A27": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:CompleteLifecycleAction", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":autoscaling:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":autoScalingGroup:*:autoScalingGroupName/", + { + "Ref": "ASG46ED3070" + } + ] + ] + } + }, + { + "Action": [ + "ecs:DescribeContainerInstances", + "ecs:DescribeTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "EC2CPClusterD5F0FD32", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EC2CPClusterD5F0FD32", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "EC2CPClusterD5F0FD32", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ASGDrainECSHookFunctionServiceRoleDefaultPolicy16848A27", + "Roles": [ + { + "Ref": "ASGDrainECSHookFunctionServiceRoleC12963BB" + } + ] + } + }, + "ASGDrainECSHookFunction5F24CF4D": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n \n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n \n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n \n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Role": { + "Fn::GetAtt": [ + "ASGDrainECSHookFunctionServiceRoleC12963BB", + "Arn" + ] + }, + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "EC2CPClusterD5F0FD32" + } + } + }, + "Handler": "index.lambda_handler", + "Runtime": "python3.6", + "Tags": [ + { + "Key": "Name", + "Value": "integ-ec2-capacity-provider/ASG" + } + ], + "Timeout": 310 + }, + "DependsOn": [ + "ASGDrainECSHookFunctionServiceRoleDefaultPolicy16848A27", + "ASGDrainECSHookFunctionServiceRoleC12963BB" + ] + }, + "ASGDrainECSHookFunctionAllowInvokeintegec2capacityproviderASGLifecycleHookDrainHookTopic4714B3C1EB63E78F": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "ASGDrainECSHookFunction5F24CF4D", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB" + } + } + }, + "ASGDrainECSHookFunctionTopicD6FC59F7": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB" + }, + "Endpoint": { + "Fn::GetAtt": [ + "ASGDrainECSHookFunction5F24CF4D", + "Arn" + ] + } + } + }, + "ASGLifecycleHookDrainHookRoleD640316C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-ec2-capacity-provider/ASG" + } + ] + } + }, + "ASGLifecycleHookDrainHookRoleDefaultPolicy3EEFDE57": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ASGLifecycleHookDrainHookRoleDefaultPolicy3EEFDE57", + "Roles": [ + { + "Ref": "ASGLifecycleHookDrainHookRoleD640316C" + } + ] + } + }, + "ASGLifecycleHookDrainHookTopicA8AD4ACB": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-ec2-capacity-provider/ASG" + } + ] + } + }, + "ASGLifecycleHookDrainHookFE4AFEBE": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "ASG46ED3070" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB" + }, + "RoleARN": { + "Fn::GetAtt": [ + "ASGLifecycleHookDrainHookRoleD640316C", + "Arn" + ] + } + }, + "DependsOn": [ + "ASGLifecycleHookDrainHookRoleDefaultPolicy3EEFDE57", + "ASGLifecycleHookDrainHookRoleD640316C" + ] + }, "EC2CapacityProvider5A2E35CD": { "Type": "AWS::ECS::CapacityProvider", "Properties": { @@ -616,7 +903,7 @@ "Status": "ENABLED", "TargetCapacity": 100 }, - "ManagedTerminationProtection": "ENABLED" + "ManagedTerminationProtection": "DISABLED" } } }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.ts index f82ce6a9f9f56..3cc93ebaf2eb2 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.capacity-provider.ts @@ -10,6 +10,7 @@ const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); const cluster = new ecs.Cluster(stack, 'EC2CPCluster', { vpc, + enableFargateCapacityProviders: true, }); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); @@ -27,6 +28,8 @@ const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'ASG', { const cp = new ecs.AsgCapacityProvider(stack, 'EC2CapacityProvider', { autoScalingGroup, + // This is to allow cdk destroy to work; otherwise deletion will hang bc ASG cannot be deleted + enableManagedTerminationProtection: false, }); cluster.addAsgCapacityProvider(cp); @@ -43,4 +46,3 @@ new ecs.Ec2Service(stack, 'EC2Service', { }); app.synth(); - diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json new file mode 100644 index 0000000000000..3f032ba6cc07e --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json @@ -0,0 +1,1173 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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.192.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": "731", + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "KmsKey46693ADD": { + "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": "*" + }, + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Condition": { + "ArnLike": { + "kms:EncryptionContext:aws:logs:arn": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "logs.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "EcsExecBucket4F468651": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + } + }, + "Ec2ClusterEE43E89D": { + "Type": "AWS::ECS::Cluster", + "Properties": { + "Configuration": { + "ExecuteCommandConfiguration": { + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "LogConfiguration": { + "CloudWatchEncryptionEnabled": true, + "CloudWatchLogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "S3BucketName": { + "Ref": "EcsExecBucket4F468651" + }, + "S3EncryptionEnabled": true, + "S3KeyPrefix": "exec-output" + }, + "Logging": "OVERRIDE" + } + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:DeregisterContainerInstance", + "ecs:RegisterContainerInstance", + "ecs:Submit*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:Poll", + "ecs:StartTelemetrySession" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:DiscoverPollEndpoint", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "Ec2ClusterEE43E89D" + }, + " >> /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" + ] + ] + } + } + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD", + "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + ] + }, + "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "LaunchConfigurationName": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3": { + "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" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:CompleteLifecycleAction", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":autoscaling:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":autoScalingGroup:*:autoScalingGroupName/", + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0" + } + ] + ] + } + }, + { + "Action": [ + "ecs:DescribeContainerInstances", + "ecs:DescribeTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n \n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n \n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n \n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Role": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3", + "Arn" + ] + }, + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "Ec2ClusterEE43E89D" + } + } + }, + "Handler": "index.lambda_handler", + "Runtime": "python3.6", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "Timeout": 310 + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33", + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3" + ] + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionAllowInvokeawsecsintegexeccommandEc2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopic05F8C92983E1AD32": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic4795E0F6": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31", + "Arn" + ] + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + }, + "RoleARN": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7", + "Arn" + ] + } + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B", + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7" + ] + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefTaskRoleDefaultPolicyA592CB18": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "LogGroupF5B46931" + }, + ":*" + ] + ] + } + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:GetEncryptionConfiguration", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefTaskRoleDefaultPolicyA592CB18", + "Roles": [{ + "Ref": "TaskDefTaskRole1EDB4A67" + }] + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Memory": "256", + "Name": "web" + } + ], + "Family": "awsecsintegexeccommandTaskDef44709274", + "NetworkMode": "bridge", + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "Ec2Service04A33183": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "LaunchType": "EC2", + "EnableECSManagedTags": false, + "EnableExecuteCommand": true, + "SchedulingStrategy": "REPLICA", + "TaskDefinition": { + "Ref": "TaskDef54694570" + } + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts new file mode 100644 index 0000000000000..ce48860fc4e6b --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts @@ -0,0 +1,54 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +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 cdk from '@aws-cdk/core'; +import * as ecs from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-exec-command'); + +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); + +const kmsKey = new kms.Key(stack, 'KmsKey'); + +const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, +}); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, +}); + +const cluster = new ecs.Cluster(stack, 'Ec2Cluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), +}); + +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + +taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 256, +}); + +new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); + +app.synth(); \ 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 af4b92370726d..f1fdbedc01064 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 @@ -3,6 +3,9 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +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 * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; @@ -145,6 +148,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE', 'FARGATE_SPOT'], })); @@ -231,6 +238,10 @@ nodeunitShim({ // THEN expect(stack).to(haveResource('AWS::ECS::Cluster', { + CapacityProviders: ABSENT, + })); + + expect(stack).to(haveResource('AWS::ECS::ClusterCapacityProviderAssociations', { CapacityProviders: ['FARGATE', 'FARGATE_SPOT'], })); @@ -2199,5 +2210,743 @@ nodeunitShim({ test.done(); }, + + 'allows setting enable execute command'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Service', { + TaskDefinition: { + Ref: 'FargateTaskDefC6FB60B4', + }, + Cluster: { + Ref: 'EcsCluster97242B84', + }, + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 50, + }, + LaunchType: LaunchType.FARGATE, + EnableECSManagedTags: false, + EnableExecuteCommand: true, + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'FargateServiceSecurityGroup0A0E79CB', + 'GroupId', + ], + }, + ], + Subnets: [ + { + Ref: 'MyVpcPrivateSubnet1Subnet5057CF7E', + }, + { + Ref: 'MyVpcPrivateSubnet2Subnet0040C983', + }, + ], + }, + }, + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'no logging enabled when logging field is set to NONE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.NONE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + logging: ecs.LogDrivers.awsLogs({ + logGroup, + streamPrefix: 'log-group', + }), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'enables execute command logging with logging field set to OVERRIDE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'ExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'ExecBucket29559356', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'enables only execute command session encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + + 'enables encryption for execute command logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: 's3:GetEncryptionConfiguration', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + Condition: { + ArnLike: { + 'kms:EncryptionContext:aws:logs:arn': { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':*', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: { + 'Fn::Join': [ + '', + [ + 'logs.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, }, }); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.capacity-providers.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.capacity-providers.expected.json index c0281dc8e14ae..7eb0eada4f244 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.capacity-providers.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.capacity-providers.expected.json @@ -356,12 +356,19 @@ } }, "FargateCPCluster668E71F2": { - "Type": "AWS::ECS::Cluster", + "Type": "AWS::ECS::Cluster" + }, + "FargateCPClusterBFD66A36": { + "Type": "AWS::ECS::ClusterCapacityProviderAssociations", "Properties": { "CapacityProviders": [ "FARGATE", "FARGATE_SPOT" - ] + ], + "Cluster": { + "Ref": "FargateCPCluster668E71F2" + }, + "DefaultCapacityProviderStrategy": [] } }, "TaskDefTaskRole1EDB4A67": { diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json new file mode 100644 index 0000000000000..083a16ee75820 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json @@ -0,0 +1,724 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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.192.0/18", + "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": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/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": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": "731", + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "KmsKey46693ADD": { + "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": "*" + }, + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Condition": { + "ArnLike": { + "kms:EncryptionContext:aws:logs:arn": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "logs.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "EcsExecBucket4F468651": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + } + }, + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster", + "Properties": { + "Configuration": { + "ExecuteCommandConfiguration": { + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "LogConfiguration": { + "CloudWatchEncryptionEnabled": true, + "CloudWatchLogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "S3BucketName": { + "Ref": "EcsExecBucket4F468651" + }, + "S3EncryptionEnabled": true, + "S3KeyPrefix": "exec-output" + }, + "Logging": "OVERRIDE" + } + } + } + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefTaskRoleDefaultPolicyA592CB18": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "LogGroupF5B46931" + }, + ":*" + ] + ] + } + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:GetEncryptionConfiguration", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefTaskRoleDefaultPolicyA592CB18", + "Roles": [{ + "Ref": "TaskDefTaskRole1EDB4A67" + }] + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Name": "web" + } + ], + "Cpu": "256", + "Family": "awsecsintegexeccommandTaskDef44709274", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "FargateServiceAC2B3B85": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "LaunchType": "FARGATE", + "EnableECSManagedTags": false, + "EnableExecuteCommand": true, + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "TaskDefinition": { + "Ref": "TaskDef54694570" + } + } + }, + "FargateServiceSecurityGroup0A0E79CB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-exec-command/FargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts new file mode 100644 index 0000000000000..5bc89fb2432b7 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts @@ -0,0 +1,50 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +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 cdk from '@aws-cdk/core'; +import * as ecs from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-exec-command'); + +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); + +const kmsKey = new kms.Key(stack, 'KmsKey'); + +const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, +}); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroup: logGroup, + cloudWatchEncryptionEnabled: true, + s3Bucket: execBucket, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); + +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); + +taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), +}); + +new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index c91c1630858c4..3b5f98cb1c6e4 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -70,7 +70,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/sinon": "^9.0.11", "@types/nodeunit": "^0.0.31", "@types/yaml": "1.9.6", diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index d60d91e0a9a51..e4029c9dc55c6 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -71,6 +71,17 @@ export interface LoadBalancerProps { * @default - Public subnets if internetFacing, Private subnets otherwise */ readonly subnetSelection?: SubnetSelection; + + /** + * Enable Loadbalancer access logs + * Can be used to avoid manual work as aws console + * Required S3 bucket name , enabled flag + * Can add interval for pushing log + * Can set bucket prefix in order to provide folder name inside bucket + * @default - disabled + */ + readonly accessLoggingPolicy?: CfnLoadBalancer.AccessLoggingPolicyProperty; + } /** @@ -262,6 +273,10 @@ export class LoadBalancer extends Resource implements IConnectable { this.elb.node.addDependency(selectedSubnets.internetConnectivityEstablished); } + if (props.accessLoggingPolicy !== undefined) { + this.elb.accessLoggingPolicy = props.accessLoggingPolicy; + } + ifUndefined(props.listeners, []).forEach(b => this.addListener(b)); ifUndefined(props.targets, []).forEach(t => this.addTarget(t)); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index 9cac87e057e87..aa6c1c8b88ad0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -249,6 +249,52 @@ describe('tests', () => { sslCertificateId: sslCertificateArn, })).toThrow(/"sslCertificateId" is deprecated, please use "sslCertificateArn" only./); }); + + test('enable load balancer access logs', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VCP'); + + // WHEN + new LoadBalancer(stack, 'LB', { + vpc, + accessLoggingPolicy: { + enabled: true, + s3BucketName: 'fakeBucket', + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + AccessLoggingPolicy: { + Enabled: true, + S3BucketName: 'fakeBucket', + }, + }); + }); + + test('disable load balancer access logs', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VCP'); + + // WHEN + new LoadBalancer(stack, 'LB', { + vpc, + accessLoggingPolicy: { + enabled: false, + s3BucketName: 'fakeBucket', + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + AccessLoggingPolicy: { + Enabled: false, + S3BucketName: 'fakeBucket', + }, + }); + }); }); class FakeTarget implements ILoadBalancerTarget { diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts index 90f8059afebfb..c5b524b3003d4 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { Annotations } from '@aws-cdk/core'; import { metadata } from './sdk-api-metadata.generated'; import { addLambdaPermission } from './util'; @@ -89,6 +90,8 @@ export class AwsApi implements events.IRuleTarget { lambdaPurpose: 'AWS', }); + checkServiceExists(this.props.service, handler); + if (this.props.policyStatement) { handler.addToRolePolicy(this.props.policyStatement); } else { @@ -117,6 +120,18 @@ export class AwsApi implements events.IRuleTarget { } } +/** + * Check if the given service exists in the AWS SDK. If not, a warning will be raised. + * @param service Service name + */ +function checkServiceExists(service: string, handler: lambda.SingletonFunction) { + const sdkService = awsSdkMetadata[service.toLowerCase()]; + if (!sdkService) { + Annotations.of(handler).addWarning(`Service ${service} does not exist in the AWS SDK. Check the list of available \ +services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html`); + } +} + /** * Transform SDK service/action to IAM action using metadata from aws-sdk module. */ diff --git a/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api.test.ts b/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api.test.ts index e43c9708574fc..c605dfa64e992 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/aws-api.test.ts @@ -1,4 +1,4 @@ -import { countResources, expect, haveResource } from '@aws-cdk/assert-internal'; +import { countResources, expect as cdkExpect, haveResource, SynthUtils } from '@aws-cdk/assert-internal'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; @@ -32,7 +32,7 @@ test('use AwsApi as an event rule target', () => { })); // THEN - expect(stack).to(haveResource('AWS::Events::Rule', { + cdkExpect(stack).to(haveResource('AWS::Events::Rule', { Targets: [ { Arn: { @@ -73,9 +73,9 @@ test('use AwsApi as an event rule target', () => { })); // Uses a singleton function - expect(stack).to(countResources('AWS::Lambda::Function', 1)); + cdkExpect(stack).to(countResources('AWS::Lambda::Function', 1)); - expect(stack).to(haveResource('AWS::IAM::Policy', { + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -112,7 +112,7 @@ test('with policy statement', () => { })); // THEN - expect(stack).to(haveResource('AWS::Events::Rule', { + cdkExpect(stack).to(haveResource('AWS::Events::Rule', { Targets: [ { Arn: { @@ -130,7 +130,7 @@ test('with policy statement', () => { ], })); - expect(stack).to(haveResource('AWS::IAM::Policy', { + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -143,3 +143,29 @@ test('with policy statement', () => { }, })); }); + +test('with service not in AWS SDK', () => { + // GIVEN + const stack = new Stack(); + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(15 minutes)'), + }); + const awsApi = new targets.AwsApi({ + service: 'no-such-service', + action: 'no-such-action', + policyStatement: new iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: ['resource'], + }), + }); + + // WHEN + rule.addTarget(awsApi); + + // THEN + const assembly = SynthUtils.synthesize(stack); + expect(assembly.messages.length).toBe(1); + const message = assembly.messages[0]; + expect(message.entry.type).toBe('aws:cdk:warning'); + expect(message.entry.data).toBe('Service no-such-service does not exist in the AWS SDK. Check the list of available services and actions from https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html'); +}); diff --git a/packages/@aws-cdk/aws-events/README.md b/packages/@aws-cdk/aws-events/README.md index 4bf4c2b390dbe..f659c2aa48d66 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -80,6 +80,18 @@ onCommitRule.addTarget(new targets.SnsTopic(topic, { })); ``` +Or using an Object: + +```ts +onCommitRule.addTarget(new targets.SnsTopic(topic, { + message: events.RuleTargetInput.fromObject( + { + DataType: `custom_${events.EventField.fromPath('$.detail-type')}` + } + ) +})); +``` + ## Scheduling You can configure a Rule to run on a schedule (cron or rate). diff --git a/packages/@aws-cdk/aws-events/lib/input.ts b/packages/@aws-cdk/aws-events/lib/input.ts index 1fd68a1754119..77798ceebd3a1 100644 --- a/packages/@aws-cdk/aws-events/lib/input.ts +++ b/packages/@aws-cdk/aws-events/lib/input.ts @@ -151,8 +151,6 @@ class FieldAwareEventInput extends RuleTargetInput { return key; } - const self = this; - class EventFieldReplacer extends DefaultTokenResolver { constructor() { super(new StringConcat()); @@ -167,7 +165,7 @@ class FieldAwareEventInput extends RuleTargetInput { } inputPathsMap[key] = t.path; - return self.keyPlaceholder(key); + return `<${key}>`; } } @@ -188,35 +186,32 @@ class FieldAwareEventInput extends RuleTargetInput { })); } - if (Object.keys(inputPathsMap).length === 0) { + const keys = Object.keys(inputPathsMap); + + if (keys.length === 0) { // Nothing special, just return 'input' return { input: resolved }; } return { - inputTemplate: this.unquoteKeyPlaceholders(resolved), + inputTemplate: this.unquoteKeyPlaceholders(resolved, keys), inputPathsMap, }; } - /** - * Return a template placeholder for the given key - * - * In object scope we'll need to get rid of surrounding quotes later on, so - * return a bracing that's unlikely to occur naturally (like tokens). - */ - private keyPlaceholder(key: string) { - if (this.inputType !== InputType.Object) { return `<${key}>`; } - return UNLIKELY_OPENING_STRING + key + UNLIKELY_CLOSING_STRING; - } - /** * Removing surrounding quotes from any object placeholders + * when key is the lone value. * * Those have been put there by JSON.stringify(), but we need to * remove them. + * + * Do not remove quotes when the key is part of a larger string. + * + * Valid: { "data": "Some string with \"quotes\"" } // key will be string + * Valid: { "data": } // Key could be number, bool, obj, or string */ - private unquoteKeyPlaceholders(sub: string) { + private unquoteKeyPlaceholders(sub: string, keys: string[]) { if (this.inputType !== InputType.Object) { return sub; } return Lazy.uncachedString({ produce: (ctx: IResolveContext) => Token.asString(deepUnquote(ctx.resolve(sub))) }); @@ -230,19 +225,13 @@ class FieldAwareEventInput extends RuleTargetInput { } return resolved; } else if (typeof(resolved) === 'string') { - return resolved.replace(OPENING_STRING_REGEX, '<').replace(CLOSING_STRING_REGEX, '>'); + return keys.reduce((r, key) => r.replace(new RegExp(`(?\"`, 'g'), `<${key}>`), resolved); } return resolved; } } } -const UNLIKELY_OPENING_STRING = '<<${'; -const UNLIKELY_CLOSING_STRING = '}>>'; - -const OPENING_STRING_REGEX = new RegExp(regexQuote('"' + UNLIKELY_OPENING_STRING), 'g'); -const CLOSING_STRING_REGEX = new RegExp(regexQuote(UNLIKELY_CLOSING_STRING + '"'), 'g'); - /** * Represents a field in the event pattern */ @@ -339,10 +328,3 @@ function isEventField(x: any): x is EventField { } const EVENT_FIELD_SYMBOL = Symbol.for('@aws-cdk/aws-events.EventField'); - -/** - * Quote a string for use in a regex - */ -function regexQuote(s: string) { - return s.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); -} diff --git a/packages/@aws-cdk/aws-events/test/test.input.ts b/packages/@aws-cdk/aws-events/test/test.input.ts index bbcc80d4afc66..43c763ef35f74 100644 --- a/packages/@aws-cdk/aws-events/test/test.input.ts +++ b/packages/@aws-cdk/aws-events/test/test.input.ts @@ -67,6 +67,162 @@ export = { test.done(); }, + 'can use joined JSON containing refs in JSON object with tricky inputs'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new Rule(stack, 'Rule', { + schedule: Schedule.rate(cdk.Duration.minutes(1)), + }); + + // WHEN + rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ + data: `they said \"hello\"${EventField.fromPath('$')}`, + stackName: cdk.Aws.STACK_NAME, + }))); + + // THEN + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + InputTransformer: { + InputPathsMap: { + f1: '$', + }, + InputTemplate: { + 'Fn::Join': [ + '', + [ + '{"data":"they said \\\"hello\\\"","stackName":"', + { Ref: 'AWS::StackName' }, + '"}', + ], + ], + }, + }, + }, + ], + })); + + test.done(); + }, + + 'can use joined JSON containing refs in JSON object and concat'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new Rule(stack, 'Rule', { + schedule: Schedule.rate(cdk.Duration.minutes(1)), + }); + + // WHEN + rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ + data: `more text ${EventField.fromPath('$')}`, + stackName: cdk.Aws.STACK_NAME, + }))); + + // THEN + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + InputTransformer: { + InputPathsMap: { + f1: '$', + }, + InputTemplate: { + 'Fn::Join': [ + '', + [ + '{"data":"more text ","stackName":"', + { Ref: 'AWS::StackName' }, + '"}', + ], + ], + }, + }, + }, + ], + })); + + test.done(); + }, + + 'can use joined JSON containing refs in JSON object and quotes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new Rule(stack, 'Rule', { + schedule: Schedule.rate(cdk.Duration.minutes(1)), + }); + + // WHEN + rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ + data: `more text "${EventField.fromPath('$')}"`, + stackName: cdk.Aws.STACK_NAME, + }))); + + // THEN + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + InputTransformer: { + InputPathsMap: { + f1: '$', + }, + InputTemplate: { + 'Fn::Join': [ + '', + [ + '{"data":"more text \\\"\\\"","stackName":"', + { Ref: 'AWS::StackName' }, + '"}', + ], + ], + }, + }, + }, + ], + })); + + test.done(); + }, + + 'can use joined JSON containing refs in JSON object and multiple keys'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new Rule(stack, 'Rule', { + schedule: Schedule.rate(cdk.Duration.minutes(1)), + }); + + // WHEN + rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ + data: `${EventField.fromPath('$')}${EventField.fromPath('$.other')}`, + stackName: cdk.Aws.STACK_NAME, + }))); + + // THEN + expect(stack).to(haveResourceLike('AWS::Events::Rule', { + Targets: [ + { + InputTransformer: { + InputPathsMap: { + f1: '$', + }, + InputTemplate: { + 'Fn::Join': [ + '', + [ + '{"data":"","stackName":"', + { Ref: 'AWS::StackName' }, + '"}', + ], + ], + }, + }, + }, + ], + })); + + test.done(); + }, + 'can use token'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index 8b359251f54d2..673b5748537c5 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -67,7 +67,7 @@ export class Database extends Resource implements IDatabase { public readonly catalogArn: string; /** - * ID of the Glue catalog in which this database is stored. + * The catalog id of the database (usually, the AWS account id). */ public readonly catalogId: string; diff --git a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts index 8777e40d5d7f7..62257140e9c30 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts @@ -30,7 +30,7 @@ export class PolicyStatement { * @param obj the PolicyStatement in object form. */ public static fromJson(obj: any) { - return new PolicyStatement({ + const ret = new PolicyStatement({ sid: obj.Sid, actions: ensureArrayOrUndefined(obj.Action), resources: ensureArrayOrUndefined(obj.Resource), @@ -41,6 +41,14 @@ export class PolicyStatement { principals: obj.Principal ? [new JsonPrincipal(obj.Principal)] : undefined, notPrincipals: obj.NotPrincipal ? [new JsonPrincipal(obj.NotPrincipal)] : undefined, }); + + // validate that the PolicyStatement has the correct shape + const errors = ret.validateForAnyPolicy(); + if (errors.length > 0) { + throw new Error('Incorrect Policy Statement: ' + errors.join('\n')); + } + + return ret; } /** diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index e681fbdce8bff..1a2abdc96aff0 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -78,7 +78,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/jest": "^26.0.23", "@types/sinon": "^9.0.11", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index bea78532bbf90..03097d9d49073 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { FeatureFlags, IResource, RemovalPolicy, Resource, Stack, Duration } from '@aws-cdk/core'; +import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Construct } from 'constructs'; import { Alias } from './alias'; @@ -485,6 +485,55 @@ export class Key extends KeyBase { return new Import(keyResourceName); } + /** + * Create a mutable {@link IKey} based on a low-level {@link CfnKey}. + * This is most useful when combined with the cloudformation-include module. + * This method is different than {@link fromKeyArn()} because the {@link IKey} + * returned from this method is mutable; + * meaning, calling any mutating methods on it, + * like {@link IKey.addToResourcePolicy()}, + * will actually be reflected in the resulting template, + * as opposed to the object returned from {@link fromKeyArn()}, + * on which calling those methods would have no effect. + */ + public static fromCfnKey(cfnKey: CfnKey): IKey { + // use a "weird" id that has a higher chance of being unique + const id = '@FromCfnKey'; + + // if fromCfnKey() was already called on this cfnKey, + // return the same L2 + // (as different L2s would conflict, because of the mutation of the keyPolicy property of the L1 below) + const existing = cfnKey.node.tryFindChild(id); + if (existing) { + return existing; + } + + let keyPolicy: iam.PolicyDocument; + try { + keyPolicy = iam.PolicyDocument.fromJson(cfnKey.keyPolicy); + } catch (e) { + // If the KeyPolicy contains any CloudFormation functions, + // PolicyDocument.fromJson() throws an exception. + // In that case, because we would have to effectively make the returned IKey immutable, + // throw an exception suggesting to use the other importing methods instead. + // We might make this parsing logic smarter later, + // but let's start by erroring out. + throw new Error('Could not parse the PolicyDocument of the passed AWS::KMS::Key resource because it contains CloudFormation functions. ' + + 'This makes it impossible to create a mutable IKey from that Policy. ' + + 'You have to use fromKeyArn instead, passing it the ARN attribute property of the low-level CfnKey'); + } + + // change the key policy of the L1, so that all changes done in the L2 are reflected in the resulting template + cfnKey.keyPolicy = Lazy.any({ produce: () => keyPolicy.toJSON() }); + + return new class extends KeyBase { + public readonly keyArn = cfnKey.attrArn; + public readonly keyId = cfnKey.ref; + protected readonly policy = keyPolicy; + protected readonly trustAccountIdentities = false; + }(cfnKey, id); + } + public readonly keyArn: string; public readonly keyId: string; protected readonly policy?: iam.PolicyDocument; diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index 0bdb6c755d7bb..f80dce397b4b1 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -1,4 +1,4 @@ -import { arrayWith, ResourcePart } from '@aws-cdk/assert-internal'; +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 * as cdk from '@aws-cdk/core'; @@ -582,6 +582,323 @@ describe('imported keys', () => { }); }); +describe('fromCfnKey()', () => { + let stack: cdk.Stack; + let cfnKey: kms.CfnKey; + let key: kms.IKey; + + beforeEach(() => { + stack = new cdk.Stack(); + cfnKey = new kms.CfnKey(stack, 'CfnKey', { + keyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: cdk.Fn.join('', [ + 'arn:', + cdk.Aws.PARTITION, + ':iam::', + cdk.Aws.ACCOUNT_ID, + ':root', + ]), + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); + key = kms.Key.fromCfnKey(cfnKey); + }); + + test("correctly resolves the 'keyId' property", () => { + expect(stack.resolve(key.keyId)).toStrictEqual({ + Ref: 'CfnKey', + }); + }); + + test("correctly resolves the 'keyArn' property", () => { + expect(stack.resolve(key.keyArn)).toStrictEqual({ + 'Fn::GetAtt': ['CfnKey', 'Arn'], + }); + }); + + test('preserves the KMS Key resource', () => { + expectCdk(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::', + { Ref: 'AWS::AccountId' }, + ':root', + ]], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + expectCdk(stack).to(countResources('AWS::KMS::Key', 1)); + }); + + describe("calling 'addToResourcePolicy()' on the returned Key", () => { + let addToResourcePolicyResult: iam.AddToResourcePolicyResult; + + beforeEach(() => { + addToResourcePolicyResult = key.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['kms:action'], + resources: ['*'], + principals: [new iam.AnyPrincipal()], + })); + }); + + test("the AddToResourcePolicyResult returned has 'statementAdded' set to 'true'", () => { + expect(addToResourcePolicyResult.statementAdded).toBeTruthy(); + }); + + test('preserves the mutating call in the resulting template', () => { + expectCdk(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::', + { Ref: 'AWS::AccountId' }, + ':root', + ]], + }, + }, + Resource: '*', + }, + { + Action: 'kms:action', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + }); + }); + + describe('calling fromCfnKey() again', () => { + beforeEach(() => { + key = kms.Key.fromCfnKey(cfnKey); + }); + + describe('and using it for grantDecrypt() on a Role', function () { + beforeEach(() => { + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.AnyPrincipal(), + }); + key.grantDecrypt(role); + }); + + test('creates the correct IAM Policy', () => { + expectCdk(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'kms:Decrypt', + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': ['CfnKey', 'Arn'], + }, + }, + ], + }, + })); + }); + + test('correctly mutates the Policy of the underlying CfnKey', () => { + expectCdk(stack).to(haveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::', + { Ref: 'AWS::AccountId' }, + ':root', + ]], + }, + }, + Resource: '*', + }, + { + Action: 'kms:Decrypt', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + }); + }); + }); + + describe("called with a CfnKey that has an 'Fn::If' passed as the KeyPolicy", () => { + beforeEach(() => { + cfnKey = new kms.CfnKey(stack, 'CfnKey2', { + keyPolicy: cdk.Fn.conditionIf( + 'AlwaysTrue', + { + Statement: [ + { + Action: 'kms:action1', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + { + Statement: [ + { + Action: 'kms:action2', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + ), + }); + }); + + test('throws a descriptive exception', () => { + expect(() => { + kms.Key.fromCfnKey(cfnKey); + }).toThrow(/Could not parse the PolicyDocument of the passed AWS::KMS::Key/); + }); + }); + + describe("called with a CfnKey that has an 'Fn::If' passed as the Statement of a KeyPolicy", () => { + beforeEach(() => { + cfnKey = new kms.CfnKey(stack, 'CfnKey2', { + keyPolicy: { + Statement: cdk.Fn.conditionIf( + 'AlwaysTrue', + [ + { + Action: 'kms:action1', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + [ + { + Action: 'kms:action2', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + ), + Version: '2012-10-17', + }, + }); + }); + + test('throws a descriptive exception', () => { + expect(() => { + kms.Key.fromCfnKey(cfnKey); + }).toThrow(/Could not parse the PolicyDocument of the passed AWS::KMS::Key/); + }); + }); + + describe("called with a CfnKey that has an 'Fn::If' passed as one of the statements of a KeyPolicy", () => { + beforeEach(() => { + cfnKey = new kms.CfnKey(stack, 'CfnKey2', { + keyPolicy: { + Statement: [ + cdk.Fn.conditionIf( + 'AlwaysTrue', + { + Action: 'kms:action1', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + { + Action: 'kms:action2', + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ), + ], + Version: '2012-10-17', + }, + }); + }); + + test('throws a descriptive exception', () => { + expect(() => { + kms.Key.fromCfnKey(cfnKey); + }).toThrow(/Could not parse the PolicyDocument of the passed AWS::KMS::Key/); + }); + }); + + describe("called with a CfnKey that has an 'Fn::If' passed for the Action in one of the statements of a KeyPolicy", () => { + beforeEach(() => { + cfnKey = new kms.CfnKey(stack, 'CfnKey2', { + keyPolicy: { + Statement: [ + { + Action: cdk.Fn.conditionIf('AlwaysTrue', 'kms:action1', 'kms:action2'), + Effect: 'Allow', + Principal: '*', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('throws a descriptive exception', () => { + expect(() => { + key = kms.Key.fromCfnKey(cfnKey); + }).toThrow(/Could not parse the PolicyDocument of the passed AWS::KMS::Key/); + }); + }); +}); + describe('addToResourcePolicy allowNoOp and there is no policy', () => { // eslint-disable-next-line jest/expect-expect testFutureBehavior('succeed if set to true (default)', flags, cdk.App, (app) => { 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 a95373bd6d45f..f10f423a4c38b 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-manager.ts @@ -5,6 +5,7 @@ interface PackageManagerProps { readonly lockFile: string; readonly installCommand: string[]; readonly runCommand: string[]; + readonly argsSeparator?: string } /** @@ -26,7 +27,8 @@ export class PackageManager { public static PNPM = new PackageManager({ lockFile: 'pnpm-lock.yaml', installCommand: ['pnpm', 'install'], - runCommand: ['pnpm', 'run'], + runCommand: ['pnpm', 'exec'], + argsSeparator: '--', }); public static fromLockFile(lockFilePath: string): PackageManager { @@ -47,11 +49,13 @@ export class PackageManager { public readonly lockFile: string; public readonly installCommand: string[]; public readonly runCommand: string[]; + public readonly argsSeparator?: string; constructor(props: PackageManagerProps) { this.lockFile = props.lockFile; this.installCommand = props.installCommand; this.runCommand = props.runCommand; + this.argsSeparator = props.argsSeparator; } public runBinCommand(bin: string): string { @@ -60,6 +64,7 @@ export class PackageManager { os.platform() === 'win32' ? `${runCommand}.cmd` : runCommand, ...runArgs, bin, + ...(this.argsSeparator ? [this.argsSeparator] : []), ].join(' '); } } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index d3a1ee74f9380..c530de7d53b3c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "5.0.0", - "esbuild": "^0.12.3", + "esbuild": "^0.12.6", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts index e0721bbec3e38..7f64a18d2123f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/package-manager.test.ts @@ -19,7 +19,7 @@ test('from a pnpm-lock.yaml', () => { const packageManager = PackageManager.fromLockFile('/path/to/pnpm-lock.yaml'); expect(packageManager).toEqual(PackageManager.PNPM); - expect(packageManager.runBinCommand('my-bin')).toBe('pnpm run my-bin'); + expect(packageManager.runBinCommand('my-bin')).toBe('pnpm exec my-bin --'); }); test('defaults to NPM', () => { diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 77c9fee5a6f7b..ffd19568aa5dc 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -44,6 +44,16 @@ If `requirements.txt` or `Pipfile` exists at the entry path, the construct will all required modules in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-python3.7) according to the `runtime`. +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. +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. + +[`pipenv`]: https://pipenv-fork.readthedocs.io/en/latest/basics.html#example-pipfile-lock +[`poetry`]: https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control + **Lambda with a requirements.txt** ```plaintext diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 8bc6704c1321d..8e52a80bf0e32 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -336,7 +336,7 @@ export interface FunctionProps extends FunctionOptions { } /** - * Deploys a file from from inside the construct library as a function. + * Deploys a file from inside the construct library as a function. * * The supplied file is subject to the 4096 bytes limit of being embedded in a * CloudFormation template. diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index e6197ec716495..e44fefa6c00fe 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -76,7 +76,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/jest": "^26.0.23", - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/lodash": "^4.14.170", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 7af39bdef71e5..c172b5dc57a3f 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -71,14 +71,14 @@ "license": "Apache-2.0", "devDependencies": { "@types/nodeunit": "^0.0.31", - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nock": "^13.0.11", + "nock": "^13.1.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", "sinon": "^9.2.4", diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index 6ab9c8822cae9..07be5b2e363d0 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -120,4 +120,13 @@ See [the Developer Guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGu }); ``` +* Route 53 record + + ```ts + new route53.ARecord(this, 'AliasRecord', { + zone, + target: route53.RecordTarget.fromAlias(new targets.Route53RecordTarget(record)), + }); + ``` + See the documentation of `@aws-cdk/aws-route53` for more information. diff --git a/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts b/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts index 5aa40d5a52f10..c87e26568d6b8 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts @@ -10,7 +10,7 @@ import * as route53 from '@aws-cdk/aws-route53'; export class ApiGatewayDomain implements route53.IAliasRecordTarget { constructor(private readonly domainName: apig.IDomainName) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: this.domainName.domainNameAliasDomainName, hostedZoneId: this.domainName.domainNameAliasHostedZoneId, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts index 6aea6f72de59e..962315ed2ab8c 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts @@ -10,10 +10,10 @@ export class ApiGatewayv2DomainProperties implements route53.IAliasRecordTarget */ constructor(private readonly regionalDomainName: string, private readonly regionalHostedZoneId: string) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: this.regionalDomainName, hostedZoneId: this.regionalHostedZoneId, }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts index 54df0f33e450f..a4497826828f1 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts @@ -10,7 +10,7 @@ export class BucketWebsiteTarget implements route53.IAliasRecordTarget { constructor(private readonly bucket: s3.IBucket) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { const { region } = Stack.of(this.bucket.stack); if (Token.isUnresolved(region)) { diff --git a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts index 8b25fbe4f331e..54bf900b1f6c8 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts @@ -8,7 +8,7 @@ export class ClassicLoadBalancerTarget implements route53.IAliasRecordTarget { constructor(private readonly loadBalancer: elb.LoadBalancer) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneNameId, dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts index d11e0c9de2324..0d6f57dcf9dd6 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts @@ -40,7 +40,7 @@ export class CloudFrontTarget implements route53.IAliasRecordTarget { constructor(private readonly distribution: cloudfront.IDistribution) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: CloudFrontTarget.getHostedZoneId(this.distribution), dnsName: this.distribution.distributionDomainName, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts index d80aaaa140b1b..e9093265ea228 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts @@ -19,7 +19,7 @@ export class GlobalAcceleratorDomainTarget implements route53.IAliasRecordTarget constructor(private readonly acceleratorDomainName: string) { } - bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: GlobalAcceleratorTarget.GLOBAL_ACCELERATOR_ZONE_ID, dnsName: this.acceleratorDomainName, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/index.ts b/packages/@aws-cdk/aws-route53-targets/lib/index.ts index 5c8b86fb959c1..92f1d9ee08d25 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/index.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/index.ts @@ -7,3 +7,4 @@ export * from './load-balancer-target'; export * from './interface-vpc-endpoint-target'; export * from './userpool-domain'; export * from './global-accelerator-target'; +export * from './route53-record'; diff --git a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts index 40b0902cd236a..1882a5225b959 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts @@ -11,7 +11,7 @@ export class InterfaceVpcEndpointTarget implements route53.IAliasRecordTarget { this.cfnVpcEndpoint = this.vpcEndpoint.node.findChild('Resource') as ec2.CfnVPCEndpoint; } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: cdk.Fn.select(1, cdk.Fn.split(':', cdk.Fn.select(0, this.cfnVpcEndpoint.attrDnsEntries))), hostedZoneId: cdk.Fn.select(0, cdk.Fn.split(':', cdk.Fn.select(0, this.cfnVpcEndpoint.attrDnsEntries))), diff --git a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts index d398256c57f8b..d3bb10280690e 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts @@ -8,7 +8,7 @@ export class LoadBalancerTarget implements route53.IAliasRecordTarget { constructor(private readonly loadBalancer: elbv2.ILoadBalancerV2) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneId, dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts b/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts new file mode 100644 index 0000000000000..e8a042c7e287c --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts @@ -0,0 +1,19 @@ +import * as route53 from '@aws-cdk/aws-route53'; + +/** + * Use another Route 53 record as an alias record target + */ +export class Route53RecordTarget implements route53.IAliasRecordTarget { + constructor(private readonly record: route53.IRecordSet) { + } + + public bind(_record: route53.IRecordSet, zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + if (!zone) { // zone introduced as optional to avoid a breaking change + throw new Error('Cannot bind to record without a zone'); + } + return { + dnsName: this.record.domainName, + hostedZoneId: zone.hostedZoneId, + }; + } +} diff --git a/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts b/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts index 20b04f4e64f3a..a4e5c585f8927 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts @@ -1,5 +1,5 @@ import { UserPoolDomain } from '@aws-cdk/aws-cognito'; -import { AliasRecordTargetConfig, IAliasRecordTarget, IRecordSet } from '@aws-cdk/aws-route53'; +import { AliasRecordTargetConfig, IAliasRecordTarget, IHostedZone, IRecordSet } from '@aws-cdk/aws-route53'; import { CloudFrontTarget } from './cloudfront-target'; /** @@ -9,7 +9,7 @@ export class UserPoolDomainTarget implements IAliasRecordTarget { constructor(private readonly domain: UserPoolDomain) { } - public bind(_record: IRecordSet): AliasRecordTargetConfig { + public bind(_record: IRecordSet, _zone?: IHostedZone): AliasRecordTargetConfig { return { dnsName: this.domain.cloudFrontDomainName, hostedZoneId: CloudFrontTarget.getHostedZoneId(this.domain), diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json new file mode 100644 index 0000000000000..9394dd9bb11c1 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json @@ -0,0 +1,42 @@ +{ + "Resources": { + "HostedZoneDB99F866": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "cdk-integ.com." + } + }, + "WWW9F8609DA": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "www.cdk-integ.com.", + "Type": "A", + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + }, + "ResourceRecords": [ + "1.2.3.4" + ], + "TTL": "1800" + } + }, + "Alias325C5727": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "cdk-integ.com.", + "Type": "A", + "AliasTarget": { + "DNSName": { + "Ref": "WWW9F8609DA" + }, + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + }, + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts new file mode 100644 index 0000000000000..aeec73fa19233 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import * as route53 from '@aws-cdk/aws-route53'; +import { App, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as targets from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const zone = new route53.PublicHostedZone(this, 'HostedZone', { + zoneName: 'cdk-integ.com', + }); + + const www = new route53.ARecord(this, 'WWW', { + zone, + recordName: 'www.cdk-integ.com', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4'), + }); + + new route53.ARecord(this, 'Alias', { + zone, + target: route53.RecordTarget.fromAlias(new targets.Route53RecordTarget(www)), + }); + } +} + +const app = new App(); +new TestStack(app, 'aws-cdk-r53-record-alias-target-integ'); +app.synth(); diff --git a/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts b/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts new file mode 100644 index 0000000000000..9b9bc7585a433 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts @@ -0,0 +1,32 @@ +import '@aws-cdk/assert-internal/jest'; +import { ARecord, PublicHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; +import { Stack } from '@aws-cdk/core'; +import { Route53RecordTarget } from '../lib'; + +test('use another route 53 record as record target', () => { + // GIVEN + const stack = new Stack(); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' }); + const record = new ARecord(zone, 'Record', { + zone, + target: RecordTarget.fromIpAddresses('1.2.3.4'), + }); + + // WHEN + new ARecord(zone, 'Alias', { + zone, + target: RecordTarget.fromAlias(new Route53RecordTarget(record)), + }); + + // THEN + expect(stack).toHaveResource('AWS::Route53::RecordSet', { + AliasTarget: { + DNSName: { + Ref: 'HostedZoneRecordB6AB510D', + }, + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index 1c4affa83e3a0..d4376f335d848 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -76,6 +76,21 @@ new route53.NsRecord(this, 'NSRecord', { }); ``` +To add a DS record to your zone: + +```ts +import * as route53 from '@aws-cdk/aws-route53'; + +new route53.DsRecord(this, 'DSRecord', { + zone: myZone, + recordName: 'foo', + values: [ + '12345 3 1 123456789abcdef67890123456789abcdef67890', + ], + ttl: Duration.minutes(90), // Optional - default is 30 minutes +}); +``` + To add an A record to your zone: ```ts diff --git a/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts b/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts index 6e04daa0f565e..e9c2248f0b470 100644 --- a/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts +++ b/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts @@ -1,3 +1,4 @@ +import { IHostedZone } from './hosted-zone-ref'; import { IRecordSet } from './record-set'; /** @@ -8,7 +9,7 @@ export interface IAliasRecordTarget { /** * Return hosted zone ID and DNS name, usable for Route53 alias targets */ - bind(record: IRecordSet): AliasRecordTargetConfig; + bind(record: IRecordSet, zone?: IHostedZone): AliasRecordTargetConfig; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 57f93931c2097..aa3d4b2303285 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -59,6 +59,13 @@ export enum RecordType { */ CNAME = 'CNAME', + /** + * A delegation signer (DS) record refers a zone key for a delegated subdomain zone. + * + * @see https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html#DSFormat + */ + DS = 'DS', + /** * An MX record specifies the names of your mail servers and, if you have two or more mail servers, * the priority order. @@ -219,7 +226,7 @@ export class RecordSet extends Resource implements IRecordSet { name: determineFullyQualifiedDomainName(props.recordName || props.zone.zoneName, props.zone), type: props.recordType, resourceRecords: props.target.values, - aliasTarget: props.target.aliasTarget && props.target.aliasTarget.bind(this), + aliasTarget: props.target.aliasTarget && props.target.aliasTarget.bind(this, props.zone), ttl, comment: props.comment, }); @@ -566,6 +573,31 @@ export class NsRecord extends RecordSet { } } +/** + * Construction properties for a DSRecord. + */ +export interface DsRecordProps extends RecordSetOptions { + /** + * The DS values. + */ + readonly values: string[]; +} + +/** + * A DNS DS record + * + * @resource AWS::Route53::RecordSet + */ +export class DsRecord extends RecordSet { + constructor(scope: Construct, id: string, props: DsRecordProps) { + super(scope, id, { + ...props, + recordType: RecordType.DS, + target: RecordTarget.fromValues(...props.values), + }); + } +} + /** * Construction properties for a ZoneDelegationRecord */ diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 004a55507b021..0d2b11232db8f 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -71,7 +71,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/jest": "^26.0.23", "@types/nodeunit": "^0.0.31", "aws-sdk": "^2.848.0", @@ -112,6 +112,7 @@ "props-physical-name:@aws-cdk/aws-route53.CaaAmazonRecordProps", "props-physical-name:@aws-cdk/aws-route53.CaaRecordProps", "props-physical-name:@aws-cdk/aws-route53.CnameRecordProps", + "props-physical-name:@aws-cdk/aws-route53.DsRecordProps", "props-physical-name:@aws-cdk/aws-route53.HostedZoneProps", "props-physical-name:@aws-cdk/aws-route53.MxRecordProps", "props-physical-name:@aws-cdk/aws-route53.NsRecordProps", diff --git a/packages/@aws-cdk/aws-route53/test/record-set.test.ts b/packages/@aws-cdk/aws-route53/test/record-set.test.ts index a9d5446185f66..ff3a7f6d08c29 100644 --- a/packages/@aws-cdk/aws-route53/test/record-set.test.ts +++ b/packages/@aws-cdk/aws-route53/test/record-set.test.ts @@ -540,6 +540,36 @@ nodeunitShim({ test.done(); }, + 'DS record'(test: Test) { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.DsRecord(stack, 'DS', { + zone, + recordName: 'www', + values: ['12345 3 1 123456789abcdef67890123456789abcdef67890'], + }); + + // THEN + expect(stack).to(haveResource('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'DS', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '12345 3 1 123456789abcdef67890123456789abcdef67890', + ], + TTL: '1800', + })); + test.done(); + }, + 'Zone delegation record'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 002435a234c0c..b375c510a14d1 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1656,6 +1656,7 @@ export class Bucket extends BucketBase { transitionDate: t.transitionDate, transitionInDays: t.transitionAfter && t.transitionAfter.toDays(), })), + expiredObjectDeleteMarker: rule.expiredObjectDeleteMarker, tagFilters: self.parseTagFilters(rule.tagFilters), }; @@ -1840,7 +1841,8 @@ export class Bucket extends BucketBase { // objects in the bucket this.addToResourcePolicy(new iam.PolicyStatement({ actions: [ - ...perms.BUCKET_READ_ACTIONS, // list objects + // list objects + ...perms.BUCKET_READ_METADATA_ACTIONS, ...perms.BUCKET_DELETE_ACTIONS, // and then delete them ], resources: [ diff --git a/packages/@aws-cdk/aws-s3/lib/perms.ts b/packages/@aws-cdk/aws-s3/lib/perms.ts index eebab60da2104..f57b97153f27c 100644 --- a/packages/@aws-cdk/aws-s3/lib/perms.ts +++ b/packages/@aws-cdk/aws-s3/lib/perms.ts @@ -4,6 +4,11 @@ export const BUCKET_READ_ACTIONS = [ 's3:List*', ]; +export const BUCKET_READ_METADATA_ACTIONS = [ + 's3:GetBucket*', + 's3:List*', +]; + export const LEGACY_BUCKET_PUT_ACTIONS = [ 's3:PutObject*', 's3:Abort*', diff --git a/packages/@aws-cdk/aws-s3/lib/rule.ts b/packages/@aws-cdk/aws-s3/lib/rule.ts index 294f3852a1d0a..92ad24e2cf6a8 100644 --- a/packages/@aws-cdk/aws-s3/lib/rule.ts +++ b/packages/@aws-cdk/aws-s3/lib/rule.ts @@ -100,6 +100,14 @@ export interface LifecycleRule { * @default Rule applies to all objects */ readonly tagFilters?: {[tag: string]: any}; + + /** + * Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. + * If set to true, the delete marker will be expired. + * + * @default false + */ + readonly expiredObjectDeleteMarker?: boolean; } /** diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index d81c3002b3f7a..86c4ce5024cf5 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -71,7 +71,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/jest": "^26.0.23", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3/test/bucket.test.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts index 95a8cf377dfc6..1c90c48957416 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -2385,7 +2385,6 @@ describe('bucket', () => { 'Statement': [ { 'Action': [ - 's3:GetObject*', 's3:GetBucket*', 's3:List*', 's3:DeleteObject*', diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket-auto-delete-objects.expected.json b/packages/@aws-cdk/aws-s3/test/integ.bucket-auto-delete-objects.expected.json index 831d072339649..107132c1cd2dd 100644 --- a/packages/@aws-cdk/aws-s3/test/integ.bucket-auto-delete-objects.expected.json +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket-auto-delete-objects.expected.json @@ -15,7 +15,6 @@ "Statement": [ { "Action": [ - "s3:GetObject*", "s3:GetBucket*", "s3:List*", "s3:DeleteObject*" diff --git a/packages/@aws-cdk/aws-s3/test/rules.test.ts b/packages/@aws-cdk/aws-s3/test/rules.test.ts index 6b64f919895e1..fb613bcf109e8 100644 --- a/packages/@aws-cdk/aws-s3/test/rules.test.ts +++ b/packages/@aws-cdk/aws-s3/test/rules.test.ts @@ -128,4 +128,28 @@ nodeunitShim({ test.done(); }, + + 'Bucket with expiredObjectDeleteMarker'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new Bucket(stack, 'Bucket', { + lifecycleRules: [{ + expiredObjectDeleteMarker: true, + }], + }); + + // THEN + expect(stack).to(haveResource('AWS::S3::Bucket', { + LifecycleConfiguration: { + Rules: [{ + ExpiredObjectDeleteMarker: true, + Status: 'Enabled', + }], + }, + })); + + test.done(); + }, }); diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index 6648518b288f4..2f78cfbee73cc 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -70,7 +70,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts index c38bd9d8d5039..5e1e1d708d2d2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts @@ -360,8 +360,10 @@ export class EmrCreateCluster extends sfn.TaskStateBase { export namespace EmrCreateCluster { /** - * Valid valus for the Cluster ScaleDownBehavior + * The Cluster ScaleDownBehavior specifies the way that individual Amazon EC2 instances terminate when an automatic scale-in activity + * occurs or an instance group is resized. * + * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_RunJobFlow.html#EMR-RunJobFlow-request-ScaleDownBehavior */ export enum EmrClusterScaleDownBehavior { /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts index b172ed94de411..7a4a56c536a6b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts @@ -83,11 +83,12 @@ interface FieldHandlers { export function recurseObject(obj: object | undefined, handlers: FieldHandlers, visited: object[] = []): object | undefined { if (obj === undefined) { return undefined; } - if (visited.includes(obj)) { - return {}; - } else { - visited.push(obj); - } + + // Avoiding infinite recursion + if (visited.includes(obj)) { return {}; } + + // Marking current object as visited for the current recursion path + visited.push(obj); const ret: any = {}; for (const [key, value] of Object.entries(obj)) { @@ -106,6 +107,10 @@ export function recurseObject(obj: object | undefined, handlers: FieldHandlers, } } + // Removing from visited after leaving the current recursion path + // Allowing it to be visited again if it's not causing a recursion (circular reference) + visited.pop(); + return ret; } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index b9ff82b175709..15591ddeebd76 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -152,4 +152,26 @@ describe('Fields', () => { expect(FieldUtils.findReferencedPaths(paths)) .toStrictEqual(['$.listField', '$.numField', '$.stringField']); }); + + test('repeated object references at different tree paths should not be considered as recursions', () => { + const repeatedObject = { + field: JsonPath.stringAt('$.stringField'), + numField: JsonPath.numberAt('$.numField'), + }; + expect(FieldUtils.renderObject( + { + reference1: repeatedObject, + reference2: repeatedObject, + }, + )).toStrictEqual({ + reference1: { + 'field.$': '$.stringField', + 'numField.$': '$.numField', + }, + reference2: { + 'field.$': '$.stringField', + 'numField.$': '$.numField', + }, + }); + }); }); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 0c6d276cbc8d3..2889a32cbcd2e 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,46 @@ +# CloudFormation Resource Specification v37.1.0 + +## New Resource Types + +* AWS::CUR::ReportDefinition + +## Attribute Changes + +* AWS::MWAA::Environment CloudWatchLogGroupArn (__added__) + +## Property Changes + +* AWS::ECR::Repository EncryptionConfiguration.PrimitiveType (__deleted__) +* AWS::ECR::Repository EncryptionConfiguration.Type (__added__) +* AWS::ECR::Repository ImageScanningConfiguration.PrimitiveType (__deleted__) +* AWS::ECR::Repository ImageScanningConfiguration.Type (__added__) +* AWS::FraudDetector::Detector AssociatedModels (__added__) +* AWS::QLDB::Ledger PermissionsMode.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::XRay::Group Tags.ItemType (__deleted__) +* AWS::XRay::Group Tags.PrimitiveItemType (__added__) +* AWS::XRay::SamplingRule Tags.ItemType (__deleted__) +* AWS::XRay::SamplingRule Tags.PrimitiveItemType (__added__) + +## Property Type Changes + +* AWS::ECR::Repository.EncryptionConfiguration (__added__) +* AWS::ECR::Repository.ImageScanningConfiguration (__added__) +* AWS::FraudDetector::Detector.Model (__added__) +* AWS::ACMPCA::CertificateAuthority.CrlConfiguration S3ObjectAcl (__added__) +* AWS::ApplicationInsights::Application.ComponentMonitoringSetting ComponentConfigurationMode.Required (__changed__) + * Old: false + * New: true +* AWS::ApplicationInsights::Application.ComponentMonitoringSetting Tier.Required (__changed__) + * Old: false + * New: true +* AWS::CloudFront::Function.FunctionMetadata FunctionARN.Required (__changed__) + * Old: true + * New: false +* AWS::FSx::FileSystem.LustreConfiguration DataCompressionType (__added__) + + # CloudFormation Resource Specification v37.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 09374235967a4..b16ec85c58260 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -37.0.0 +37.1.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 1487b4a21e720..643c97ee1a78f 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -424,6 +424,12 @@ "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" + }, + "S3ObjectAcl": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificateauthority-crlconfiguration.html#cfn-acmpca-certificateauthority-crlconfiguration-s3objectacl", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -7218,7 +7224,7 @@ "ComponentConfigurationMode": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-applicationinsights-application-componentmonitoringsetting.html#cfn-applicationinsights-application-componentmonitoringsetting-componentconfigurationmode", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "ComponentName": { @@ -7242,7 +7248,7 @@ "Tier": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-applicationinsights-application-componentmonitoringsetting.html#cfn-applicationinsights-application-componentmonitoringsetting-tier", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -11181,7 +11187,7 @@ "FunctionARN": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-function-functionmetadata.html#cfn-cloudfront-function-functionmetadata-functionarn", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -20861,6 +20867,34 @@ } } }, + "AWS::ECR::Repository.EncryptionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-encryptionconfiguration.html", + "Properties": { + "EncryptionType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-encryptionconfiguration.html#cfn-ecr-repository-encryptionconfiguration-encryptiontype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "KmsKey": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-encryptionconfiguration.html#cfn-ecr-repository-encryptionconfiguration-kmskey", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ECR::Repository.ImageScanningConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-imagescanningconfiguration.html", + "Properties": { + "ScanOnPush": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-imagescanningconfiguration.html#cfn-ecr-repository-imagescanningconfiguration-scanonpush", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ECR::Repository.LifecyclePolicy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecr-repository-lifecyclepolicy.html", "Properties": { @@ -26407,6 +26441,12 @@ "Required": false, "UpdateType": "Mutable" }, + "DataCompressionType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-datacompressiontype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DeploymentType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-deploymenttype", "PrimitiveType": "String", @@ -26844,6 +26884,17 @@ } } }, + "AWS::FraudDetector::Detector.Model": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-frauddetector-detector-model.html", + "Properties": { + "Arn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-frauddetector-detector-model.html#cfn-frauddetector-detector-model-arn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::FraudDetector::Detector.Outcome": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-frauddetector-detector-outcome.html", "Properties": { @@ -59785,7 +59836,7 @@ } } }, - "ResourceSpecificationVersion": "37.0.0", + "ResourceSpecificationVersion": "37.1.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -65066,6 +65117,85 @@ } } }, + "AWS::CUR::ReportDefinition": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html", + "Properties": { + "AdditionalArtifacts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-additionalartifacts", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AdditionalSchemaElements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-additionalschemaelements", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "BillingViewArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-billingviewarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Compression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-compression", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Format": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-format", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RefreshClosedReports": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-refreshclosedreports", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + }, + "ReportName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-reportname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ReportVersioning": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-reportversioning", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3Prefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-s3prefix", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3Region": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-s3region", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TimeUnit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cur-reportdefinition.html#cfn-cur-reportdefinition-timeunit", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::Cassandra::Keyspace": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cassandra-keyspace.html", "Properties": { @@ -73737,14 +73867,14 @@ "Properties": { "EncryptionConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html#cfn-ecr-repository-encryptionconfiguration", - "PrimitiveType": "Json", "Required": false, + "Type": "EncryptionConfiguration", "UpdateType": "Immutable" }, "ImageScanningConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html#cfn-ecr-repository-imagescanningconfiguration", - "PrimitiveType": "Json", "Required": false, + "Type": "ImageScanningConfiguration", "UpdateType": "Mutable" }, "ImageTagMutability": { @@ -77290,6 +77420,14 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-detector.html", "Properties": { + "AssociatedModels": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-detector.html#cfn-frauddetector-detector-associatedmodels", + "DuplicatesAllowed": true, + "ItemType": "Model", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "Description": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-detector.html#cfn-frauddetector-detector-description", "PrimitiveType": "String", @@ -84450,6 +84588,9 @@ "Arn": { "PrimitiveType": "String" }, + "CloudWatchLogGroupArn": { + "PrimitiveType": "String" + }, "WebserverUrl": { "PrimitiveType": "String" } @@ -88519,7 +88660,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-qldb-ledger.html#cfn-qldb-ledger-permissionsmode", "PrimitiveType": "String", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-qldb-ledger.html#cfn-qldb-ledger-tags", @@ -96556,7 +96697,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-xray-group.html#cfn-xray-group-tags", - "ItemType": "Json", + "PrimitiveItemType": "Json", "Required": false, "Type": "List", "UpdateType": "Mutable" @@ -96597,7 +96738,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-xray-samplingrule.html#cfn-xray-samplingrule-tags", - "ItemType": "Json", + "PrimitiveItemType": "Json", "Required": false, "Type": "List", "UpdateType": "Mutable" 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 56c609a08dd39..a9e5df9bd1d0b 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 @@ -64,6 +64,13 @@ export interface AmiContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Owners to DescribeImages call * @@ -90,6 +97,14 @@ export interface AvailabilityZonesContextQuery { * Query region */ readonly region: string; + + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + } /** @@ -106,6 +121,13 @@ export interface HostedZoneContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * The domain name e.g. example.com to lookup */ @@ -143,6 +165,13 @@ export interface SSMParameterContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Parameter name to query */ @@ -163,6 +192,13 @@ export interface VpcContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Filters to apply to the VPC * @@ -205,6 +241,13 @@ export interface EndpointServiceAvailabilityZonesContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Query service name */ @@ -261,6 +304,13 @@ export interface LoadBalancerContextQuery extends LoadBalancerFilter { * Query region */ readonly region: string; + + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; } /** @@ -312,6 +362,13 @@ export interface LoadBalancerListenerContextQuery extends LoadBalancerFilter { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Find by listener's arn * @default - does not find by listener arn @@ -345,6 +402,13 @@ export interface SecurityGroupContextQuery { */ readonly region: string; + /** + * The ARN of the role that should be used to look up the missing values + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * Security group id */ diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 5b573ad4c418e..1ee1151b7e0c2 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -65,7 +65,7 @@ "jest": "^26.6.3", "mock-fs": "^4.14.0", "pkglint": "0.0.0", - "typescript-json-schema": "^0.50.0" + "typescript-json-schema": "^0.50.1" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", 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 77d3117d0aae2..3c0f38f598570 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 @@ -453,6 +453,10 @@ "description": "Region to query", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "owners": { "description": "Owners to DescribeImages call (Default - All owners)", "type": "array", @@ -488,6 +492,10 @@ "region": { "description": "Query region", "type": "string" + }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" } }, "required": [ @@ -507,6 +515,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "domainName": { "description": "The domain name e.g. example.com to lookup", "type": "string" @@ -539,6 +551,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "parameterName": { "description": "Parameter name to query", "type": "string" @@ -562,6 +578,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "filter": { "description": "Filters to apply to the VPC\n\nFilter parameters are the same as passed to DescribeVpcs.", "type": "object", @@ -597,6 +617,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "serviceName": { "description": "Query service name", "type": "string" @@ -620,6 +644,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "loadBalancerType": { "$ref": "#/definitions/LoadBalancerType", "description": "Filter load balancers by their type" @@ -662,6 +690,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "listenerArn": { "description": "Find by listener's arn (Default - does not find by listener arn)", "type": "string" @@ -716,6 +748,10 @@ "description": "Query region", "type": "string" }, + "lookupRoleArn": { + "description": "The ARN of the role that should be used to look up the missing values (Default - None)", + "type": "string" + }, "securityGroupId": { "description": "Security group id", "type": "string" 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 b056ff69e87b5..42c883f995fd4 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":"11.0.0"} \ No newline at end of file +{"version":"12.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index c4e3b0a8a1b6a..626391a9fc4e3 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -33,7 +33,7 @@ "@types/jest": "^26.0.23", "@types/string-width": "^4.0.1", "cdk-build-tools": "0.0.0", - "fast-check": "^2.14.0", + "fast-check": "^2.16.0", "jest": "^26.6.3", "pkglint": "0.0.0", "ts-jest": "^26.5.6" diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 2dcd71edc2b38..2c67aeb4d554b 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -118,14 +118,112 @@ role.addToPolicy(new iam.PolicyStatement({ })); ``` -If you need, you can also convert the CloudFormation resource to a higher-level -resource by importing it: +### Converting L1 resources to L2 + +The resources the `getResource` method returns are what the CDK calls +[Layer 1 resources](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_cfn) +(like `CfnBucket`). +However, in many places in the Construct Library, +the CDK requires so-called Layer 2 resources, like `IBucket`. +There are two ways of going from an L1 to an L2 resource. + +#### Using`fromCfn*()` methods + +This is the preferred method of converting an L1 resource to an L2. +It works by invoking a static method of the class of the L2 resource +whose name starts with `fromCfn` - +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'; + +const cfnKey = cfnTemplate.getResource('Key') as kms.CfnKey; +const key = kms.Key.fromCfnKey(cfnKey); +``` + +This returns an instance of the `kms.IKey` type that can be passed anywhere in the CDK an `IKey` is expected. +What is more, that `IKey` instance will be mutable - +which means calling any mutating methods on it, +like `addToResourcePolicy()`, +will be reflected in the resulting template. + +Note that, in some cases, the `fromCfn*()` method might not be able to create an L2 from the underlying L1. +This can happen when the underlying L1 heavily uses CloudFormation functions. +For example, if you tried to create an L2 `IKey` +from an L1 represented as this CloudFormation template: + +```json +{ + "Resources": { + "Key": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Fn::If": [ + "Condition", + { + "Action": "kms:if-action", + "Resource": "*", + "Principal": "*", + "Effect": "Allow" + }, + { + "Action": "kms:else-action", + "Resource": "*", + "Principal": "*", + "Effect": "Allow" + } + ] + } + ], + "Version": "2012-10-17" + } + } + } + } +} +``` + +The `Key.fromCfnKey()` method does not know how to translate that into CDK L2 concepts, +and would throw an exception. + +In those cases, you need the use the second method of converting an L1 to an L2. + +#### Using `from*Name/Arn/Attributes()` methods + +If the resource you need does not have a `fromCfn*()` method, +or if it does, but it throws an exception for your particular L1, +you need to use the second method of converting an L1 resource to L2. + +Each L2 class has static factory methods with names like `from*Name()`, +`from*Arn()`, and/or `from*Attributes()`. +You can obtain an L2 resource from an L1 by passing the correct properties of the L1 as the arguments to those methods: ```ts +// using from*Name() const bucket = s3.Bucket.fromBucketName(this, 'L2Bucket', cfnBucket.ref); -// bucket is of type s3.IBucket + +// using from*Arn() +const key = kms.Key.fromKeyArn(this, 'L2Key', cfnKey.attrArn); + +// using from*Attributes() +const vpc = ec2.Vpc.fromVpcAttributes(this, 'L2Vpc', { + vpcId: cfnVpc.ref, + availabilityZones: cdk.Fn.getAzs(), + privateSubnetIds: [privateCfnSubnet1.ref, privateCfnSubnet2.ref], +}); ``` +As long as they just need to be referenced, +and not changed in any way, everything should work; +however, note that resources returned from those methods, +unlike those returned by `fromCfn*()` methods, +are immutable, which means calling any mutating methods on them will have no effect. +You will have to mutate the underlying L1 in order to change them. + ## Non-resource template elements In addition to resources, @@ -215,7 +313,7 @@ new inc.CfnInclude(this, 'includeTemplate', { ``` This will replace all references to `MyParam` with the string `'my-value'`, -and `MyParam` will be removed from the 'Parameters' section of the template. +and `MyParam` will be removed from the 'Parameters' section of the resulting template. ## Nested Stacks diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index db6efaa5d4416..9e1c99e7331fd 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -108,6 +108,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-config": "0.0.0", + "@aws-cdk/aws-cur": "0.0.0", "@aws-cdk/aws-customerprofiles": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-datapipeline": "0.0.0", @@ -272,6 +273,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-config": "0.0.0", + "@aws-cdk/aws-cur": "0.0.0", "@aws-cdk/aws-customerprofiles": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-datapipeline": "0.0.0", diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/properties-not-in-cfn-spec.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/properties-not-in-cfn-spec.json index eec4fc6f608c7..383b85eb5b1a0 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/properties-not-in-cfn-spec.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/properties-not-in-cfn-spec.json @@ -42,6 +42,11 @@ "Type": "Api", "PropertyNotInCfnSchema": "unmodeled property in map" } + }, + "ParentPropertyNotInCfnSchema": { + "ChildPropertyNotInCfnSchema": { + "Ref": "Bucket" + } } } } diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index 9f9ee669374ed..d6be50b9149fb 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -7,9 +7,11 @@ import { CfnCreationPolicy, CfnDeletionPolicy, CfnUpdatePolicy } from './cfn-res import { Construct, IConstruct, Node } from 'constructs'; import { addDependency } from './deps'; import { CfnReference } from './private/cfn-reference'; +import { CLOUDFORMATION_TOKEN_RESOLVER } from './private/cloudformation-lang'; import { Reference } from './reference'; import { RemovalPolicy, RemovalPolicyOptions } from './removal-policy'; import { TagManager } from './tag-manager'; +import { Tokenization } from './token'; import { capitalizePropertyNames, ignoreEmpty, PostResolveToken } from './util'; export interface CfnResourceProps { @@ -326,7 +328,14 @@ export class CfnResource extends CfnRefElement { const hasDefined = Object.values(renderedProps).find(v => v !== undefined); resourceDef.Properties = hasDefined !== undefined ? renderedProps : undefined; } - return deepMerge(resourceDef, this.rawOverrides); + const resolvedRawOverrides = Tokenization.resolve(this.rawOverrides, { + scope: this, + resolver: CLOUDFORMATION_TOKEN_RESOLVER, + // we need to preserve the empty elements here, + // as that's how removing overrides are represented as + removeEmpty: false, + }); + return deepMerge(resourceDef, resolvedRawOverrides); }), }, }; diff --git a/packages/@aws-cdk/core/lib/private/resolve.ts b/packages/@aws-cdk/core/lib/private/resolve.ts index 5f9620ecb759c..f560a852ffd4d 100644 --- a/packages/@aws-cdk/core/lib/private/resolve.ts +++ b/packages/@aws-cdk/core/lib/private/resolve.ts @@ -84,6 +84,13 @@ export interface IResolveOptions { * @default false */ allowIntrinsicKeys?: boolean; + + /** + * Whether to remove undefined elements from arrays and objects when resolving. + * + * @default true + */ + removeEmpty?: boolean; } /** @@ -120,6 +127,9 @@ export function resolve(obj: any, options: IResolveOptions): any { throw new Error('Unable to resolve object tree with circular reference. Path: ' + pathName); } + // whether to leave the empty elements when resolving - false by default + const leaveEmpty = options.removeEmpty === false; + // // undefined // @@ -188,7 +198,7 @@ export function resolve(obj: any, options: IResolveOptions): any { const arr = obj .map((x, i) => makeContext(`${i}`)[0].resolve(x)) - .filter(x => typeof(x) !== 'undefined'); + .filter(x => leaveEmpty || typeof(x) !== 'undefined'); return arr; } @@ -221,6 +231,9 @@ export function resolve(obj: any, options: IResolveOptions): any { // skip undefined if (typeof(value) === 'undefined') { + if (leaveEmpty) { + result[key] = undefined; + } continue; } @@ -326,4 +339,4 @@ function tagResolvedValue(value: any, typeHint: ResolutionTypeHint): any { export function resolvedTypeHint(value: any): ResolutionTypeHint | undefined { if (typeof value !== 'object' || value == null) { return undefined; } return value[RESOLUTION_TYPEHINT_SYM]; -} \ No newline at end of file +} 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 c902e3326d44b..1dc8ff754c99a 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -84,6 +84,13 @@ export interface DefaultStackSynthesizerProps { */ readonly imageAssetPublishingRoleArn?: string; + /** + * The role to use to look up values from the target AWS account during synthesis + * + * @default - None + */ + readonly lookupRoleArn?: string; + /** * External ID to use when assuming role for image asset publishing * @@ -158,6 +165,15 @@ export interface DefaultStackSynthesizerProps { * @default - DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PREFIX */ readonly bucketPrefix?: string; + + /** + * Bootstrap stack version SSM parameter. + * + * The placeholder `${Qualifier}` will be replaced with the value of qualifier. + * + * @default DefaultStackSynthesizer.DEFAULT_BOOTSTRAP_STACK_VERSION_SSM_PARAMETER + */ + readonly bootstrapStackVersionSsmParameter?: string; } /** @@ -195,6 +211,11 @@ export class DefaultStackSynthesizer extends StackSynthesizer { */ public static readonly DEFAULT_IMAGE_ASSET_PUBLISHING_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region}'; + /** + * Default lookup role ARN for missing values. + */ + public static readonly DEFAULT_LOOKUP_ROLE_ARN = 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region}'; + /** * Default image assets repository name */ @@ -215,6 +236,11 @@ export class DefaultStackSynthesizer extends StackSynthesizer { */ public static readonly DEFAULT_FILE_ASSET_PREFIX = ''; + /** + * Default bootstrap stack version SSM parameter. + */ + public static readonly DEFAULT_BOOTSTRAP_STACK_VERSION_SSM_PARAMETER = '/cdk-bootstrap/${Qualifier}/version'; + private _stack?: Stack; private bucketName?: string; private repositoryName?: string; @@ -222,8 +248,10 @@ export class DefaultStackSynthesizer extends StackSynthesizer { private _cloudFormationExecutionRoleArn?: string; private fileAssetPublishingRoleArn?: string; private imageAssetPublishingRoleArn?: string; + private lookupRoleArn?: string; private qualifier?: string; - private bucketPrefix?: string + private bucketPrefix?: string; + private bootstrapStackVersionSsmParameter?: string; private readonly files: NonNullable = {}; private readonly dockerImages: NonNullable = {}; @@ -282,7 +310,13 @@ export class DefaultStackSynthesizer extends StackSynthesizer { this._cloudFormationExecutionRoleArn = specialize(this.props.cloudFormationExecutionRole ?? DefaultStackSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN); this.fileAssetPublishingRoleArn = specialize(this.props.fileAssetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSET_PUBLISHING_ROLE_ARN); 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.bootstrapStackVersionSsmParameter = replaceAll( + this.props.bootstrapStackVersionSsmParameter ?? DefaultStackSynthesizer.DEFAULT_BOOTSTRAP_STACK_VERSION_SSM_PARAMETER, + '${Qualifier}', + qualifier, + ); /* eslint-enable max-len */ } @@ -362,6 +396,10 @@ export class DefaultStackSynthesizer extends StackSynthesizer { }; } + protected synthesizeStackTemplate(stack: Stack, session: ISynthesisSession): void { + stack._synthesizeTemplate(session, this.lookupRoleArn); + } + /** * Synthesize the associated stack to the session */ @@ -375,7 +413,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { // If it's done AFTER _synthesizeTemplate(), then the template won't contain the // right constructs. if (this.props.generateBootstrapVersionRule ?? true) { - addBootstrapVersionRule(this.stack, MIN_BOOTSTRAP_STACK_VERSION, this.qualifier); + addBootstrapVersionRule(this.stack, MIN_BOOTSTRAP_STACK_VERSION, this.bootstrapStackVersionSsmParameter); } this.synthesizeStackTemplate(this.stack, session); @@ -390,7 +428,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { cloudFormationExecutionRoleArn: this._cloudFormationExecutionRoleArn, stackTemplateAssetObjectUrl: templateManifestUrl, requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, - bootstrapStackVersionSsmParameter: `/cdk-bootstrap/${this.qualifier}/version`, + bootstrapStackVersionSsmParameter: this.bootstrapStackVersionSsmParameter, additionalDependencies: [artifactId], }); } @@ -479,7 +517,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer { properties: { file: manifestFile, requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, - bootstrapStackVersionSsmParameter: `/cdk-bootstrap/${this.qualifier}/version`, + bootstrapStackVersionSsmParameter: this.bootstrapStackVersionSsmParameter, }, }); @@ -546,7 +584,7 @@ function stackLocationOrInstrinsics(stack: Stack) { * The CLI normally checks this, but in a pipeline the CLI is not involved * so we encode this rule into the template in a way that CloudFormation will check it. */ -function addBootstrapVersionRule(stack: Stack, requiredVersion: number, qualifier: string) { +function addBootstrapVersionRule(stack: Stack, requiredVersion: number, bootstrapStackVersionSsmParameter: string) { // Because of https://github.com/aws/aws-cdk/blob/master/packages/assert-internal/lib/synth-utils.ts#L74 // synthesize() may be called more than once on a stack in unit tests, and the below would break // if we execute it a second time. Guard against the constructs already existing. @@ -555,7 +593,7 @@ function addBootstrapVersionRule(stack: Stack, requiredVersion: number, qualifie 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.', - default: `/cdk-bootstrap/${qualifier}/version`, + default: bootstrapStackVersionSsmParameter, }); // There is no >= check in CloudFormation, so we have to check the number diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index e256f73231714..b32095233b80d 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -750,7 +750,7 @@ export class Stack extends CoreConstruct implements ITaggable { * Synthesizes the cloudformation template into a cloud assembly. * @internal */ - public _synthesizeTemplate(session: ISynthesisSession): void { + public _synthesizeTemplate(session: ISynthesisSession, lookupRoleArn?: string): void { // In principle, stack synthesis is delegated to the // StackSynthesis object. // @@ -777,7 +777,11 @@ export class Stack extends CoreConstruct implements ITaggable { fs.writeFileSync(outPath, JSON.stringify(template, undefined, 2)); for (const ctx of this._missingContext) { - builder.addMissing(ctx); + if (lookupRoleArn != null) { + builder.addMissing({ ...ctx, props: { ...ctx.props, lookupRoleArn } }); + } else { + builder.addMissing(ctx); + } } } diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index efe65f115ab55..737f376848c69 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -202,7 +202,7 @@ export class Stage extends CoreConstruct { } /** - * Options for assemly synthesis. + * Options for assembly synthesis. */ export interface StageSynthesisOptions { /** diff --git a/packages/@aws-cdk/core/lib/token.ts b/packages/@aws-cdk/core/lib/token.ts index e17227d0f8ef5..9b87a0792fa2b 100644 --- a/packages/@aws-cdk/core/lib/token.ts +++ b/packages/@aws-cdk/core/lib/token.ts @@ -191,6 +191,7 @@ export class Tokenization { scope: options.scope, resolver: options.resolver, preparing: (options.preparing ?? false), + removeEmpty: options.removeEmpty, }); } @@ -265,6 +266,13 @@ export interface ResolveOptions { * @default false */ readonly preparing?: boolean; + + /** + * Whether to remove undefined elements from arrays and objects when resolving. + * + * @default true + */ + readonly removeEmpty?: boolean; } /** diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index cf43b0a09c5aa..4a42bf3560a25 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -174,7 +174,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.23", "@types/lodash": "^4.14.170", @@ -183,7 +183,7 @@ "@types/sinon": "^9.0.11", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "fast-check": "^2.14.0", + "fast-check": "^2.16.0", "lodash": "^4.17.21", "nodeunit-shim": "0.0.0", "pkglint": "0.0.0", diff --git a/packages/@aws-cdk/core/test/app.test.ts b/packages/@aws-cdk/core/test/app.test.ts index 199b36dc87465..3bb178edef56a 100644 --- a/packages/@aws-cdk/core/test/app.test.ts +++ b/packages/@aws-cdk/core/test/app.test.ts @@ -1,7 +1,7 @@ import { ContextProvider } from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { nodeunitShim, Test } from 'nodeunit-shim'; -import { CfnResource, Construct, Stack, StackProps } from '../lib'; +import { CfnResource, Construct, DefaultStackSynthesizer, Stack, StackProps } from '../lib'; import { Annotations } from '../lib/annotations'; import { App, AppProps } from '../lib/app'; @@ -219,7 +219,7 @@ nodeunitShim({ } const assembly = withApp({}, app => { - new MyStack(app, 'MyStack'); + new MyStack(app, 'MyStack', { synthesizer: new DefaultStackSynthesizer() }); }); test.deepEqual(assembly.manifest.missing, [ @@ -227,6 +227,7 @@ nodeunitShim({ key: 'missing-context-key', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { + lookupRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}', account: '12345689012', region: 'ab-north-1', }, @@ -235,6 +236,7 @@ nodeunitShim({ key: 'missing-context-key-2', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { + lookupRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}', account: '12345689012', region: 'ab-south-1', }, diff --git a/packages/@aws-cdk/core/test/resource.test.ts b/packages/@aws-cdk/core/test/resource.test.ts index aa8fbe74575fc..4fbe41692756d 100644 --- a/packages/@aws-cdk/core/test/resource.test.ts +++ b/packages/@aws-cdk/core/test/resource.test.ts @@ -420,6 +420,37 @@ nodeunitShim({ test.done(); }, + 'addPropertyOverride() allows assigning an attribute of a different resource'(test: Test) { + // GIVEN + const stack = new Stack(); + const r1 = new CfnResource(stack, 'MyResource1', { type: 'AWS::Resource::Type' }); + const r2 = new CfnResource(stack, 'MyResource2', { type: 'AWS::Resource::Type' }); + + // WHEN + r2.addPropertyOverride('A', { + B: r1.getAtt('Arn'), + }); + + // THEN + test.deepEqual(toCloudFormation(stack), { + Resources: { + MyResource1: { + Type: 'AWS::Resource::Type', + }, + MyResource2: { + Type: 'AWS::Resource::Type', + Properties: { + A: { + B: { 'Fn::GetAtt': ['MyResource1', 'Arn'] }, + }, + }, + }, + }, + }); + + test.done(); + }, + 'addOverride(p, null) will assign an "null" value'(test: Test) { // GIVEN const stack = new Stack(); @@ -513,7 +544,7 @@ nodeunitShim({ test.done(); }, - 'addDeletionOverride(p) and addPropertyDeletionOverride(pp) are sugar `undefined`'(test: Test) { + 'addDeletionOverride(p) and addPropertyDeletionOverride(pp) are sugar for `undefined`'(test: Test) { // GIVEN const stack = new Stack(); @@ -904,4 +935,4 @@ class CustomizableResource extends CfnResource { /** * Because Resource is abstract */ -class TestResource extends Resource {} \ No newline at end of file +class TestResource extends Resource {} 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 73f8f185f06ba..2bd29743f6941 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 @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { nodeunitShim, Test } from 'nodeunit-shim'; -import { App, Aws, CfnResource, DefaultStackSynthesizer, FileAssetPackaging, Stack } from '../../lib'; +import { App, Aws, CfnResource, ContextProvider, DefaultStackSynthesizer, FileAssetPackaging, Stack } from '../../lib'; import { evaluateCFN } from '../evaluate-cfn'; const CFN_CONTEXT = { @@ -101,6 +101,56 @@ nodeunitShim({ test.done(); }, + 'customize version parameter'(test: Test) { + // GIVEN + const myapp = new App(); + + // WHEN + const mystack = new Stack(myapp, 'mystack', { + synthesizer: new DefaultStackSynthesizer({ + bootstrapStackVersionSsmParameter: 'stack-version-parameter', + }), + }); + + mystack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'file-asset-hash', + }); + + // THEN + const asm = myapp.synth(); + const manifestArtifact = getAssetManifest(asm); + + // THEN - the asset manifest has an SSM parameter entry + expect(manifestArtifact.bootstrapStackVersionSsmParameter).toEqual('stack-version-parameter'); + + test.done(); + }, + + 'generates missing context with the lookup role ARN as one of the missing context properties'(test: Test) { + // GIVEN + stack = new Stack(app, 'Stack2', { + synthesizer: new DefaultStackSynthesizer({ + generateBootstrapVersionRule: false, + }), + env: { + account: '111111111111', region: 'us-east-1', + }, + }); + ContextProvider.getValue(stack, { + provider: cxschema.ContextProvider.VPC_PROVIDER, + props: {}, + dummyValue: undefined, + }).value; + + // THEN + const assembly = app.synth(); + test.equal(assembly.manifest.missing![0].props.lookupRoleArn, 'arn:${AWS::Partition}:iam::111111111111:role/cdk-hnb659fds-lookup-role-111111111111-us-east-1'); + + test.done(); + }, + 'add file asset'(test: Test) { // WHEN const location = stack.synthesizer.addFileAsset({ @@ -223,7 +273,6 @@ nodeunitShim({ test.done(); }, - 'synthesis with bucketPrefix'(test: Test) { // GIVEN const myapp = new App(); diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index 693b60209d7a9..ccf5765dea13e 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -33,14 +33,18 @@ the actual handler. ```ts import { CustomResource } from '@aws-cdk/core'; import * as logs from '@aws-cdk/aws-logs'; +import * as iam from '@aws-cdk/aws-iam'; import * as cr from '@aws-cdk/custom-resources'; const onEvent = new lambda.Function(this, 'MyHandler', { /* ... */ }); +const myRole = new iam.Role(this, 'MyRole', { /* ... */ }); + const myProvider = new cr.Provider(this, 'MyProvider', { onEventHandler: onEvent, isCompleteHandler: isComplete, // optional async "waiter" - logRetention: logs.RetentionDays.ONE_DAY // default is INFINITE + logRetention: logs.RetentionDays.ONE_DAY, // default is INFINITE + role: myRole, // must be assumable by the `lambda.amazonaws.com` service principal }); new CustomResource(this, 'Resource1', { serviceToken: myProvider.serviceToken }); @@ -264,8 +268,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-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). +[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). 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/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index fe46a7dd7f0b5..8d99963627a4a 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import { Duration } from '@aws-cdk/core'; @@ -105,6 +106,15 @@ export interface ProviderProps { */ readonly securityGroups?: ec2.ISecurityGroup[]; + /** + * AWS Lambda execution role. + * + * The role that will be assumed by the AWS Lambda. + * Must be assumable by the 'lambda.amazonaws.com' service principal. + * + * @default - A default role will be created. + */ + readonly role?: iam.IRole; } /** @@ -135,6 +145,7 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider { private readonly vpc?: ec2.IVpc; private readonly vpcSubnets?: ec2.SubnetSelection; private readonly securityGroups?: ec2.ISecurityGroup[]; + private readonly role?: iam.IRole; constructor(scope: Construct, id: string, props: ProviderProps) { super(scope, id); @@ -152,6 +163,8 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider { this.vpcSubnets = props.vpcSubnets; this.securityGroups = props.securityGroups; + this.role = props.role; + const onEventFunction = this.createFunction(consts.FRAMEWORK_ON_EVENT_HANDLER_NAME); if (this.isCompleteHandler) { @@ -166,7 +179,6 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider { interval: retry.interval, maxAttempts: retry.maxAttempts, }); - // the on-event entrypoint is going to start the execution of the waiter onEventFunction.addEnvironment(consts.WAITER_STATE_MACHINE_ARN_ENV, waiterStateMachine.stateMachineArn); waiterStateMachine.grantStartExecution(onEventFunction); @@ -197,6 +209,7 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider { vpc: this.vpc, vpcSubnets: this.vpcSubnets, securityGroups: this.securityGroups, + role: this.role, }); fn.addEnvironment(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, this.onEventHandler.functionArn); diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index c93d01bc53e7e..38a1eaaf7dd14 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -75,7 +75,7 @@ "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", - "@types/aws-lambda": "^8.10.76", + "@types/aws-lambda": "^8.10.77", "@types/fs-extra": "^8.1.1", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", @@ -84,7 +84,7 @@ "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "fs-extra": "^9.1.0", - "nock": "^13.0.11", + "nock": "^13.1.0", "pkglint": "0.0.0", "sinon": "^9.2.4", "@aws-cdk/assert-internal": "0.0.0" diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts index 3b8ba5de5ada9..e9cfa0b5e6867 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import { Duration, Stack } from '@aws-cdk/core'; @@ -342,3 +343,57 @@ describe('log retention', () => { expect(stack).not.toHaveResource('Custom::LogRetention'); }); }); + +describe('role', () => { + it('uses custom role when present', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + role: new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + 'Fn::GetAtt': [ + 'MyRoleF48FFE04', + 'Arn', + ], + }, + }); + }); + + it('uses default role otherwise', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + 'Fn::GetAtt': [ + 'MyProviderframeworkonEventServiceRole8761E48D', + 'Arn', + ], + }, + }); + }); +}); diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 82aded1d69b4b..d59446e8b8b7e 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -632,6 +632,17 @@ $ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ aws://222222222222/us-east-2 ``` +If you only want to trust an account to do lookups (e.g, when your CDK application has a +`Vpc.fromLookup()` call), use the option `--trust-for-lookup`: + +```console +$ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ + [--profile admin-profile-2] \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \ + --trust-for-lookup 11111111111 \ + aws://222222222222/us-east-2 +``` + These command lines explained: * `npx`: means to use the CDK CLI from the current NPM install. If you are using @@ -647,6 +658,10 @@ These command lines explained: CDK applications into this account. In this case we indicate the Pipeline's account, but you could also use this for developer accounts (don't do that for production application accounts though!). +* `--trust-for-lookup`: similar to `--trust`, but gives a more limited set of permissions to the + trusted account, allowing it to only look up values, such as availability zones, EC2 images and + VPCs. Note that if you provide an account using `--trust`, that account can also do lookups. + So you only need to pass `--trust-for-lookup` if you need to use a different account. * `aws://222222222222/us-east-2`: the account and region we're bootstrapping. > **Security tip**: we recommend that you use administrative credentials to an @@ -800,6 +815,26 @@ After turning on `privilegedMode: true`, you will need to do a one-time manual c pipeline to get it going again (as with a broken 'synth' the pipeline will not be able to self update to the right state). +### S3 error: Access Denied + +Some constructs, such as EKS clusters, generate nested stacks. When CloudFormation tries +to deploy those stacks, it may fail with this error: + +```console +S3 error: Access Denied For more information check http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html +``` + +This happens because the pipeline is not self-mutating and, as a consequence, the `FileAssetX` +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', { + selfMutating: true, + ... +}); +``` + ## Current Limitations Limitations that we are aware of and will address: diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 126f6579c9e80..8923c9b0cf15d 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -161,6 +161,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-config": "0.0.0", + "@aws-cdk/aws-cur": "0.0.0", "@aws-cdk/aws-customerprofiles": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-datapipeline": "0.0.0", diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 7c69bcb43a3f3..b36c01e10969b 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -79,6 +79,7 @@ async function parseCommandLineArguments() { .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true }) .option('trust', { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true }) + .option('trust-for-lookup', { type: 'array', desc: 'The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true }) .option('cloudformation-execution-policies', { type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true }) .option('force', { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false }) .option('termination-protection', { type: 'boolean', default: undefined, desc: 'Toggle CloudFormation termination protection on the bootstrap stacks' }) @@ -279,6 +280,7 @@ async function initCommandLine() { qualifier: args.qualifier, publicAccessBlockConfiguration: args.publicAccessBlockConfiguration, trustedAccounts: arrayFromYargs(args.trust), + trustedAccountsForLookup: arrayFromYargs(args.trustForLookup), cloudFormationExecutionPolicies: arrayFromYargs(args.cloudformationExecutionPolicies), }, }); diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index d5787a258275d..888901a8c33bf 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -158,8 +158,9 @@ export class SDK implements ISDK { ...this.sdkOptions.assumeRoleCredentialsSourceDescription ? [`using ${this.sdkOptions.assumeRoleCredentialsSourceDescription}`] : [], - '(did you bootstrap the environment with the right \'--trust\'s?):', e.message, + '. Please make sure that this role exists in the account. If it doesn\'t exist, (re)-bootstrap the environment ' + + 'with the right \'--trust\', using the latest version of the CDK CLI.', ].join(' ')); } } diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index 5350524b0dae3..7877368710c5f 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -92,7 +92,10 @@ export class Bootstrapper { // templates doesn't seem to be able to express the conditions that we need // (can't use Fn::Join or reference Conditions) so we do it here instead. const trustedAccounts = params.trustedAccounts ?? splitCfnArray(current.parameters.TrustedAccounts); - info(`Trusted accounts: ${trustedAccounts.length > 0 ? trustedAccounts.join(', ') : '(none)'}`); + info(`Trusted accounts for deployment: ${trustedAccounts.length > 0 ? trustedAccounts.join(', ') : '(none)'}`); + + const trustedAccountsForLookup = params.trustedAccountsForLookup ?? splitCfnArray(current.parameters.TrustedAccountsForLookup); + info(`Trusted accounts for lookup: ${trustedAccountsForLookup.length > 0 ? trustedAccountsForLookup.join(', ') : '(none)'}`); const cloudFormationExecutionPolicies = params.cloudFormationExecutionPolicies ?? splitCfnArray(current.parameters.CloudFormationExecutionPolicies); if (trustedAccounts.length === 0 && cloudFormationExecutionPolicies.length === 0) { @@ -137,6 +140,7 @@ export class Bootstrapper { FileAssetsBucketKmsKeyId: kmsKeyId, // Empty array becomes empty string TrustedAccounts: trustedAccounts.join(','), + TrustedAccountsForLookup: trustedAccountsForLookup.join(','), CloudFormationExecutionPolicies: cloudFormationExecutionPolicies.join(','), Qualifier: params.qualifier, PublicAccessBlockConfiguration: params.publicAccessBlockConfiguration || params.publicAccessBlockConfiguration === undefined ? 'true' : 'false', diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index dd36739d4381d..10fc4fc8ff598 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -71,6 +71,13 @@ export interface BootstrappingParameters { */ readonly trustedAccounts?: string[]; + /** + * The list of AWS account IDs that are trusted to look up values in the environment being bootstrapped. + * + * @default - only the bootstrapped account can look up values in this environment + */ + readonly trustedAccountsForLookup?: string[]; + /** * The ARNs of the IAM managed policies that should be attached to the role performing CloudFormation deployments. * In most cases, this will be the AdministratorAccess policy. diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index b541401f930e7..eec900592e641 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -6,6 +6,11 @@ Parameters: stacks to this environment Default: '' Type: CommaDelimitedList + TrustedAccountsForLookup: + Description: List of AWS accounts that are trusted to look up values in this + environment + Default: '' + Type: CommaDelimitedList CloudFormationExecutionPolicies: Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role @@ -45,6 +50,13 @@ Conditions: - Fn::Join: - '' - Ref: TrustedAccounts + HasTrustedAccountsForLookup: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: TrustedAccountsForLookup HasCloudFormationExecutionPolicies: Fn::Not: - Fn::Equals: @@ -233,6 +245,57 @@ Resources: - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + LookupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccountsForLookup + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccountsForLookup + - Ref: AWS::NoValue + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} + Policies: + - PolicyDocument: + Statement: + - Action: + - ec2:DescribeVpcs + - ec2:DescribeAvailabilityZones + - ec2:DescribeSubnets + - ec2:DescribeRouteTables + - ec2:DescribeVpnGateways + - ec2:DescribeImages + - ec2:DescribeVpcEndpointServices + - ec2:DescribeSecurityGroups + - elasticloadbalancing:DescribeLoadBalancers + - elasticloadbalancing:DescribeTags + - elasticloadbalancing:DescribeListeners + - route53:ListHostedZonesByName + - route53:GetHostedZone + - ssm:GetParameter + Resource: "*" + Effect: Allow + Version: '2012-10-17' + PolicyName: + Fn::Sub: cdk-${Qualifier}-lookup-role-default-policy-${AWS::AccountId}-${AWS::Region} FilePublishingRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: @@ -279,6 +342,8 @@ Resources: - ecr:BatchCheckLayerAvailability - ecr:DescribeRepositories - ecr:DescribeImages + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer Resource: Fn::Sub: "${ContainerAssetsRepository.Arn}" Effect: Allow @@ -393,7 +458,7 @@ Resources: Type: String Name: Fn::Sub: '/cdk-bootstrap/${Qualifier}/version' - Value: '5' + Value: '6' Outputs: BucketName: Description: The name of the S3 bucket owned by the CDK toolkit stack diff --git a/packages/aws-cdk/lib/context-providers/ami.ts b/packages/aws-cdk/lib/context-providers/ami.ts index 050afdb0a1b5f..f49bbef114175 100644 --- a/packages/aws-cdk/lib/context-providers/ami.ts +++ b/packages/aws-cdk/lib/context-providers/ami.ts @@ -20,7 +20,8 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin { print(`Searching for AMI in ${account}:${region}`); debug(`AMI search parameters: ${JSON.stringify(args)}`); - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); const response = await ec2.describeImages({ Owners: args.owners, Filters: Object.entries(args.filters).map(([key, values]) => ({ diff --git a/packages/aws-cdk/lib/context-providers/availability-zones.ts b/packages/aws-cdk/lib/context-providers/availability-zones.ts index beaa651cf22ba..1f3d656b17c11 100644 --- a/packages/aws-cdk/lib/context-providers/availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/availability-zones.ts @@ -15,7 +15,8 @@ export class AZContextProviderPlugin implements ContextProviderPlugin { const region = args.region; const account = args.account; debug(`Reading AZs for ${account}:${region}`); - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); const response = await ec2.describeAvailabilityZones().promise(); if (!response.AvailabilityZones) { return []; } const azs = response.AvailabilityZones.filter(zone => zone.State === 'available').map(zone => zone.ZoneName); diff --git a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts index f9099154e2407..68147799ff517 100644 --- a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts @@ -10,12 +10,13 @@ export class EndpointServiceAZContextProviderPlugin implements ContextProviderPl constructor(private readonly aws: SdkProvider) { } - public async getValue(args: {[key: string]: any}) { + public async getValue(args: { [key: string]: any }) { const region = args.region; const account = args.account; const serviceName = args.serviceName; debug(`Reading AZs for ${account}:${region}:${serviceName}`); - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); const response = await ec2.describeVpcEndpointServices({ ServiceNames: [serviceName] }).promise(); // expect a service in the response diff --git a/packages/aws-cdk/lib/context-providers/hosted-zones.ts b/packages/aws-cdk/lib/context-providers/hosted-zones.ts index 8d959c5c1e142..909475eaccc06 100644 --- a/packages/aws-cdk/lib/context-providers/hosted-zones.ts +++ b/packages/aws-cdk/lib/context-providers/hosted-zones.ts @@ -17,7 +17,8 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { } const domainName = args.domainName; debug(`Reading hosted zone ${account}:${region}:${domainName}`); - const r53 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).route53(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const r53 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).route53(); const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); if (!response.HostedZones) { throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts index e60ad6066d280..a87183d3b4e46 100644 --- a/packages/aws-cdk/lib/context-providers/index.ts +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -13,7 +13,7 @@ import { SecurityGroupContextProviderPlugin } from './security-groups'; import { SSMContextProviderPlugin } from './ssm-parameters'; import { VpcNetworkContextProviderPlugin } from './vpcs'; -type ProviderConstructor = (new (sdk: SdkProvider) => ContextProviderPlugin); +type ProviderConstructor = (new (sdk: SdkProvider, lookupRoleArn?: string) => ContextProviderPlugin); export type ProviderMap = {[name: string]: ProviderConstructor}; /** diff --git a/packages/aws-cdk/lib/context-providers/load-balancers.ts b/packages/aws-cdk/lib/context-providers/load-balancers.ts index 26f00c7746fe3..a36e29c0bec58 100644 --- a/packages/aws-cdk/lib/context-providers/load-balancers.ts +++ b/packages/aws-cdk/lib/context-providers/load-balancers.ts @@ -12,7 +12,8 @@ export class LoadBalancerContextProviderPlugin implements ContextProviderPlugin } async getValue(query: cxschema.LoadBalancerContextQuery): Promise { - const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading)).elbv2(); + const options = { assumeRoleArn: query.lookupRoleArn }; + const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading, options)).elbv2(); if (!query.loadBalancerArn && !query.loadBalancerTags) { throw new Error('The load balancer lookup query must specify either `loadBalancerArn` or `loadBalancerTags`'); @@ -57,7 +58,8 @@ export class LoadBalancerListenerContextProviderPlugin implements ContextProvide } async getValue(query: LoadBalancerListenerQuery): Promise { - const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading)).elbv2(); + const options = { assumeRoleArn: query.lookupRoleArn }; + const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading, options)).elbv2(); if (!query.listenerArn && !query.loadBalancerArn && !query.loadBalancerTags) { throw new Error('The load balancer listener query must specify at least one of: `listenerArn`, `loadBalancerArn` or `loadBalancerTags`'); diff --git a/packages/aws-cdk/lib/context-providers/security-groups.ts b/packages/aws-cdk/lib/context-providers/security-groups.ts index e8f464128b68d..7edde696fba45 100644 --- a/packages/aws-cdk/lib/context-providers/security-groups.ts +++ b/packages/aws-cdk/lib/context-providers/security-groups.ts @@ -12,7 +12,8 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin const account: string = args.account!; const region: string = args.region!; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); const response = await ec2.describeSecurityGroups({ GroupIds: [args.securityGroupId], diff --git a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts index 79558a89ff4bf..150f4f14dad40 100644 --- a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts +++ b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts @@ -21,7 +21,7 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { const parameterName = args.parameterName; debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); - const response = await this.getSsmParameterValue(account, region, parameterName); + const response = await this.getSsmParameterValue(account, region, parameterName, args.lookupRoleArn); if (!response.Parameter || response.Parameter.Value === undefined) { throw new Error(`SSM parameter not available in account ${account}, region ${region}: ${parameterName}`); } @@ -33,13 +33,16 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { * @param account the account in which the SSM Parameter is expected to be. * @param region the region in which the SSM Parameter is expected to be. * @param parameterName the name of the SSM Parameter + * @param lookupRoleArn the ARN of the lookup role. * * @returns the result of the ``GetParameter`` operation. * * @throws Error if a service error (other than ``ParameterNotFound``) occurs. */ - private async getSsmParameterValue(account: string, region: string, parameterName: string): Promise { - const ssm = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ssm(); + private async getSsmParameterValue(account: string, region: string, parameterName: string, lookupRoleArn?: string) + : Promise { + const options = { assumeRoleArn: lookupRoleArn }; + const ssm = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ssm(); try { return await ssm.getParameter({ Name: parameterName }).promise(); } catch (e) { diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts index 33e2e09ff9499..9b37eadee7aef 100644 --- a/packages/aws-cdk/lib/context-providers/vpcs.ts +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -14,7 +14,8 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { const account: string = args.account!; const region: string = args.region!; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); + const options = { assumeRoleArn: args.lookupRoleArn }; + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); const vpcId = await this.findVpc(ec2, args); diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index aa6ec027a38ca..a21c0922a802e 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -39,7 +39,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/core": "0.0.0", - "@octokit/rest": "^18.5.3", + "@octokit/rest": "^18.5.6", "@types/archiver": "^5.1.0", "@types/fs-extra": "^8.1.1", "@types/glob": "^7.1.3", @@ -59,7 +59,7 @@ "jest": "^26.6.3", "make-runnable": "^1.3.9", "mockery": "^2.1.0", - "nock": "^13.0.11", + "nock": "^13.1.0", "pkglint": "0.0.0", "sinon": "^9.2.4", "ts-jest": "^26.5.6", diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts index 639f0a6d759bc..bf6f6e836a79a 100644 --- a/packages/aws-cdk/test/api/bootstrap2.test.ts +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -123,6 +123,21 @@ describe('Bootstrapping v2', () => { })); }); + test('passing trusted accounts for lookup generates the correct stack parameter', async () => { + await bootstrapper.bootstrapEnvironment(env, sdk, { + parameters: { + trustedAccountsForLookup: ['123456789012'], + cloudFormationExecutionPolicies: ['aws://foo'], + }, + }); + + expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ + parameters: expect.objectContaining({ + TrustedAccountsForLookup: '123456789012', + }), + })); + }); + test('allow adding trusted account if there was already a policy on the stack', async () => { // GIVEN mockTheToolkitInfo({ diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index c7dfbbcfeb45c..0e9f0bd01e366 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -312,7 +312,7 @@ describe('with intercepted network calls', () => { }); // THEN - error message contains both a helpful hint and the underlying AssumeRole message - await expect(promise).rejects.toThrow('did you bootstrap'); + await expect(promise).rejects.toThrow('(re)-bootstrap the environment'); await expect(promise).rejects.toThrow('doesnotexist.role.arn'); }); diff --git a/packages/awslint/package.json b/packages/awslint/package.json index 3ddbccad9cdb5..9f764e6d59108 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -16,11 +16,11 @@ "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.29.0", + "@jsii/spec": "^1.30.0", "camelcase": "^6.2.0", "colors": "^1.4.0", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.29.0", + "jsii-reflect": "^1.30.0", "yargs": "^16.2.0" }, "devDependencies": { @@ -29,13 +29,13 @@ "@types/yargs": "^15.0.13", "pkglint": "0.0.0", "typescript": "~3.9.9", - "@typescript-eslint/eslint-plugin": "^4.25.0", - "@typescript-eslint/parser": "^4.25.0", - "eslint": "^7.27.0", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", + "eslint": "^7.28.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-cdk": "0.0.0", - "eslint-plugin-import": "^2.23.3", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.3.6", "jest": "^26.6.3" }, diff --git a/packages/cdk-assets/lib/private/handlers/container-images.ts b/packages/cdk-assets/lib/private/handlers/container-images.ts index a3b6756ecb18d..1d2b5bedee38e 100644 --- a/packages/cdk-assets/lib/private/handlers/container-images.ts +++ b/packages/cdk-assets/lib/private/handlers/container-images.ts @@ -19,11 +19,11 @@ export class ContainerImageAssetHandler implements IAssetHandler { public async publish(): Promise { const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws); const ecr = await this.host.aws.ecrClient(destination); - const account = (await this.host.aws.discoverCurrentAccount()).accountId; + const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId; const repoUri = await repositoryUri(ecr, destination.repositoryName); if (!repoUri) { - throw new Error(`No ECR repository named '${destination.repositoryName}' in account ${account}. Is this account bootstrapped?`); + throw new Error(`No ECR repository named '${destination.repositoryName}' in account ${await account()}. Is this account bootstrapped?`); } const imageUri = `${repoUri}:${destination.imageTag}`; diff --git a/packages/cdk-assets/test/docker-images.test.ts b/packages/cdk-assets/test/docker-images.test.ts index 3b608a1e63ffe..8dc4f70b7726a 100644 --- a/packages/cdk-assets/test/docker-images.test.ts +++ b/packages/cdk-assets/test/docker-images.test.ts @@ -104,6 +104,18 @@ describe('with a complete manifest', () => { })); }); + test('Displays an error if the ECR repository cannot be found', async () => { + aws.mockEcr.describeImages = mockedApiFailure('RepositoryNotFoundException', 'Repository not Found'); + + await expect(pub.publish()).rejects.toThrow('Error publishing: Repository not Found'); + }); + + test('successful run does not need to query account ID', async () => { + aws.mockEcr.describeImages = mockedApiResult({ /* No error == image exists */ }); + await pub.publish(); + expect(aws.discoverCurrentAccount).not.toHaveBeenCalled(); + }); + test('upload docker image if not uploaded yet but exists locally', async () => { aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist'); aws.mockEcr.getAuthorizationToken = mockedApiResult({ diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index eb248837815bc..f7ef3a582e1da 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -26,7 +26,7 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.29.0", + "codemaker": "^1.30.0", "yaml": "1.10.2" }, "devDependencies": { diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 52ed71e1f050e..fcd346e420200 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -79,6 +79,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-config": "0.0.0", + "@aws-cdk/aws-cur": "0.0.0", "@aws-cdk/aws-customerprofiles": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-datapipeline": "0.0.0", @@ -227,7 +228,7 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.29.0", + "jsii-reflect": "^1.30.0", "jsonschema": "^1.4.0", "yaml": "1.10.2", "yargs": "^16.2.0" @@ -238,7 +239,7 @@ "@types/yaml": "1.9.7", "@types/yargs": "^15.0.13", "jest": "^26.6.3", - "jsii": "^1.29.0" + "jsii": "^1.30.0" }, "keywords": [ "aws", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index a2f21672d76cf..e578bd26d353b 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -162,6 +162,7 @@ "@aws-cdk/aws-codestarnotifications": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-config": "0.0.0", + "@aws-cdk/aws-cur": "0.0.0", "@aws-cdk/aws-customerprofiles": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-datapipeline": "0.0.0", diff --git a/patches/jsii-rosetta+1.28.0.patch b/patches/jsii-rosetta+1.28.0.patch deleted file mode 100644 index 89dc133e267ca..0000000000000 --- a/patches/jsii-rosetta+1.28.0.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/node_modules/jsii-rosetta/lib/commands/extract.js b/node_modules/jsii-rosetta/lib/commands/extract.js -index e695ea9..539038e 100644 ---- a/node_modules/jsii-rosetta/lib/commands/extract.js -+++ b/node_modules/jsii-rosetta/lib/commands/extract.js -@@ -104,7 +104,8 @@ exports.singleThreadedTranslateAll = singleThreadedTranslateAll; - async function workerBasedTranslateAll(worker, snippets, includeCompilerDiagnostics) { - // Use about half the advertised cores because hyperthreading doesn't seem to help that - // much (on my machine, using more than half the cores actually makes it slower). -- const N = Math.max(1, Math.ceil(os.cpus().length / 2)); -+ // Cap to a reasonable top-level limit to prevent thrash on machines with many, many cores. -+ const N = Math.min(16, Math.max(1, Math.ceil(os.cpus().length / 2))); - const snippetArr = Array.from(snippets); - const groups = util_1.divideEvenly(N, snippetArr); - logging.info(`Translating ${snippetArr.length} snippets using ${groups.length} workers`); diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index a53f7158034d2..4ea1b81de257c 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -40,22 +40,22 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.25.0", - "@typescript-eslint/parser": "^4.25.0", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", "awslint": "0.0.0", "colors": "^1.4.0", - "eslint": "^7.27.0", + "eslint": "^7.28.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-cdk": "0.0.0", - "eslint-plugin-import": "^2.23.3", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.3.6", "fs-extra": "^9.1.0", "jest": "^26.6.3", "jest-junit": "^11.1.0", - "jsii": "^1.29.0", - "jsii-pacmak": "^1.29.0", - "jsii-reflect": "^1.29.0", + "jsii": "^1.30.0", + "jsii-pacmak": "^1.30.0", + "jsii-reflect": "^1.30.0", "markdownlint-cli": "^0.27.1", "nodeunit": "^0.11.3", "nyc": "^15.1.0", diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index b487b3f04c021..0a0b978a48e5c 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -30,7 +30,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.29.0", + "codemaker": "^1.30.0", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.1.0", "yargs": "^16.2.0" diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index cc1037445d739..5c7d9f515f5b2 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -12,7 +12,7 @@ "build+test": "npm run build && npm test" }, "devDependencies": { - "@types/eslint": "^7.2.11", + "@types/eslint": "^7.2.13", "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.23", "@types/node": "^10.17.60", @@ -22,8 +22,8 @@ "typescript": "~3.9.9" }, "dependencies": { - "@typescript-eslint/parser": "^4.25.0", - "eslint": "^7.27.0", + "@typescript-eslint/parser": "^4.26.0", + "eslint": "^7.28.0", "fs-extra": "^9.1.0" }, "jest": { diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index d7494a372c1c7..07d6ad69c552e 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -1707,6 +1707,31 @@ export class AwsCdkLibReadmeMatchesCore extends ValidationRule { } } +/** + * Enforces that the aws-cdk's package.json on the V2 branch does not have the "main" + * and "types" keys filled. + */ +export class CdkCliV2MissesMainAndTypes extends ValidationRule { + public readonly name = 'aws-cdk/cli/v2/package.json/main'; + + public validate(pkg: PackageJson): void { + // this rule only applies to the CLI + if (pkg.json.name !== 'aws-cdk') { return; } + // this only applies to V2 + if (cdkMajorVersion() === 1) { return; } + + if (pkg.json.main || pkg.json.types) { + pkg.report({ + ruleName: this.name, + message: 'The package.json file for the aws-cdk CLI package in V2 cannot have "main" and "types" keys', + fix: () => { + delete pkg.json.main; + delete pkg.json.types; + }, + }); + } + } +} /** * Determine whether this is a JSII package @@ -1794,4 +1819,4 @@ function isIncludedInMonolith(pkg: PackageJson): boolean { return false; } return true; -} \ No newline at end of file +} diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index e3f1e6c55b676..bd3cf5034b6c3 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -40,13 +40,13 @@ "@types/jest": "^26.0.23", "@types/semver": "^7.3.6", "@types/yargs": "^15.0.13", - "@typescript-eslint/eslint-plugin": "^4.25.0", - "@typescript-eslint/parser": "^4.25.0", - "eslint": "^7.27.0", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", + "eslint": "^7.28.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-cdk": "0.0.0", - "eslint-plugin-import": "^2.23.3", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-jest": "^24.3.6", "jest": "^26.6.3", "typescript": "~3.9.9" diff --git a/version.v1.json b/version.v1.json index 58a27f352558f..986a6e0396196 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.107.0" + "version": "1.108.0" } diff --git a/yarn.lock b/yarn.lock index 60b0cc2629c51..a66d061e6ebec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,25 +37,25 @@ dependencies: "@babel/highlight" "^7.12.13" -"@babel/compat-data@^7.13.15": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.0.tgz#a901128bce2ad02565df95e6ecbf195cf9465919" - integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q== +"@babel/compat-data@^7.14.4": + version "7.14.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.4.tgz#45720fe0cecf3fd42019e1d12cc3d27fadc98d58" + integrity sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ== "@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.0.tgz#47299ff3ec8d111b493f1a9d04bf88c04e728d88" - integrity sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw== + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.3.tgz#5395e30405f0776067fbd9cf0884f15bfb770a38" + integrity sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg== dependencies: "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.0" + "@babel/generator" "^7.14.3" "@babel/helper-compilation-targets" "^7.13.16" - "@babel/helper-module-transforms" "^7.14.0" + "@babel/helper-module-transforms" "^7.14.2" "@babel/helpers" "^7.14.0" - "@babel/parser" "^7.14.0" + "@babel/parser" "^7.14.3" "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -63,33 +63,33 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.14.0", "@babel/generator@^7.4.0": - version "7.14.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.1.tgz#1f99331babd65700183628da186f36f63d615c93" - integrity sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ== +"@babel/generator@^7.14.2", "@babel/generator@^7.14.3", "@babel/generator@^7.4.0": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91" + integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA== dependencies: - "@babel/types" "^7.14.1" + "@babel/types" "^7.14.2" jsesc "^2.5.1" source-map "^0.5.0" "@babel/helper-compilation-targets@^7.13.16": - version "7.13.16" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz#6e91dccf15e3f43e5556dffe32d860109887563c" - integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA== + version "7.14.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz#33ebd0ffc34248051ee2089350a929ab02f2a516" + integrity sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA== dependencies: - "@babel/compat-data" "^7.13.15" + "@babel/compat-data" "^7.14.4" "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" + browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-function-name@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" - integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== +"@babel/helper-function-name@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz#397688b590760b6ef7725b5f0860c82427ebaac2" + integrity sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ== dependencies: "@babel/helper-get-function-arity" "^7.12.13" "@babel/template" "^7.12.13" - "@babel/types" "^7.12.13" + "@babel/types" "^7.14.2" "@babel/helper-get-function-arity@^7.12.13": version "7.12.13" @@ -112,10 +112,10 @@ dependencies: "@babel/types" "^7.13.12" -"@babel/helper-module-transforms@^7.14.0": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz#8fcf78be220156f22633ee204ea81f73f826a8ad" - integrity sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw== +"@babel/helper-module-transforms@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz#ac1cc30ee47b945e3e0c4db12fa0c5389509dfe5" + integrity sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA== dependencies: "@babel/helper-module-imports" "^7.13.12" "@babel/helper-replace-supers" "^7.13.12" @@ -123,8 +123,8 @@ "@babel/helper-split-export-declaration" "^7.12.13" "@babel/helper-validator-identifier" "^7.14.0" "@babel/template" "^7.12.13" - "@babel/traverse" "^7.14.0" - "@babel/types" "^7.14.0" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" "@babel/helper-optimise-call-expression@^7.12.13": version "7.12.13" @@ -139,14 +139,14 @@ integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== "@babel/helper-replace-supers@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" - integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== + version "7.14.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz#b2ab16875deecfff3ddfcd539bc315f72998d836" + integrity sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ== dependencies: "@babel/helper-member-expression-to-functions" "^7.13.12" "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.12" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.4" "@babel/helper-simple-access@^7.13.12": version "7.13.12" @@ -190,10 +190,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.0", "@babel/parser@^7.4.3": - version "7.14.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.1.tgz#1bd644b5db3f5797c4479d89ec1817fe02b84c47" - integrity sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.2", "@babel/parser@^7.14.3", "@babel/parser@^7.4.3": + version "7.14.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.4.tgz#a5c560d6db6cd8e6ed342368dea8039232cbab18" + integrity sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -288,24 +288,24 @@ "@babel/parser" "^7.12.13" "@babel/types" "^7.12.13" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.4.3": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.0.tgz#cea0dc8ae7e2b1dec65f512f39f3483e8cc95aef" - integrity sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.4.3": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" + integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== dependencies: "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.14.0" - "@babel/helper-function-name" "^7.12.13" + "@babel/generator" "^7.14.2" + "@babel/helper-function-name" "^7.14.2" "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.14.0" - "@babel/types" "^7.14.0" + "@babel/parser" "^7.14.2" + "@babel/types" "^7.14.2" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.12", "@babel/types@^7.14.0", "@babel/types@^7.14.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0": - version "7.14.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.1.tgz#095bd12f1c08ab63eff6e8f7745fa7c9cc15a9db" - integrity sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA== +"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.12", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.14.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0": + version "7.14.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.4.tgz#bfd6980108168593b38b3eb48a24aa026b919bc0" + integrity sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw== dependencies: "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" @@ -328,15 +328,15 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@eslint/eslintrc@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" - integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ== +"@eslint/eslintrc@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" + integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" - globals "^12.1.0" + globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" @@ -530,10 +530,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jsii/spec@^1.29.0": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.29.0.tgz#2378bbacd94e0159c6344c1556af0129e4d3e2f4" - integrity sha512-Y0ouCaYVPy7KjQ8di6Hu4xizKYp4klqqDf08BaEgqd38TzqBuO0abgcd9OJFUQyoDnCCWTdGBfqTo2xfvW9Pbw== +"@jsii/spec@^1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.30.0.tgz#e5b2381b2be0b9c0839190f9f45d0a038654c73d" + integrity sha512-oXIwvZyHHc/TrwA/3pzQ3gkqBe916EWBvaexNI3rnKZujlHZT4vVVHMCjQ/kUJhcR0GEaahvwlNhiPTu6roC2g== dependencies: jsonschema "^1.4.0" @@ -1208,25 +1208,25 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@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" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@nodelib/fs.stat" "2.0.4" + "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@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" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" + integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@npmcli/ci-detect@^1.0.0": @@ -1316,7 +1316,7 @@ is-plain-object "^5.0.0" universal-user-agent "^6.0.0" -"@octokit/graphql@^4.3.1": +"@octokit/graphql@^4.3.1", "@octokit/graphql@^4.5.8": version "4.6.2" resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.2.tgz#ec44abdfa87f2b9233282136ae33e4ba446a04e7" integrity sha512-WmsIR1OzOr/3IqfG9JIczI8gMJUMzzyx5j0XXQ4YihHtKlQc+u35VpVoOXhlKAlaBntvry1WpAzPl/a+s3n89Q== @@ -1325,19 +1325,10 @@ "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" -"@octokit/graphql@^4.5.8": - version "4.6.1" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.1.tgz#f975486a46c94b7dbe58a0ca751935edc7e32cc9" - integrity sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA== - dependencies: - "@octokit/request" "^5.3.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-7.0.0.tgz#0f6992db9854af15eca77d71ab0ec7fad2f20411" - integrity sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw== +"@octokit/openapi-types@^7.2.3": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-7.2.3.tgz#a7105796db9b85d25d3feba9a1785a124c7803e4" + integrity sha512-V1ycxkR19jqbIl3evf2RQiMRBvTNRi+Iy9h20G5OP5dPfEF6GJ1DPlUeiZRxo2HJxRr+UA4i0H1nn4btBDPFrw== "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" @@ -1371,12 +1362,12 @@ "@octokit/types" "^2.0.1" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.1.tgz#631b8d4edc6798b03489911252a25f2a4e58c594" - integrity sha512-vvWbPtPqLyIzJ7A4IPdTl+8IeuKAwMJ4LjvmqWOOdfSuqWQYZXq2CEd0hsnkidff2YfKlguzujHs/reBdAx8Sg== +"@octokit/plugin-rest-endpoint-methods@5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.3.1.tgz#deddce769b4ec3179170709ab42e4e9e6195aaa9" + integrity sha512-3B2iguGmkh6bQQaVOtCsS0gixrz8Lg0v4JuXPqBcFqLKuJtxAUf3K88RxMEf/naDOI73spD+goJ/o7Ie7Cvdjg== dependencies: - "@octokit/types" "^6.13.1" + "@octokit/types" "^6.16.2" deprecation "^2.3.1" "@octokit/request-error@^1.0.2": @@ -1398,13 +1389,13 @@ once "^1.4.0" "@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.4.12": - version "5.4.15" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128" - integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag== + version "5.5.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.5.0.tgz#6588c532255b8e71886cefa0d2b64b4ad73bf18c" + integrity sha512-jxbMLQdQ3heFMZUaTLSCqcKs2oAHEYh7SnLLXyxbZmlULExZ/RXai7QUWWFKowcGGPlCZuKTZg0gSKHWrfYEoQ== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.7.1" + "@octokit/types" "^6.16.1" is-plain-object "^5.0.0" node-fetch "^2.6.1" universal-user-agent "^6.0.0" @@ -1431,15 +1422,15 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^18.1.0", "@octokit/rest@^18.5.3": - version "18.5.3" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.3.tgz#6a2e6006a87ebbc34079c419258dd29ec9ff659d" - integrity sha512-KPAsUCr1DOdLVbZJgGNuE/QVLWEaVBpFQwDAz/2Cnya6uW2wJ/P5RVGk0itx7yyN1aGa8uXm2pri4umEqG1JBA== +"@octokit/rest@^18.1.0", "@octokit/rest@^18.5.6": + version "18.5.6" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.6.tgz#8c9a7c9329c7bbf478af20df78ddeab0d21f6d89" + integrity sha512-8HdG6ZjQdZytU6tCt8BQ2XLC7EJ5m4RrbyU/EARSkAM1/HP3ceOzMG/9atEfe17EDMer3IVdHWLedz2wDi73YQ== dependencies: "@octokit/core" "^3.2.3" "@octokit/plugin-paginate-rest" "^2.6.2" "@octokit/plugin-request-log" "^1.0.2" - "@octokit/plugin-rest-endpoint-methods" "5.0.1" + "@octokit/plugin-rest-endpoint-methods" "5.3.1" "@octokit/types@^2.0.0", "@octokit/types@^2.0.1": version "2.16.2" @@ -1448,12 +1439,12 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.13.1", "@octokit/types@^6.7.1": - version "6.14.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.14.2.tgz#64c9457f38fb8522bdbba3c8cc814590a2d61bf5" - integrity sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA== +"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.16.1", "@octokit/types@^6.16.2": + version "6.16.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.16.2.tgz#62242e0565a3eb99ca2fd376283fe78b4ea057b4" + integrity sha512-wWPSynU4oLy3i4KGyk+J1BLwRKyoeW2TwRHgwbDz17WtVFzSK2GOErGliruIx8c+MaYtHSYTx36DSmLNoNbtgA== dependencies: - "@octokit/openapi-types" "^7.0.0" + "@octokit/openapi-types" "^7.2.3" "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": version "1.8.3" @@ -1495,10 +1486,10 @@ dependencies: "@types/glob" "*" -"@types/aws-lambda@^8.10.76": - version "8.10.76" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.76.tgz#a20191677f1f5e32fe1f26739b1d6fbbea9cf636" - integrity sha512-lCTyeRm3NWqSwDnoji0z82Pl0tsOpr1p+33AiNeidgarloWXh3wdiVRUuxEa+sY9S5YLOYGz5X3N3Zvpibvm5w== +"@types/aws-lambda@^8.10.77": + version "8.10.77" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.77.tgz#04c4e3a06ab5552f2fa80816f8adca54b6bb9671" + integrity sha512-n0EMFJU/7u3KvHrR83l/zrKOVURXl5pUJPNED/Bzjah89QKCHwCiKCBoVUXRwTGRfCYGIDdinJaAlKDHZdp/Ng== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.14" @@ -1533,18 +1524,18 @@ dependencies: "@babel/types" "^7.3.0" -"@types/eslint@^7.2.11": - version "7.2.11" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.11.tgz#180b58f5bb7d7376e39d22496e2b08901aa52fd2" - integrity sha512-WYhv//5K8kQtsSc9F1Kn2vHzhYor6KpwPbARH7hwYe3C3ETD0EVx/3P5qQybUoaBEuUa9f/02JjBiXFWalYUmw== +"@types/eslint@^7.2.13": + version "7.2.13" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.13.tgz#e0ca7219ba5ded402062ad6f926d491ebb29dd53" + integrity sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*": - version "0.0.47" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" - integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== + version "0.0.48" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.48.tgz#18dc8091b285df90db2f25aa7d906cfc394b7f74" + integrity sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew== "@types/fs-extra@^8.1.1": version "8.1.1" @@ -1588,9 +1579,9 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" - integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" @@ -1602,7 +1593,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.7": +"@types/json-schema@*", "@types/json-schema@^7.0.7": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -1653,15 +1644,10 @@ resolved "https://registry.yarnpkg.com/@types/mockery/-/mockery-1.4.29.tgz#9ba22df37f07e3780fff8531d1a38e633f9457a5" integrity sha1-m6It838H43gP/4Ux0aOOYz+UV6U= -"@types/node@*": - version "15.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.2.tgz#51e9c0920d1b45936ea04341aa3e2e58d339fb67" - integrity sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA== - -"@types/node@>= 8": - version "15.6.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.1.tgz#32d43390d5c62c5b6ec486a9bc9c59544de39a08" - integrity sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA== +"@types/node@*", "@types/node@>= 8": + version "15.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2" + integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw== "@types/node@^10.17.60": version "10.17.60" @@ -1669,9 +1655,9 @@ integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== "@types/node@^14.14.33": - version "14.14.44" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.44.tgz#df7503e6002847b834371c004b372529f3f85215" - integrity sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA== + version "14.17.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.2.tgz#1e94476db57ec93a372c7f7d29aa5707cfb92339" + integrity sha512-sld7b/xmFum66AAKuz/rp/CUO8+98fMpyQ3SBfzzBNGMd/1iHBTAg9oyAvcYlAj46bpc74r91jSw2iFdnx29nw== "@types/nodeunit@^0.0.31": version "0.0.31" @@ -1787,120 +1773,74 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== -"@typescript-eslint/eslint-plugin@^4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz#d82657b6ab4caa4c3f888ff923175fadc2f31f2a" - integrity sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ== +"@typescript-eslint/eslint-plugin@^4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.0.tgz#12bbd6ebd5e7fabd32e48e1e60efa1f3554a3242" + integrity sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg== dependencies: - "@typescript-eslint/experimental-utils" "4.25.0" - "@typescript-eslint/scope-manager" "4.25.0" - debug "^4.1.1" + "@typescript-eslint/experimental-utils" "4.26.0" + "@typescript-eslint/scope-manager" "4.26.0" + debug "^4.3.1" functional-red-black-tree "^1.0.1" - lodash "^4.17.15" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz#b2febcfa715d2c1806fd5f0335193a6cd270df54" - integrity sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.25.0" - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/typescript-estree" "4.25.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/experimental-utils@^4.0.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.1.tgz#3938a5c89b27dc9a39b5de63a62ab1623ab27497" - integrity sha512-svYlHecSMCQGDO2qN1v477ax/IDQwWhc7PRBiwAdAMJE7GXk5stF4Z9R/8wbRkuX/5e9dHqbIWxjeOjckK3wLQ== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.22.1" - "@typescript-eslint/types" "4.22.1" - "@typescript-eslint/typescript-estree" "4.22.1" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.25.0.tgz#6b2cb6285aa3d55bfb263c650739091b0f19aceb" - integrity sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg== - dependencies: - "@typescript-eslint/scope-manager" "4.25.0" - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/typescript-estree" "4.25.0" - debug "^4.1.1" - -"@typescript-eslint/scope-manager@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.22.1.tgz#5bb357f94f9cd8b94e6be43dd637eb73b8f355b4" - integrity sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g== - dependencies: - "@typescript-eslint/types" "4.22.1" - "@typescript-eslint/visitor-keys" "4.22.1" - -"@typescript-eslint/scope-manager@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz#9d86a5bcc46ef40acd03d85ad4e908e5aab8d4ca" - integrity sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w== - dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - -"@typescript-eslint/types@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.1.tgz#bf99c6cec0b4a23d53a61894816927f2adad856a" - integrity sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw== - -"@typescript-eslint/types@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.25.0.tgz#0e444a5c5e3c22d7ffa5e16e0e60510b3de5af87" - integrity sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ== - -"@typescript-eslint/typescript-estree@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.1.tgz#dca379eead8cdfd4edc04805e83af6d148c164f9" - integrity sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A== - dependencies: - "@typescript-eslint/types" "4.22.1" - "@typescript-eslint/visitor-keys" "4.22.1" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" + lodash "^4.17.21" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz#942e4e25888736bff5b360d9b0b61e013d0cfa25" - integrity sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg== +"@typescript-eslint/experimental-utils@4.26.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.0.tgz#ba7848b3f088659cdf71bce22454795fc55be99a" + integrity sha512-TH2FO2rdDm7AWfAVRB5RSlbUhWxGVuxPNzGT7W65zVfl8H/WeXTk1e69IrcEVsBslrQSTDKQSaJD89hwKrhdkw== dependencies: - "@typescript-eslint/types" "4.25.0" - "@typescript-eslint/visitor-keys" "4.25.0" - debug "^4.1.1" - globby "^11.0.1" + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.26.0" + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/typescript-estree" "4.26.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.26.0.tgz#31b6b732c9454f757b020dab9b6754112aa5eeaf" + integrity sha512-b4jekVJG9FfmjUfmM4VoOItQhPlnt6MPOBUL0AQbiTmm+SSpSdhHYlwayOm4IW9KLI/4/cRKtQCmDl1oE2OlPg== + dependencies: + "@typescript-eslint/scope-manager" "4.26.0" + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/typescript-estree" "4.26.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.26.0.tgz#60d1a71df162404e954b9d1c6343ff3bee496194" + integrity sha512-G6xB6mMo4xVxwMt5lEsNTz3x4qGDt0NSGmTBNBPJxNsrTXJSm21c6raeYroS2OwQsOyIXqKZv266L/Gln1BWqg== + dependencies: + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/visitor-keys" "4.26.0" + +"@typescript-eslint/types@4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.26.0.tgz#7c6732c0414f0a69595f4f846ebe12616243d546" + integrity sha512-rADNgXl1kS/EKnDr3G+m7fB9yeJNnR9kF7xMiXL6mSIWpr3Wg5MhxyfEXy/IlYthsqwBqHOr22boFbf/u6O88A== + +"@typescript-eslint/typescript-estree@4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.0.tgz#aea17a40e62dc31c63d5b1bbe9a75783f2ce7109" + integrity sha512-GHUgahPcm9GfBuy3TzdsizCcPjKOAauG9xkz9TR8kOdssz2Iz9jRCSQm6+aVFa23d5NcSpo1GdHGSQKe0tlcbg== + dependencies: + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/visitor-keys" "4.26.0" + debug "^4.3.1" + globby "^11.0.3" is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/visitor-keys@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.1.tgz#6045ae25a11662c671f90b3a403d682dfca0b7a6" - integrity sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ== - dependencies: - "@typescript-eslint/types" "4.22.1" - eslint-visitor-keys "^2.0.0" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.25.0": - version "4.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz#863e7ed23da4287c5b469b13223255d0fde6aaa7" - integrity sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg== +"@typescript-eslint/visitor-keys@4.26.0": + version "4.26.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.0.tgz#26d2583169222815be4dcd1da4fe5459bc3bcc23" + integrity sha512-cw4j8lH38V1ycGBbF+aFiLUls9Z0Bw8QschP3mkth50BbWzgFS33ISIgBzUMuQ2IdahoEv/rXstr8Zhlz4B1Zg== dependencies: - "@typescript-eslint/types" "4.25.0" + "@typescript-eslint/types" "4.26.0" eslint-visitor-keys "^2.0.0" "@yarnpkg/lockfile@^1.1.0": @@ -1949,10 +1889,10 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.1.0: - version "8.2.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0" - integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg== +acorn@^8.2.4: + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88" + integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw== add-stream@^1.0.0: version "1.0.0" @@ -1994,9 +1934,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.3.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.3.0.tgz#25ee7348e32cdc4a1dbb38256bf6bdc451dd577c" - integrity sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q== + version "8.6.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.0.tgz#60cc45d9c46a477d80d92c48076d972c342e5720" + integrity sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2168,11 +2108,6 @@ array-differ@^3.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -2283,11 +2218,9 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" + version "1.0.4" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9" + integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA== aws-sdk-mock@^5.1.0: version "5.1.0" @@ -2299,9 +2232,9 @@ aws-sdk-mock@^5.1.0: traverse "^0.6.6" aws-sdk@^2.596.0, aws-sdk@^2.637.0, aws-sdk@^2.848.0: - version "2.903.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.903.0.tgz#4c8252723370ebbdaffe69f4dfddc5973b1dab4a" - integrity sha512-BP/giYLP8QJ63Jta59kph1F76oPITxRt/wNr3BdoEs9BtshWlGKk149UaseDB4wJtI+0TER5jtzBIUBcP6E+wA== + version "2.922.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.922.0.tgz#4568be067dceaaeda5d2d5a7e2f22666687f0b32" + integrity sha512-SufbR5TTCK94Zk/xIv4v/m0MM9z+KW999XnjXOyNWGFGHP9/FArjtHtq69+a3KpohYBR1dBj8wUhVjbClmQIBA== dependencies: buffer "4.9.2" events "1.1.1" @@ -2422,9 +2355,9 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" before-after-hook@^2.0.0, before-after-hook@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" - integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== bind-obj-methods@^2.0.0: version "2.0.2" @@ -2481,7 +2414,7 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.14.5: +browserslist@^4.16.6: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== @@ -2558,10 +2491,10 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^15.0.5: - version "15.0.6" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099" - integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w== +cacache@^15.0.5, cacache@^15.2.0: + version "15.2.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.2.0.tgz#73af75f77c58e72d8c630a7a2858cb18ef523389" + integrity sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw== dependencies: "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" @@ -2662,9 +2595,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001219: - version "1.0.30001228" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" - integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A== + version "1.0.30001235" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz#ad5ca75bc5a1f7b12df79ad806d715a43a5ac4ed" + integrity sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A== capture-exit@^2.0.0: version "2.0.0" @@ -2853,10 +2786,10 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -codemaker@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.29.0.tgz#c262d1149681348103c7a79913a9a39ba13098f5" - integrity sha512-dNUTiOhxNYB7MV75bLLCie1gr0SUh6wEOPc5OyZob4mLj51OETYIeRYILEiz39QVKLRx6YSbKoCY/S4PqQ0PNQ== +codemaker@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.30.0.tgz#c718a5178e5bdd06d6ab2ddb629edc64de80cb51" + integrity sha512-yntR55JhhVlZTfR4CPV6IrCULovPDrk3z0yQR7/ygEtNxEOQrHhX17djJ0rVmIwCJUawv+ODTJ1ipJY9CbxJQw== dependencies: camelcase "^6.2.0" decamelize "^5.0.0" @@ -2922,7 +2855,7 @@ columnify@^1.5.4: strip-ansi "^3.0.0" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@~1.0.6: +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" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2963,12 +2896,12 @@ component-emitter@^1.2.1: integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== compress-commons@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.0.tgz#25ec7a4528852ccd1d441a7d4353cd0ece11371b" - integrity sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== dependencies: buffer-crc32 "^0.2.13" - crc32-stream "^4.0.1" + crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" @@ -2988,9 +2921,9 @@ concat-stream@^2.0.0: typedarray "^0.0.6" config-chain@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" - integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -3249,7 +3182,7 @@ crc-32@^1.2.0: exit-on-epipe "~1.0.1" printj "~1.1.0" -crc32-stream@^4.0.1: +crc32-stream@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== @@ -3360,7 +3293,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/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, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -3545,9 +3478,9 @@ detect-indent@^5.0.0: integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= detect-indent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" - integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== + version "6.1.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== detect-newline@^2.1.0: version "2.1.0" @@ -3680,9 +3613,9 @@ ejs@^2.5.2: integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== electron-to-chromium@^1.3.723: - version "1.3.727" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" - integrity sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg== + version "1.3.749" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz#0ecebc529ceb49dd2a7c838ae425236644c3439a" + integrity sha512-F+v2zxZgw/fMwPz/VUGIggG4ZndDsYy0vlpthi3tjmDZlcfbhN5mYW0evXUsBr2sUtuDANFtle410A9u/sd/4A== emittery@^0.7.1: version "0.7.2" @@ -3747,10 +3680,10 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: - version "1.18.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" - integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: + version "1.18.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -3760,14 +3693,14 @@ es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: has-symbols "^1.0.2" is-callable "^1.2.3" is-negative-zero "^2.0.1" - is-regex "^1.1.2" - is-string "^1.0.5" - object-inspect "^1.9.0" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" object-keys "^1.1.1" object.assign "^4.1.2" string.prototype.trimend "^1.0.4" string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.0" + unbox-primitive "^1.0.1" es-get-iterator@^1.1.1: version "1.1.2" @@ -3802,10 +3735,10 @@ es6-error@^4.0.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.12.3: - version "0.12.3" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.3.tgz#9d978e8aa700034c3e3316e47df1c142ef290a72" - integrity sha512-0kuxNbGLGli8DYMHebJ4pk+RQ9ORYL1PmCSrSQpna1ioHvm+fzCAs99jwRYXGSfCNed1mj0vFSM9LbBm6YVbIQ== +esbuild@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce" + integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA== escalade@^3.1.1: version "3.1.1" @@ -3891,10 +3824,10 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@^2.23.3: - version "2.23.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.23.3.tgz#8a1b073289fff03c4af0f04b6df956b7d463e191" - integrity sha512-wDxdYbSB55F7T5CC7ucDjY641VvKmlRwT0Vxh7PkY1mI4rclVRFWYfsrjDgZvwYYDZ5ee0ZtfFKXowWjqvEoRQ== +eslint-plugin-import@^2.23.4: + version "2.23.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz#8dceb1ed6b73e46e50ec9a5bb2411b645e7d3d97" + integrity sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ== dependencies: array-includes "^3.1.3" array.prototype.flat "^1.2.4" @@ -3946,7 +3879,7 @@ eslint-plugin-standard@^4.1.0: resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== -eslint-scope@^5.0.0, eslint-scope@^5.1.1: +eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -3961,6 +3894,13 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: 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" + 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" @@ -3971,13 +3911,13 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@^7.27.0: - version "7.27.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7" - integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA== +eslint@^7.28.0: + version "7.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.28.0.tgz#435aa17a0b82c13bb2be9d51408b617e49c1e820" + integrity sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g== dependencies: "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.1" + "@eslint/eslintrc" "^0.4.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -3994,7 +3934,7 @@ eslint@^7.27.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" + glob-parent "^5.1.2" globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" @@ -4113,9 +4053,9 @@ execa@^4.0.0: strip-final-newline "^2.0.0" execa@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" - integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" get-stream "^6.0.0" @@ -4215,10 +4155,10 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-check@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.14.0.tgz#13e891977a7cc1ba87aa3883c75053990c02fb21" - integrity sha512-4hm0ioyEVCwgjBLBqriwAFYu3/yc7pNH9fBfvzi6JRWRs4R3mwZwrr/d4MHbY0fTBJ0Uakg4TaMAAQ8Cnt2+KQ== +fast-check@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.16.0.tgz#352271e6b29d465048ed943c68c14c7a7ce1e4ae" + integrity sha512-r1uoJQoLzKUgfAeGzSZ/7dGyvSLG3OGjmWIKZRJpKtY/791di7n/x389F0Bei3Ry8126Z6MKp78Cbt4+9LUp1g== dependencies: pure-rand "^4.1.1" @@ -4446,6 +4386,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/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" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -4725,7 +4674,7 @@ github-api@^3.4.0: js-base64 "^2.1.9" utf8 "^2.1.1" -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1: +glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -4749,21 +4698,14 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^13.6.0: - version "13.8.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3" - integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q== +globals@^13.6.0, globals@^13.9.0: + version "13.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" + integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.2: +globby@^11.0.2, globby@^11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== @@ -4989,9 +4931,9 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: safer-buffer ">= 2.1.2 < 3" iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" @@ -5333,12 +5275,12 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-potential-custom-element-name@^1.0.0: +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" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-regex@^1.1.1, is-regex@^1.1.2: +is-regex@^1.1.1, is-regex@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== @@ -5352,9 +5294,9 @@ is-set@^2.0.1, is-set@^2.0.2: integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-ssh@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b" - integrity sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ== + version "1.3.3" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" + integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== dependencies: protocols "^1.1.0" @@ -5368,7 +5310,7 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.5: +is-string@^1.0.5, is-string@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== @@ -6009,12 +5951,12 @@ jsbn@~0.1.0: integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= jsdom@^16.4.0: - version "16.5.3" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.5.3.tgz#13a755b3950eb938b4482c407238ddf16f0d2136" - integrity sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA== + version "16.6.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.6.0.tgz#f79b3786682065492a3da6a60a4695da983805ac" + integrity sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg== dependencies: abab "^2.0.5" - acorn "^8.1.0" + acorn "^8.2.4" acorn-globals "^6.0.0" cssom "^0.4.4" cssstyle "^2.3.0" @@ -6022,12 +5964,13 @@ jsdom@^16.4.0: decimal.js "^10.2.1" domexception "^2.0.1" escodegen "^2.0.0" + form-data "^3.0.0" html-encoding-sniffer "^2.0.1" - is-potential-custom-element-name "^1.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" nwsapi "^2.2.0" parse5 "6.0.1" - request "^2.88.2" - request-promise-native "^1.0.9" saxes "^5.0.1" symbol-tree "^3.2.4" tough-cookie "^4.0.0" @@ -6037,7 +5980,7 @@ jsdom@^16.4.0: whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" whatwg-url "^8.5.0" - ws "^7.4.4" + ws "^7.4.5" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -6045,65 +5988,65 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.29.0.tgz#572bd3e0c9172fac3f1bf1cc4635501f57bbc8a0" - integrity sha512-Zc+h5C2XUGgVtT3qxerIDtPlvtjie45b5o5bZ6T/R+p0ClULrYmRl74+u1ztLqtOYcVqkhs6Qj11dgyZOCGzBA== +jsii-diff@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.30.0.tgz#b905760ddf5e29c6c6ef31b8c670a2d1db7291c0" + integrity sha512-74GeV8ab8BrS3k8h8HKnI8f5PecsRahflElxJuc6bI9xA5AhRAzBF/Lrt5HibuYPuSsyLAmhTU1GTHdRvKq8aA== dependencies: - "@jsii/spec" "^1.29.0" + "@jsii/spec" "^1.30.0" fs-extra "^9.1.0" - jsii-reflect "^1.29.0" + jsii-reflect "^1.30.0" log4js "^6.3.0" typescript "~3.9.9" yargs "^16.2.0" -jsii-pacmak@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.29.0.tgz#12db86247f7350379d6a34d9a2aebec816c10152" - integrity sha512-wpVDrvh+hClB4Y68v/sYCcRnXlXoDwEUTC0X+uz9o5xUHs/WfuDglS5AAhq6g51INAQc0ed3anrkqmFcDK6QPw== +jsii-pacmak@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.30.0.tgz#a6a7570da1388027ce4e5ca1603d4144f341d307" + integrity sha512-hYvISYBXZ5WL/+LtG3HpVrimguqAoWa3D8jaqsnoiIGrdmaxKCZ0VnioJYxEX7wVamYuCwXu5NFx/b31BspU6A== dependencies: - "@jsii/spec" "^1.29.0" + "@jsii/spec" "^1.30.0" clone "^2.1.2" - codemaker "^1.29.0" + codemaker "^1.30.0" commonmark "^0.29.3" escape-string-regexp "^4.0.0" fs-extra "^9.1.0" - jsii-reflect "^1.29.0" - jsii-rosetta "^1.29.0" + jsii-reflect "^1.30.0" + jsii-rosetta "^1.30.0" semver "^7.3.5" spdx-license-list "^6.4.0" xmlbuilder "^15.1.1" yargs "^16.2.0" -jsii-reflect@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.29.0.tgz#eaa80f383619586cddab1f296bb5a30c7157c3a0" - integrity sha512-r1XpKsnaMTaI0B2XXJ4rF1rbufqFDThASrArE+SyuuGeWTJxWQ4UtIzvXNVFLbZba0A5hX4K0JxiMIfaRFCEEg== +jsii-reflect@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.30.0.tgz#b079d448ed35c9d9dfea8798a8ef39487ed0c86c" + integrity sha512-t/1Zr1gGqQSYt94Lfq860VLnCr8y8MLvlLorWYqmBeWKCaSPhtYSC1blGhZhDrAW+CBXiT0Oy64j4Q++AntRmw== dependencies: - "@jsii/spec" "^1.29.0" + "@jsii/spec" "^1.30.0" colors "^1.4.0" fs-extra "^9.1.0" - oo-ascii-tree "^1.29.0" + oo-ascii-tree "^1.30.0" yargs "^16.2.0" -jsii-rosetta@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.29.0.tgz#6363df806efd49ab09a0ff2062b7bf2cb289a141" - integrity sha512-WhTlFFm/xp2ictShT7XreBoqNpFj/U4MK4VyHyzYV1jS58uvJJMkwifMz/MOqciqRtWIAvGM3Ria4EB3VqmTfA== +jsii-rosetta@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.30.0.tgz#5c974eefef9a8e5e1b8364e53e6856f07c7eaf68" + integrity sha512-ChFg5qhvxCaM2bspCqizs48yMtsm5YLHjBoNZLCkbXyc3yMM5l8pnn787B5ww5TI3+tKxKYWkbiKf356kQ1OgQ== dependencies: - "@jsii/spec" "^1.29.0" + "@jsii/spec" "^1.30.0" commonmark "^0.29.3" fs-extra "^9.1.0" typescript "~3.9.9" - xmldom "^0.5.0" + xmldom "^0.6.0" yargs "^16.2.0" -jsii@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.29.0.tgz#1f34c29db9299ace0f361e6d72627d4478994396" - integrity sha512-7o1yE/si/nbGsNquSejwxaiPq0iDSTPfDENd7ZyO3xzGIROV8UZSs+VhGyys9t/VF4og8p9s2olkajEN60fzMw== +jsii@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.30.0.tgz#fe20f60e33d0beaae24bc6537fb623333e913da4" + integrity sha512-TfVHhGjP0QiTEkyfnxrDIE8Da+itxnNUK2YoD69qIPAzmZ58goKVqK4sbXrXz2urHSToGLDmWI8+H69cLeVjJw== dependencies: - "@jsii/spec" "^1.29.0" + "@jsii/spec" "^1.30.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.5" @@ -6354,23 +6297,23 @@ levn@~0.3.0: type-check "~0.3.2" libnpmaccess@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.2.tgz#781832fb7ccb867b26343a75a85ad9c43e50406e" - integrity sha512-avXtJibZuGap0/qADDYqb9zdpgzVu/yG5+tl2sTRa7MCkDNv2ZlGwCYI0r6/+tmqXPj0iB9fKexHz426vB326w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" + integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== dependencies: aproba "^2.0.0" minipass "^3.1.1" npm-package-arg "^8.1.2" - npm-registry-fetch "^10.0.0" + npm-registry-fetch "^11.0.0" libnpmpublish@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.1.tgz#08ca2cbb5d7f6be1ce4f3f9c49b3822682bcf166" - integrity sha512-hZCrZ8v4G9YH3DxpIyBdob25ijD5v5LNzRbwsej4pPDopjdcLLj1Widl+BUeFa7D0ble1JYL4F3owjLJqiA8yA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" + integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== dependencies: normalize-package-data "^3.0.2" npm-package-arg "^8.1.2" - npm-registry-fetch "^10.0.0" + npm-registry-fetch "^11.0.0" semver "^7.1.3" ssri "^8.0.1" @@ -6641,6 +6584,28 @@ make-fetch-happen@^8.0.9: socks-proxy-agent "^5.0.0" ssri "^8.0.0" +make-fetch-happen@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.0.2.tgz#aa8c0e4a5e3a5f2be86c54d3abed44fe5a32ad5d" + integrity sha512-UkAWAuXPXSSlVviTjH2We20mtj1NnZW2Qq/oTY2dyMbRQ5CR3Xed3akCDMnM7j6axrMY80lhgM7loNE132PfAw== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^5.0.0" + ssri "^8.0.0" + make-runnable@^1.3.9: version "1.3.9" resolved "https://registry.yarnpkg.com/make-runnable/-/make-runnable-1.3.9.tgz#924bf6b28c4f1524391ec34ec1f14fc24aac3af8" @@ -6817,17 +6782,17 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" - integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== dependencies: - mime-db "1.47.0" + mime-db "1.48.0" mimic-fn@^2.1.0: version "2.1.0" @@ -7039,6 +7004,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/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" + 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" @@ -7070,10 +7040,10 @@ nise@^4.0.4: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^13.0.11: - version "13.0.11" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.0.11.tgz#ba733252e720897ca50033205c39db0c7470f331" - integrity sha512-sKZltNkkWblkqqPAsjYW0bm3s9DcHRPiMOyKO/PkfJ+ANHZ2+LA2PLe22r4lLrKgXaiSaDQwW3qGsJFtIpQIeQ== +nock@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.1.0.tgz#41c8ce8b35ab7d618c4cbf40de1d5bce319979ba" + integrity sha512-3N3DUY8XYrxxzWazQ+nSBpiaJ3q6gcpNh4gXovC/QBxrsvNp4tq+wsLHF6mJ3nrn3lPLn7KCJqKxy/9aD+0fdw== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -7148,9 +7118,9 @@ node-preload@^0.2.1: process-on-spawn "^1.0.0" node-releases@^1.1.71: - version "1.1.71" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" - integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== nodeunit@^0.11.3: version "0.11.3" @@ -7207,10 +7177,10 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.0.1.tgz#a4f27f58cf8c7b287b440b8a8201f42d0b00d256" + integrity sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ== npm-bundled@^1.1.1, npm-bundled@^1.1.2: version "1.1.2" @@ -7246,9 +7216,9 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: 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: - version "8.1.2" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.2.tgz#b868016ae7de5619e729993fbd8d11dc3c52ab62" - integrity sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA== + version "8.1.4" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.4.tgz#8001cdbc4363997b8ef6c6cf7aaf543c5805879d" + integrity sha512-xLokoCFqj/rPdr3LvcdDL6Kj6ipXGEDHD/QGpzwU6/pibYUOXmp5DBmg76yukFyx4ZDbrXNOTn+BPyd8TD4Jlw== dependencies: hosted-git-info "^4.0.1" semver "^7.3.4" @@ -7274,13 +7244,12 @@ npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: npm-package-arg "^8.1.2" semver "^7.3.4" -npm-registry-fetch@^10.0.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-10.1.1.tgz#97bc7a0fca5e8f76cc5162185b8de8caa8bea639" - integrity sha512-F6a3l+ffCQ7hvvN16YG5bpm1rPZntCg66PLHDQ1apWJPOCUVHoKnL2w5fqEaTVhp42dmossTyXeR7hTGirfXrg== +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" + integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== dependencies: - lru-cache "^6.0.0" - make-fetch-happen "^8.0.9" + make-fetch-happen "^9.0.1" minipass "^3.1.3" minipass-fetch "^1.3.0" minipass-json-stream "^1.0.1" @@ -7418,7 +7387,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.9.0: +object-inspect@^1.10.3, object-inspect@^1.9.0: version "1.10.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== @@ -7470,14 +7439,13 @@ object.pick@^1.3.0: isobject "^3.0.1" object.values@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" - integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" + es-abstract "^1.18.2" octokit-pagination-methods@^1.1.0: version "1.1.0" @@ -7498,10 +7466,10 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.29.0.tgz#101db364fad798656bec7add53cd9ab1a4f2bf4e" - integrity sha512-DUwUL3Yc3lS2znWBlOi5eEU4pKoGGK2IaB/S7XygSBzmSS2jJE6+waAip17FNeNXfC4aXClr95HxZXamCLtYqQ== +oo-ascii-tree@^1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.30.0.tgz#5a20204d05370c0578b800836ed1e8c660d3c4e0" + integrity sha512-TzXuoCnha2QHFcAR+8+tBgD7Wnn6Uh+P3aZMoXKDJ3CVLXFnTnzHy4WMmmz01pTfv+f5haQMjhL9OIFJLEZ5kA== open@^7.4.2: version "7.4.2" @@ -7743,9 +7711,9 @@ package-hash@^4.0.0: release-zalgo "^1.0.0" pacote@^11.2.6: - version "11.3.3" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.3.tgz#d7d6091464f77c09691699df2ded13ab906b3e68" - integrity sha512-GQxBX+UcVZrrJRYMK2HoG+gPeSUX/rQhnbPkkGrCYa4n2F/bgClFPaMm0nsdnYrxnmUy85uMHoFXZ0jTD0drew== + version "11.3.4" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.4.tgz#c290b790a5cee3082bb8fa223f3f3e2fdf3d0bfc" + integrity sha512-RfahPCunM9GI7ryJV/zY0bWQiokZyLqaSNHXtbNSoLb7bwTvBbJBEyCJ01KWs4j1Gj7GmX8crYXQ1sNX6P2VKA== dependencies: "@npmcli/git" "^2.0.1" "@npmcli/installed-package-contents" "^1.0.6" @@ -7760,7 +7728,7 @@ pacote@^11.2.6: npm-package-arg "^8.0.1" npm-packlist "^2.1.4" npm-pick-manifest "^6.0.0" - npm-registry-fetch "^10.0.0" + npm-registry-fetch "^11.0.0" promise-retry "^2.0.1" read-package-json-fast "^2.0.1" rimraf "^3.0.2" @@ -7820,12 +7788,12 @@ parse-path@^4.0.0: query-string "^6.13.8" parse-url@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.2.tgz#856a3be1fcdf78dc93fc8b3791f169072d898b59" - integrity sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA== + version "5.0.3" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.3.tgz#c158560f14cb1560917e0b7fd8b01adc1e9d3cab" + integrity sha512-nrLCVMJpqo12X8uUJT4GJPd5AFaTOrGx/QpJy3HNcVtq0AZSstVIsnxS5fqNPuoqMUs3MyfBoOP6Zvu2Arok5A== dependencies: is-ssh "^1.3.0" - normalize-url "^3.3.0" + normalize-url "^6.0.1" parse-path "^4.0.0" protocols "^1.4.0" @@ -7891,9 +7859,9 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/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" @@ -7929,9 +7897,9 @@ performance-now@^2.1.0: integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^2.0.0, pify@^2.3.0: version "2.3.0" @@ -8154,9 +8122,9 @@ punycode@^2.0.0, punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== pure-rand@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-4.1.2.tgz#cbad2a3e3ea6df0a8d80d8ba204779b5679a5205" - integrity sha512-uLzZpQWfroIqyFWmX/pl0OL2JHJdoU3dbh0dvZ25fChHFJJi56J5oQZhW6QgbT2Llwh1upki84LnTwlZvsungA== + version "4.2.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-4.2.0.tgz#ea08faf7accdad7bbcb68295641881b2ea5eb74c" + integrity sha512-d2IwZpSFSLGWmjThAdMECofl07uOMqmuIHqH8mRY7h7Hl2jY5ZJZ9fx9dEBIKkKyk4jmEvS0vnpdFUtzPi3Ctg== q@^1.5.1: version "1.5.1" @@ -8457,22 +8425,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -8973,9 +8925,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" - integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + version "3.0.9" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" + integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== spdx-license-list@^6.4.0: version "6.4.0" @@ -9083,11 +9035,6 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - streamroller@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" @@ -9279,19 +9226,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@*: - version "6.7.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.0.tgz#26274751f0ee099c547f6cb91d3eff0d61d155b2" - integrity sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" - -table@^6.0.9, table@^6.7.1: +table@*, table@^6.0.9, table@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== @@ -9568,14 +9503,6 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -9585,10 +9512,18 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" - integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== dependencies: punycode "^2.1.1" @@ -9603,9 +9538,9 @@ trim-newlines@^1.0.0: integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trim-off-newlines@^1.0.0: version "1.0.1" @@ -9686,7 +9621,7 @@ tslib@^2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== -tsutils@^3.17.1: +tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== @@ -9771,17 +9706,17 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript-json-schema@^0.50.0: - version "0.50.0" - resolved "https://registry.yarnpkg.com/typescript-json-schema/-/typescript-json-schema-0.50.0.tgz#4a78b6b0eca0cfcd518ccfbacef4575ee50c2e45" - integrity sha512-53X6J+Mgf/4P9qkPnLM0tkfbGMzjL2MDSsMvpO5MKyZOMp//Widup3oJS9Su9GnIme01ki7LARCPh6Y0Ej14iQ== +typescript-json-schema@^0.50.1: + version "0.50.1" + resolved "https://registry.yarnpkg.com/typescript-json-schema/-/typescript-json-schema-0.50.1.tgz#48041eb9f6efbdf4ba88c3e3af9433601f7a2b47" + integrity sha512-GCof/SDoiTDl0qzPonNEV4CHyCsZEIIf+mZtlrjoD8vURCcEzEfa2deRuxYid8Znp/e27eDR7Cjg8jgGrimBCA== dependencies: "@types/json-schema" "^7.0.7" "@types/node" "^14.14.33" glob "^7.1.6" json-stable-stringify "^1.0.1" ts-node "^9.1.1" - typescript "^4.2.3" + typescript "~4.2.3" yargs "^16.2.0" typescript@^3.3.3, typescript@~3.9.9: @@ -9789,25 +9724,25 @@ typescript@^3.3.3, typescript@~3.9.9: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== -typescript@^4.2.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== - typescript@~3.8.3: version "3.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@~4.2.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/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" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.13.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.5.tgz#5d71d6dbba64cf441f32929b1efce7365bb4f113" - integrity sha512-xtB8yEqIkn7zmOyS2zUNBsYCBRhDkvlNxMMY2smuJ/qA8NCHeQvKCF3i9Z4k8FJH4+PJvZRtMrPynfZ75+CSZw== + version "3.13.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.9.tgz#4d8d21dcd497f29cfd8e9378b9df123ad025999b" + integrity sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g== uid-number@0.0.6: version "0.0.6" @@ -9819,7 +9754,7 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -unbox-primitive@^1.0.0: +unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== @@ -10221,10 +10156,10 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@^7.4.4: - version "7.4.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" - integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== +ws@^7.4.5: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xml-js@^1.6.11: version "1.6.11" @@ -10266,10 +10201,10 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmldom@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e" - integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA== +xmldom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== xregexp@2.0.0: version "2.0.0"